/*
 *  Copyright (c) 2000-2022 Inria
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  * Neither the name of the ALICE Project-Team nor the names of its
 *  contributors may be used to endorse or promote products derived from this
 *  software without specific prior written permission.
 * 
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

#include <geogram/basic/common.h>
#include <geogram/basic/logger.h>
#include <geogram/basic/command_line.h>
#include <geogram/basic/command_line_args.h>
#include <geogram/basic/stopwatch.h>
#include <geogram/basic/file_system.h>
#include <geogram/mesh/mesh.h>
#include <geogram/mesh/mesh_io.h>
#include <geogram/mesh/mesh_reorder.h>
#include <geogram/delaunay/delaunay.h>
#include <algorithm>

namespace {
    using namespace GEO;
    
   /**
    * \brief Loads points from a file.
    * \param[in] points_filename the name of the file with the points.
    *  -If the example was compiled with the Geogram library, then any
    *  mesh file handled by Geogram can be used.
    *  -if the example was compiled with Delaunay_psm (single file), then
    *  the file should be ASCII, with one point per line.
    * \param[in] dimension number of coordinates of the points.
    * \param[out] points the loaded points, in a single vector of coordinates.
    *  In the end, the number of loaded points is points.size()/dimension.
    */
    bool load_points(
	const std::string& points_filename,
	index_t dimension,
	vector<double>& points
	) {
#ifdef GEOGRAM_PSM
	// Simple data input: one point per line, coordinates in ASCII
	LineInput input(points_filename);
	if(!input.OK()) {
	    return false;
	}
	while(!input.eof() && input.get_line()) {
	    input.get_fields();
	    if(input.nb_fields() == dimension) {
		for(index_t c=0; c<dimension; ++c) {
		    points.push_back(input.field_as_double(c));
		}
	    }
	}
#else
	// Using Geogram mesh I/O
	Mesh M;
	MeshIOFlags flags;
	flags.reset_element(MESH_FACETS);
	flags.reset_element(MESH_CELLS);
	if(!mesh_load(points_filename, M, flags)) {
	    return false;
	}
	M.vertices.set_dimension(dimension);
	index_t nb_points = M.vertices.nb();
	points.resize(nb_points * dimension);
	Memory::copy(
	    points.data(),
	    M.vertices.point_ptr(0),
	    M.vertices.nb()*dimension*sizeof(double)
	);
#endif
	return true;
    }

   /**
    * \brief Saves a Delaunay triangulation to a file.
    * \param[in] delaunay a pointer to the Delaunay triangulation.
    * \param[in] filename the name of the file to be saved.
    *  -If the example was compiled with the Geogram library, then any
    *  mesh file handled by Geogram can be used.
    *  if the example was compiled with Delaunay_psm (single file), then
    *  the points and vertices of the triangulation are output in ASCII.
    * \param[in] convex_hull_only if true, then only the triangles on the
    *  convex hull are output.
    */
    void save_Delaunay(
	Delaunay* delaunay, const std::string& filename,
	bool convex_hull_only = false
    ) {
	vector<index_t> tri2v;
	
	if(convex_hull_only) {
	    
	    // The convex hull can be efficiently traversed only if infinite
	    // tetrahedra are kept.
	    geo_assert(delaunay->keeps_infinite());
	    
	    // The convex hull can be retrieved as the finite facets
	    // of the infinite cells (note: it would be also possible to
	    // throw away the infinite cells and get the convex hull as
	    // the facets adjacent to no cell). Here we use the infinite
	    // cells to show an example with them.
	    
	    
	    // This block is just a sanity check
	    {
		for(index_t t=0; t < delaunay->nb_finite_cells(); ++t) {
		    geo_debug_assert(delaunay->cell_is_finite(t));
		}
		
		for(index_t t=delaunay->nb_finite_cells();
		    t < delaunay->nb_cells(); ++t) {
		    geo_debug_assert(delaunay->cell_is_infinite(t));
		}
	    }
	    
	    // This iterates on the infinite cells
	    for(
		index_t t = delaunay->nb_finite_cells();
		t < delaunay->nb_cells(); ++t
	     ) {
		for(index_t lv=0; lv<4; ++lv) {
		    signed_index_t v = delaunay->cell_vertex(t,lv);
		    if(v != -1) {
			tri2v.push_back(index_t(v));
		    }
		}
	    }
	}
	
#ifdef GEOGRAM_PSM
	// Simple data output: output vertices and simplices
	
	Logger::out("Delaunay") << "Saving output to " << filename << std::endl;
	std::ofstream out(filename.c_str());
	
	out << delaunay->nb_vertices() << " vertices" << std::endl;
	for(index_t v=0; v < delaunay->nb_vertices(); ++v) {
	    for(index_t c=0; c < delaunay->dimension(); ++c) {
		out << delaunay->vertex_ptr(v)[c] << " ";
	    }
	    out << std::endl;
	}
	if(convex_hull_only) {
	    out << tri2v.size()/3 << " simplices" << std::endl;
	    for(index_t t=0; t<tri2v.size()/3; ++t) {
		out << tri2v[3*t] << " "
		    << tri2v[3*t+1] << " "
		    << tri2v[3*t+2] << std::endl;
	    }
	} else {
	    out << delaunay->nb_cells() << " simplices" << std::endl;
	    for(index_t t=0; t<delaunay->nb_cells(); ++t) {
		for(index_t lv=0; lv<delaunay->cell_size(); ++lv) {
		    out << delaunay->cell_vertex(t,lv) << " ";
		}
		out << std::endl;
	    }
	}
	
#else
	// Using Geogram mesh I/O: copy Delaunay into a Geogram
	// mesh and save it to disk.
	
	Mesh M_out;
	vector<double> pts(delaunay->nb_vertices() * 3);
	for(index_t v = 0; v < delaunay->nb_vertices(); ++v) {
	    pts[3 * v] = delaunay->vertex_ptr(v)[0];
	    pts[3 * v + 1] = delaunay->vertex_ptr(v)[1];
	    pts[3 * v + 2] =
		(delaunay->dimension() >= 3) ? delaunay->vertex_ptr(v)[2] : 0.0;
	}
	
	if(convex_hull_only) {
	    M_out.facets.assign_triangle_mesh(3, pts, tri2v, true);
	} else if(delaunay->dimension() == 3) {
	    vector<index_t> tet2v(delaunay->nb_cells() * 4);
	    for(index_t t = 0; t < delaunay->nb_cells(); ++t) {
	    tet2v[4 * t] = index_t(delaunay->cell_vertex(t, 0));
	    tet2v[4 * t + 1] = index_t(delaunay->cell_vertex(t, 1));
	    tet2v[4 * t + 2] = index_t(delaunay->cell_vertex(t, 2));
	    tet2v[4 * t + 3] = index_t(delaunay->cell_vertex(t, 3));
	    }
	    M_out.cells.assign_tet_mesh(3, pts, tet2v, true);
	} else if(delaunay->dimension() == 2) {
	    tri2v.resize(delaunay->nb_cells() * 3);
	    for(index_t t = 0; t < delaunay->nb_cells(); ++t) {
		tri2v[3 * t] = index_t(delaunay->cell_vertex(t, 0));
		tri2v[3 * t + 1] = index_t(delaunay->cell_vertex(t, 1));
		tri2v[3 * t + 2] = index_t(delaunay->cell_vertex(t, 2));
	    }
	    M_out.facets.assign_triangle_mesh(3, pts, tri2v, true);
	}
	M_out.show_stats();
	
	Logger::div("Saving the result");
	MeshIOFlags flags;
	flags.set_element(MESH_FACETS);            
	flags.set_element(MESH_CELLS);
	mesh_save(M_out, filename, flags);
#endif    
    }
    
}

int main(int argc, char** argv) {
    using namespace GEO;

    // Needs to be called once.
    GEO::initialize();

    try {

        Stopwatch Wtot("Total time");

        std::vector<std::string> filenames;
 
        CmdLine::import_arg_group("standard");
        CmdLine::import_arg_group("algo");

        CmdLine::declare_arg(
            "convex_hull", false,
            "compute just the convex hull of the points"
        );

        CmdLine::declare_arg(
            "dimension", 3, "3 for 3D, 2 for 2D"
        );

        CmdLine::set_arg("algo:delaunay","default");
        
        if(
            !CmdLine::parse(
                argc, argv, filenames, "pointsfile <outputfile|none>"
            )
        ) {
            return 1;
        }


        std::string points_filename = filenames[0];

        std::string output_filename =
            filenames.size() >= 2 ? filenames[1] : std::string("out.mesh");

        bool output = (output_filename != "none");

        Logger::div("Data I/O");

        Logger::out("I/O") << "Output = " << output_filename << std::endl;

        bool convex_hull_only = CmdLine::get_arg_bool("convex_hull");
        index_t dimension = index_t(CmdLine::get_arg_int("dimension"));

        std::string del = CmdLine::get_arg("algo:delaunay");
        if(del == "default") {
            if(dimension == 3) {
                if(DelaunayFactory::has_creator("PDEL")) {
  		    // PDEL = Parallel 3D Delaunay
                    CmdLine::set_arg("algo:delaunay", "PDEL");
                } else {
		    // BDEL = Sequential 3D Delaunay
                    CmdLine::set_arg("algo:delaunay", "BDEL");
                }
            } else if(dimension == 2) {
	        // BDEL2d = Sequential 2D Delaunay
                CmdLine::set_arg("algo:delaunay", "BDEL2d");                
            }
        }

        Logger::out("Delaunay")
            << "Using " << CmdLine::get_arg("algo:delaunay") << std::endl;

        // Note: To create a parallel Delaunay 3D, one can use directly:
        // Delaunay_var delaunay = Delaunay::create(3,"PDEL") instead
        // of the line below (that uses the command line to select the
        // implementation of Delaunay).
        Delaunay_var delaunay = Delaunay::create(coord_index_t(dimension));

	//   If we want the convex hull, we keep the infinite facets,
	// because the convex hull can be retreived as the finite facets
	// of the infinite cells (note: it would be also possible to
	// throw away the infinite cells and get the convex hull as
	// the facets adjacent to no cell).
	if(convex_hull_only) {
	    delaunay->set_keeps_infinite(true);
	}

	vector<double> points;
	
	if(!load_points(points_filename, dimension, points)) {
	    Logger::err("Delaunay") << "Could not load points" << std::endl;
	    return 1;
	}

	index_t nb_points = points.size() / dimension;
	
	Logger::out("Delaunay")
	    << "Loaded " << nb_points << " points" << std::endl;

	double time = 0.0;
	{
	    Stopwatch Wdel("Delaunay");
	    // Note: this does not transfer ownership of memory, caller
	    // is still responsible of the memory of the points (here the
    	    // vector<double>). No memory is copied, Delaunay just keeps
	    // a pointer.
	    delaunay->set_vertices(nb_points, points.data());
	    time = Wdel.elapsed_time();
	}
	
        Logger::out("Delaunay") << delaunay->nb_cells() << " tetrahedra"
            << std::endl;

	Logger::out("Delaunay") << double(delaunay->nb_cells()) / time
				<< " tetrahedra / second"
				<< std::endl;
	
        if(output) {
	    save_Delaunay(delaunay, output_filename, convex_hull_only);
	}
    }
    catch(const std::exception& e) {
        std::cerr << "Received an exception: " << e.what() << std::endl;
        return 1;
    }

    Logger::out("") << "Everything OK, Returning status 0" << std::endl;
    return 0;
}

