/*****************************************************************************
 * MeshLab - Alpha Wrap plugin                                       o o     *
 * by PTC, based on CGAL                                           o     o   *
 *                                                                _   O  _   *
 * Copyright(C) 2023                                                \/)\/    *
 * PTC                                                             /\/|      *
 * Lex van der Sluijs                                                 |      *
 *                                                                    \      *
 * All rights reserved.                                                      *
 *                                                                           *
 * This program is free software; you can redistribute it and/or modify      *
 * it under the terms of the GNU General Public License as published by      *
 * the Free Software Foundation; either version 2 of the License, or         *
 * (at your option) any later version.                                       *
 *                                                                           *
 * This program is distributed in the hope that it will be useful,           *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)          *
 * for more details.                                                         *
 *                                                                           *
 ****************************************************************************/

#include "filter_mesh_alpha_wrap.h"

#include <common/utilities/eigen_mesh_conversions.h>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
#include <CGAL/Surface_mesh.h>
#include "CGAL/alpha_wrap_3.h"
#include "qdebug.h"


namespace PMP = CGAL::Polygon_mesh_processing;
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Point_3 = K::Point_3;
using CgalMesh = CGAL::Surface_mesh<Point_3>;

typedef CgalMesh::Vertex_index vertex_descriptor;
typedef CgalMesh::Face_index face_descriptor;

typedef boost::graph_traits<CgalMesh>::vertex_descriptor boost_vertex_descriptor;

/**
 * @brief
 * Constructor usually performs only two simple tasks of filling the two lists
 *  - typeList: with all the possible id of the filtering actions
 *  - actionList with the corresponding actions.
 * If you want to add icons to your filtering actions you can do here by construction the QActions
 * accordingly
 */
FilterMeshAlphaWrap::FilterMeshAlphaWrap()
{
	typeList = {MESH_ALPHA_WRAP};

	for (const ActionIDType& tt : typeList)
		actionList.push_back(new QAction(filterName(tt), this));
}

QString FilterMeshAlphaWrap::pluginName() const
{
	return "FilterMeshAlphaWrap";
}

QString FilterMeshAlphaWrap::vendor() const
{
    return "PTC";
}

/**
 * @brief ST() must return the very short string describing each filtering action
 * (this string is used also to define the menu entry)
 * @param filterId: the id of the filter
 * @return the name of the filter
 */
QString FilterMeshAlphaWrap::filterName(ActionIDType filterId) const
{
	switch (filterId) {
	case MESH_ALPHA_WRAP: return "Alpha Wrap";
	default: assert(0); return QString();
	}
}

QString FilterMeshAlphaWrap::pythonFilterName(ActionIDType f) const
{
	switch (f) {
	case MESH_ALPHA_WRAP: return "generate_alpha_wrap";
	default: assert(0); return QString();
	}
}

/**
 * @brief // Info() must return the longer string describing each filtering action
 * (this string is used in the About plugin dialog)
 * @param filterId: the id of the filter
 * @return an info string of the filter
 */
QString FilterMeshAlphaWrap::filterInfo(ActionIDType filterId) const
{
	QString description =
		"This filter compute Alpha wrapping with an offset. <br>"
		"The filter uses the original code provided in CGAL "
		"<a href=\"https://doc.cgal.org/latest/Alpha_wrap_3/index.html#Chapter_3D_Alpha_wrapping\">3D Alpha Wrapping</a>.<br>"
		"<br>"
		"The implementation is based on the following paper:<br>"
		"Cédric Portaneri, Mael Rouxel-Labbé, Michael Hemmer, David Cohen-Steiner, Pierre Alliez, "
		"<a href=\"https://inria.hal.science/hal-03688637\"><b>\"Alpha Wrapping with an Offset\"</b></a> (2022)<br><br>This plugin is contributed by Lex van der Sluijs at PTC.<br>";
	switch (filterId) {
	case MESH_ALPHA_WRAP: return description;
	default: assert(0); return "Unknown Filter";
	}
}

/**
 * @brief The FilterClass describes in which generic class of filters it fits.
 * This choice affect the submenu in which each filter will be placed
 * More than a single class can be chosen.
 * @param a: the action of the filter
 * @return the class od the filter
 */
FilterMeshAlphaWrap::FilterClass FilterMeshAlphaWrap::getClass(const QAction* a) const
{
	switch (ID(a)) {
	case MESH_ALPHA_WRAP:
		return FilterPlugin::FilterClass(
			FilterPlugin::FilterClass(FilterPlugin::Remeshing));
	default: assert(0); return FilterPlugin::Generic;
	}
}

/**
 * @brief FilterSamplePlugin::filterArity
 * @return
 */
FilterPlugin::FilterArity FilterMeshAlphaWrap::filterArity(const QAction*) const
{
    return FIXED;
}

/**
 * @brief FilterSamplePlugin::getPreConditions
 * @return
 */
// int FilterMeshBooleans::getPreConditions(const QAction*) const
//{
//	return MeshModel::MM_NONE;
//}

/**
 * @brief FilterSamplePlugin::postCondition
 * @return
 */
// int FilterMeshBooleans::postCondition(const QAction*) const
//{
//	return MeshModel::MM_VERTCOORD | MeshModel::MM_FACENORMAL | MeshModel::MM_VERTNORMAL;
//}

/**
 * @brief This function define the needed parameters for each filter. Return true if the filter has
 * some parameters it is called every time, so you can set the default value of parameters according
 * to the mesh For each parameter you need to define,
 * - the name of the parameter,
 * - the default value
 * - the string shown in the dialog
 * - a possibly long string describing the meaning of that parameter (shown as a popup help in the
 * dialog)
 * @param action
 * @param m
 * @param parlst
 */
RichParameterList
FilterMeshAlphaWrap::initParameterList(const QAction* action, const MeshDocument& md)
{
	RichParameterList parlst;
	switch (ID(action)) {
	case MESH_ALPHA_WRAP:
	{
		parlst.addParam(RichPercentage(
			"Alpha",
			md.mm()->cm.bbox.Diag()*0.02,
			0.0f,
			md.mm()->cm.bbox.Diag(),
			tr("Alpha"),
			tr("The radius of the rolling ball. Note that for smaller radius computation time increases.")));
		parlst.addParam(RichPercentage(
			"Offset",
			md.mm()->cm.bbox.Diag()*0.001,
			0.0f,
			md.mm()->cm.bbox.Diag(),			
			tr("Offset"),
			tr("The offset added to the surface. Note that this value should be larger than 0.0.")));
	} break;
	default: assert(0);
	}
	return parlst;
}



/**
 * @brief The Real Core Function doing the actual mesh processing.
 * @param action
 * @param md: an object containing all the meshes and rasters of MeshLab
 * @param par: the set of parameters of each filter
 * @param cb: callback object to tell MeshLab the percentage of execution of the filter
 * @return true if the filter has been applied correctly, false otherwise
 */
std::map<std::string, QVariant> FilterMeshAlphaWrap::applyFilter(
	const QAction*           action,
	const RichParameterList& par,
	MeshDocument&            md,
	unsigned int& /*postConditionMask*/,
	vcg::CallBackPos*)
{	
	switch (ID(action)) {
	case MESH_ALPHA_WRAP:
	{
		float alpha_fraction = par.getAbsPerc("Alpha");
		float offset_fraction = par.getAbsPerc("Offset");
		
		CgalMesh inputMesh;
		CgalMesh wrapResult;
		
			   // ------- initialize the CGAL input mesh based on the contents of the MeshDocument ----------
		CMeshO& m = md.mm()->cm;
		
		// note: coding guided from these pages:
		// http://vcglib.net/platonic_8h_source.html to read from VCGlib mesh
		// https://doc.cgal.org/latest/Surface_mesh/index.html#Chapter_3D_Surface_mesh to write to CGAL mesh
		
		for (auto vi = m.vert.begin(); vi != m.vert.end(); ++vi){
			vertex_descriptor vertex = inputMesh.add_vertex(K::Point_3((*vi).P().X(), (*vi).P().Y(), (*vi).P().Z()));
		}
		
		for (auto fi = m.face.begin(); fi != m.face.end(); ++fi)
		{
			vertex_descriptor u( (*fi).V(0)->Index() );
			vertex_descriptor v( (*fi).V(1)->Index() );
			vertex_descriptor w( (*fi).V(2)->Index() );
			face_descriptor face = inputMesh.add_face(u, v, w);
		}
		

		CGAL::alpha_wrap_3(inputMesh, alpha_fraction, offset_fraction, wrapResult);

		// making a nice label that has the used parameter doesn't work, since it will be interpreted
		// as a file name and the UI will manipulate the string based on where the dots are.
		// QString newName = QString::asprintf("Alpha Wrap a=%.4f, o=%.4f").arg(alpha_fraction, offset_fraction);
		QString newName = "Alpha wrap";
		MeshModel* result = md.addNewMesh("", newName, true);

		for(auto rvi = wrapResult.vertices().begin(); rvi != wrapResult.vertices().end(); ++rvi)
		{
			vcg::tri::Allocator<CMeshO>::AddVertex(result->cm,Point3m(wrapResult.point(*rvi).x(),wrapResult.point(*rvi).y(),wrapResult.point(*rvi).z()));
		}

		for(auto rfi = wrapResult.faces().begin(); rfi != wrapResult.faces().end(); ++rfi)
		{
			std::vector<int> faceVertices;
			// qDebug() << "vertices around face " << *rfi;
			CGAL::Vertex_around_face_iterator<CgalMesh> vbegin, vend;
			for(boost::tie(vbegin, vend) = vertices_around_face(wrapResult.halfedge( *rfi), wrapResult);   vbegin != vend;  ++vbegin)
			{
				faceVertices.push_back(vbegin->idx());
			}			
			vcg::tri::Allocator<CMeshO>::AddFace(result->cm,faceVertices[0],faceVertices[1],faceVertices[2]);
		}
		result->updateBoxAndNormals();
		}
	break;

	default: wrongActionCalled(action);
	}
	return std::map<std::string, QVariant>();
}


MESHLAB_PLUGIN_NAME_EXPORTER(FilterMeshAlphaWrap)
