/*HEAD Geogram.cpp */
/*==================================================================================================

                    Copyright Siemens 2019
               Published - All rights reserved

====================================================================================================
File description: 
        A wrapper API for geogram dll.

        Version 1.0

$HISTORY$
==================================================================================================*/

#include <geogram/basic/common.h>
#include <geogram/basic/command_line.h>
#include <geogram/basic/command_line_args.h>
#include <geogram/mesh/mesh.h>
#include <geogram/mesh/mesh_io.h>
#include <geogram/mesh/mesh_distance.h>
#include <geogram/mesh/mesh_geometry.h>
#include <geogram/mesh/mesh_repair.h>
#include <geogram/mesh/mesh_remesh.h>
#include <geogram/mesh/mesh_topology.h>
#include <geogram/points/co3ne.h>
#include <geogram/points/nn_search.h>
#include <geogram/delaunay/delaunay_3d.h>
#include <geogram/mesh/mesh_preprocessing.h>
#include "Geogram.h"

//////////////////////////////////////////////////////////////////////////
static void Print(std::ostream* logger, const char* message)
{
	if (!logger)
		return;
	*logger << message;
	logger->flush();
}

//////////////////////////////////////////////////////////////////////////
static bool InitGeogram()
{
	try
	{
		//Initializing GEO with all options
		GEO::initialize();
		GEO::CmdLine::initialize();
		//Import the arguments for default settings
		GEO::CmdLine::import_arg_group("standard");
		GEO::CmdLine::import_arg_group("remesh");
		GEO::CmdLine::import_arg_group("pre");
		GEO::CmdLine::import_arg_group("post");
		GEO::CmdLine::import_arg_group("algo");
		GEO::CmdLine::import_arg_group("opt");
		GEO::CmdLine::import_arg_group("co3ne");
		GEO::CmdLine::import_arg_group("stat");
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static GEOMesh* Construct(std::ostream* logger)
{
	try
	{
		Print(logger, "Constructing GEO::Mesh\n");
		return new GEOMesh(3, false);
	}
	catch (std::exception&)
	{
		return NULL;
	}
}

//////////////////////////////////////////////////////////////////////////
static void Destroy(GEOMesh* mesh)
{
	delete mesh;
}

//////////////////////////////////////////////////////////////////////////
static bool LoadMeshFromFile(const char* fileName, GEOMesh* mesh)
{
	try
	{
		InitGeogram();
		mesh->clear();
		return GEO::mesh_load(fileName, *mesh);
	}
	catch (std::exception&)
	{
		return false;
	}
}

//////////////////////////////////////////////////////////////////////////
static bool RepairMesh(double searchRadius, bool vertexOnly, GEOMesh* mesh)
{
	try
	{
		GEO::mesh_repair(*mesh, vertexOnly ? GEO::MESH_REPAIR_COLOCATE : GEO::MESH_REPAIR_DEFAULT, searchRadius);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool MeshSurfaceArea(const GEOMesh* mesh, double* surfaceArea)
{
	try
	{
		*surfaceArea = GEO::Geom::mesh_area(*mesh, 3);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool RemoveSmallConnectedComponents(double minComponentArea, unsigned int minComponentFacets, GEOMesh* mesh)
{
	try
	{
		GEO::remove_small_connected_components(*mesh, minComponentArea, minComponentFacets);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool FillHoles(double maxArea, unsigned int maxNumEdges, bool repair, GEOMesh* mesh)
{
	try
	{
		GEO::fill_holes(*mesh, maxArea, maxNumEdges, repair);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool PostProcessRDT(GEOMesh* mesh)
{	
	try
	{
		GEO::mesh_postprocess_RDT(*mesh);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool SmoothMesh(int smoothFactor, int numNeighbors, GEOMesh* mesh)
{
	try
	{
		GEO::Co3Ne_smooth(*mesh, numNeighbors, smoothFactor);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool ReconstructSurface(double searchRadius, int smoothFactor, unsigned int maxHoleEdges, 
	double maxHoleArea, unsigned int minCompFacets, double minCompArea, GEOMesh* mesh)
{
	GEO::CmdLine::set_arg("co3ne:use_normals", false);
	GEO::CmdLine::set_arg("co3ne:repair", true);
	GEO::CmdLine::set_arg("co3ne:max_hole_edges", maxHoleEdges);
	GEO::CmdLine::set_arg_percent("co3ne:max_hole_area", maxHoleArea);
	GEO::CmdLine::set_arg("co3ne:min_comp_facets", minCompFacets);
	GEO::CmdLine::set_arg_percent("co3ne:min_comp_area", minCompArea);
	try
	{
		GEO::Co3Ne_smooth_and_reconstruct(*mesh, 30, smoothFactor, searchRadius);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static int GetNumVertices(const GEOMesh* mesh)
{
	return mesh->vertices.nb();
}

//////////////////////////////////////////////////////////////////////////
static double* GetVertex(int vertexId, GEOMesh* mesh)
{
	return mesh->vertices.point_ptr(vertexId);
}

//////////////////////////////////////////////////////////////////////////
static int GetNumFacets(const GEOMesh* mesh)
{
	return mesh->facets.nb();
}

//////////////////////////////////////////////////////////////////////////
static double* GetVertexOnFacet(int facetId, int vertexLocalId, GEOMesh* mesh)
{
	return mesh->vertices.point_ptr(mesh->facets.vertex(facetId, vertexLocalId));
}

//////////////////////////////////////////////////////////////////////////
static int GetVertexIdOnFacet(int facetId, int vertexLocalId, const GEOMesh* mesh)
{
	return mesh->facets.vertex(facetId, vertexLocalId);
}

//////////////////////////////////////////////////////////////////////////
static bool ClearMesh(GEOMesh* mesh)
{
	try
	{
		mesh->clear();
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static int AddVertex(double* point, GEOMesh* mesh)
{
	return (int)mesh->vertices.create_vertex(point);
}

//////////////////////////////////////////////////////////////////////////
static bool CreateVerticesFromArray(const double* pointArray, int numPoint, GEOMesh* mesh)
{
	try
	{
		InitGeogram();
		mesh->clear();
		mesh->vertices.assign_points(pointArray, 3, numPoint);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool CreateMeshTrianglesFromArray(const double* pointArray, int numTriangles, GEOMesh* mesh)
{
	try
	{
		InitGeogram();
		mesh->clear();
		mesh->vertices.assign_points(pointArray, 3, 3 * numTriangles);
		mesh->facets.create_triangles(numTriangles);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static double* GetVertexPtr(GEOMesh* mesh)
{
	return mesh->vertices.point_ptr(0);
}

//////////////////////////////////////////////////////////////////////////
static int* GetVertexConnectionsPtr(GEOMesh* mesh)
{
	return (int*)mesh->facet_corners.vertex_index_ptr(0);
}

//////////////////////////////////////////////////////////////////////////
static bool LaplacianSmooth(unsigned int smoothIter, bool normalsOnly, GEOMesh* mesh)
{
	try
	{
		GEO::simple_Laplacian_smooth(*mesh, smoothIter, normalsOnly);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool ComputeSizingField(double sizeAdapter, unsigned int numbSamples, GEOMesh* mesh)
{
	try
	{
		GEO::compute_sizing_field(*mesh, sizeAdapter, numbSamples);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool SetAnisotropy(double shapeAdapter, GEOMesh* mesh)
{
	try
	{
		GEO::set_anisotropy(*mesh, shapeAdapter);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool RemeshSmooth(unsigned int numbVertOut, unsigned int numbLloydIter, unsigned int numbNewtonIter, unsigned int numbNewtonInnerIter, GEOMesh* mesh)
{
	try
	{
		GEOMesh* meshOut = new GEOMesh(3, false);
		GEO::remesh_smooth(*mesh, *meshOut, numbVertOut, 0, numbLloydIter, numbNewtonIter, numbNewtonInnerIter);
		mesh->copy(*meshOut);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static bool ComputeNormals(GEOMesh* mesh)
{
	try
	{
		GEO::compute_normals(*mesh);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static int GetDimension(const GEOMesh* mesh)
{
	return mesh->vertices.dimension();
}

//////////////////////////////////////////////////////////////////////////
static bool SetDimension(unsigned int vertDdimension, GEOMesh* mesh)
{
	try
	{
		mesh->vertices.set_dimension(vertDdimension);
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
static GEONearestNeighborSearch* ConstructNNSearch(std::ostream* logger)
{
	try
	{
		Print(logger, "Constructing GEO::NearestNeighborSearch\n");
		return GEONearestNeighborSearch::create(3, "BNN");
	}
	catch (std::exception&)
	{
		return NULL;
	}
}

//////////////////////////////////////////////////////////////////////////
static void DestroyNNSearch(GEONearestNeighborSearch* nnSearch)
{
	nnSearch->unref();
}

//////////////////////////////////////////////////////////////////////////
bool InitNNSearch(bool executeSearch, unsigned int numPoints, const double* points, GEONearestNeighborSearch* nnSearch)
{
	try
	{
		nnSearch->set_points(numPoints, points, 3);
		nnSearch->set_exact(executeSearch); //Set approximate search executeSearch = false for faster search
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
bool GetNNSqDistance(const double* queryPoint, bool queryFromThisMesh, GEONearestNeighborSearch* nnSearch, double* sqrDist)
{
	GEO::index_t neighbors[2]; // = new GEO::index_t[2];
	double neighborsSquareDistance[2];
	try
	{
		nnSearch->get_nearest_neighbors(2, queryPoint, neighbors, neighborsSquareDistance);
		if (queryFromThisMesh)
			*sqrDist = neighborsSquareDistance[1]; //neighborsSquareDistance[0] is 0 if query point belongs to the cloud 
		else
			*sqrDist = neighborsSquareDistance[0];
	}
	catch (std::exception&)
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
GeogramDispatchTables const* GetGeogramDispatchTables()
{	
	static GeogramDispatchTables gDispatchTable;
	static GeoMeshFnTable gMeshTable;

	gDispatchTable.geoMesh = &gMeshTable;
	gMeshTable.Construct = Construct;
	gMeshTable.Destroy = Destroy;
	gMeshTable.InitGeogram = InitGeogram;
	gMeshTable.LoadMeshFromFile = LoadMeshFromFile;
	gMeshTable.RepairMesh = RepairMesh;
	gMeshTable.SmoothMesh = SmoothMesh;
	gMeshTable.ReconstructSurface = ReconstructSurface;
	gMeshTable.MeshSurfaceArea = MeshSurfaceArea;
	gMeshTable.RemoveSmallConnectedComponents = RemoveSmallConnectedComponents;
	gMeshTable.FillHoles = FillHoles;
	gMeshTable.PostProcessRDT = PostProcessRDT;
	gMeshTable.GetNumVertices = GetNumVertices;
	gMeshTable.GetVertex = GetVertex;
	gMeshTable.GetNumFacets = GetNumFacets;
	gMeshTable.GetVertexOnFacet = GetVertexOnFacet;
	gMeshTable.GetVertexIdOnFacet = GetVertexIdOnFacet;
	gMeshTable.ClearMesh = ClearMesh;
	gMeshTable.AddVertex = AddVertex;
	gMeshTable.CreateVerticesFromArray = CreateVerticesFromArray;
	gMeshTable.CreateMeshTrianglesFromArray = CreateMeshTrianglesFromArray;
	gMeshTable.GetVertexPtr = GetVertexPtr;
	gMeshTable.GetVertexConnectionsPtr = GetVertexConnectionsPtr;
	gMeshTable.LaplacianSmooth = LaplacianSmooth;
	gMeshTable.ComputeSizingField = ComputeSizingField;
	gMeshTable.SetAnisotropy = SetAnisotropy;
	gMeshTable.RemeshSmooth = RemeshSmooth;
	gMeshTable.ComputeNormals = ComputeNormals;
	gMeshTable.GetDimension = GetDimension;
	gMeshTable.SetDimension = SetDimension;

	static NearestNeighborSearchFnTable nnSearchTable;

	gDispatchTable.nnSearch = &nnSearchTable;
	nnSearchTable.Construct = ConstructNNSearch;
	nnSearchTable.Destroy = DestroyNNSearch;
	nnSearchTable.InitNNSearch = InitNNSearch;
	nnSearchTable.GetNNSqDistance = GetNNSqDistance;

	return &gDispatchTable;
}
