/****************************************************************************
* MeshLab                                                           o o     *
* A versatile mesh processing toolbox                             o     o   *
*                                                                _   O  _   *
* Copyright(C) 2005                                                \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* 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 <math.h>
#include <stdlib.h>
#include <time.h>

#include "filter_trioptimize.h"
#include "curvedgeflip.h"

#include <vcg/complex/algorithms/clean.h>
#include <vcg/complex/algorithms/smooth.h>

using namespace vcg;

// forward declarations
class NSMCEFlip;
class MeanCEFlip;
class AbsCEFlip;

class NSMCEFlip : public vcg::tri::CurvEdgeFlip<CMeshO, NSMCEFlip, NSMCEval >
{
public:
  NSMCEFlip(PosType pos, int mark,BaseParameterClass *_pp) :
    vcg::tri::CurvEdgeFlip<CMeshO, NSMCEFlip, NSMCEval >(pos, mark,_pp) {}
};

class MeanCEFlip : public vcg::tri::CurvEdgeFlip<CMeshO, MeanCEFlip, MeanCEval >
{
public:
  MeanCEFlip(PosType pos, int mark,BaseParameterClass *_pp) :
    vcg::tri::CurvEdgeFlip<CMeshO, MeanCEFlip, MeanCEval >(pos, mark,_pp) {}
};

class AbsCEFlip : public vcg::tri::CurvEdgeFlip<CMeshO, AbsCEFlip, AbsCEval >
{
public:
  AbsCEFlip(PosType pos, int mark,BaseParameterClass *_pp) :
    vcg::tri::CurvEdgeFlip<CMeshO, AbsCEFlip, AbsCEval >(pos, mark, _pp) {}
};

// forward declarations
class MyTriEFlip;
class MyTopoEFlip;
class QEFlip;
class QRadiiEFlip;
class QMeanRatioEFlip;

class MyTriEFlip : public vcg::tri::TriEdgeFlip<CMeshO, MyTriEFlip>
{
public:
  MyTriEFlip(PosType pos, int mark,BaseParameterClass *pp) :
    vcg::tri::TriEdgeFlip<CMeshO, MyTriEFlip>(pos, mark,pp) {}
};

class MyTopoEFlip : public vcg::tri::TopoEdgeFlip<CMeshO, MyTopoEFlip>
{
public:
  MyTopoEFlip(PosType pos, int mark, BaseParameterClass *pp) :
    vcg::tri::TopoEdgeFlip<CMeshO, MyTopoEFlip>(pos, mark,pp) {}
};

class QEFlip : public vcg::tri::PlanarEdgeFlip<CMeshO, QEFlip>
{
public:
  QEFlip(PosType pos, int mark,BaseParameterClass *pp) :
    vcg::tri::PlanarEdgeFlip<CMeshO, QEFlip>(pos, mark,pp) {}
};

class QRadiiEFlip :
public vcg::tri::PlanarEdgeFlip<CMeshO, QRadiiEFlip, QualityRadii>
{
public:
  QRadiiEFlip(PosType pos, int mark,BaseParameterClass *pp) :
    vcg::tri::PlanarEdgeFlip<CMeshO, QRadiiEFlip, QualityRadii>(pos, mark,pp) {}
};

class QMeanRatioEFlip :
public vcg::tri::PlanarEdgeFlip<CMeshO, QMeanRatioEFlip, QualityMeanRatio>
{
public:
  QMeanRatioEFlip(PosType pos, int mark,BaseParameterClass *pp) :
    vcg::tri::PlanarEdgeFlip<CMeshO, QMeanRatioEFlip, QualityMeanRatio>(pos, mark,pp) {}
};


// 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
TriOptimizePlugin::TriOptimizePlugin()
{
	typeList = {
		FP_PLANAR_EDGE_FLIP,
		FP_CURVATURE_EDGE_FLIP,
		FP_NEAR_LAPLACIAN_SMOOTH
	};

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

QString TriOptimizePlugin::pluginName() const
{
	return "FilterTriOptimize";
}

QString TriOptimizePlugin::pythonFilterName(ActionIDType f) const
{
	switch (f) {
	case FP_PLANAR_EDGE_FLIP: return tr("meshing_edge_flip_by_planar_optimization");
	case FP_CURVATURE_EDGE_FLIP: return tr("meshing_edge_flip_by_curvature_optimization");
	case FP_NEAR_LAPLACIAN_SMOOTH: return tr("apply_coord_laplacian_smoothing_surface_preserving");
	default: assert(0); return QString();
	}
}

QString TriOptimizePlugin::filterName(ActionIDType filterId) const
{
	switch (filterId) {
	case FP_PLANAR_EDGE_FLIP: return tr("Planar flipping optimization");
	case FP_CURVATURE_EDGE_FLIP: return tr("Curvature flipping optimization");
	case FP_NEAR_LAPLACIAN_SMOOTH: return tr("Laplacian Smooth (surface preserving)");
	default: assert(0); return QString();
	}
}

int TriOptimizePlugin::getRequirements(const QAction *action)
{
	switch (ID(action)) {
		case FP_PLANAR_EDGE_FLIP:
		case FP_CURVATURE_EDGE_FLIP:
			return MeshModel::MM_FACEFACETOPO |
				   MeshModel::MM_VERTFACETOPO |
				   MeshModel::MM_VERTMARK;
	}

	return 0;
}

QString TriOptimizePlugin::filterInfo(ActionIDType filterId) const
{
	switch(filterId) {
		case FP_PLANAR_EDGE_FLIP:
			return tr("Mesh optimization by edge flipping, to improve local triangle quality");
		case FP_CURVATURE_EDGE_FLIP:
			return tr("Mesh optimization by edge flipping, to improve local "
								"mesh curvature");
		case FP_NEAR_LAPLACIAN_SMOOTH:
			return tr("Laplacian smooth with limited surface modification: move "
					   "each vertex in the average position of neighbors "
					   "vertices, only if the new position still (almost) lies "
					   "on original surface");
		default : assert(0);
	}
	return {};
}

 TriOptimizePlugin::FilterClass TriOptimizePlugin::getClass(const QAction *action) const
{
	switch(ID(action)) {
		case FP_PLANAR_EDGE_FLIP:             return FilterPlugin::Remeshing;
		case FP_CURVATURE_EDGE_FLIP:             return FilterPlugin::Remeshing;
		case FP_NEAR_LAPLACIAN_SMOOTH: return FilterPlugin::Smoothing;
	}
 return FilterPlugin::Generic;
}

int TriOptimizePlugin::postCondition(const QAction *a) const
{
	switch(ID(a))
	{
		case FP_PLANAR_EDGE_FLIP      :
		case FP_CURVATURE_EDGE_FLIP   : return MeshModel::MM_ALL;
		case FP_NEAR_LAPLACIAN_SMOOTH : return MeshModel::MM_VERTCOORD | MeshModel::MM_VERTNORMAL;
		default                       : assert(0);
	}
	return {};
}

// 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 string shown in the dialog
// - the default value
// - a possibly long string describing the meaning of that parameter (shown as a popup help in the dialog)
RichParameterList TriOptimizePlugin::initParameterList(const QAction *action, const MeshModel &m)
{
	RichParameterList parlst;
	if (ID(action) == FP_CURVATURE_EDGE_FLIP) {
		parlst.addParam(RichBool("selection", m.cm.sfn > 0, tr("Update selection"), tr("Apply edge flip optimization on selected faces only")));
		parlst.addParam(RichFloat("pthreshold", 1.0f,
											tr("Angle Thr (deg)"),
											tr("To avoid excessive flipping/swapping we consider only couple of faces with a significant diedral angle (e.g. greater than the indicated threshold). ")));

		QStringList cmetrics;
		cmetrics.push_back("mean");
		cmetrics.push_back("norm squared");
		cmetrics.push_back("absolute");

		parlst.addParam(RichEnum("curvtype", 0, cmetrics, tr("Curvature metric"),
				tr("<p style=\'white-space:pre\'>"
					"Choose a metric to compute surface curvature on vertices<br>"
					"H = mean curv, K = gaussian curv, A = area per vertex<br><br>"
					"1: Mean curvature = H<br>"
					"2: Norm squared mean curvature = (H * H) / A<br>"
					"3: Absolute curvature:<br>"
					"     if(K >= 0) return 2 * H<br>"
					"     else return 2 * sqrt(H ^ 2 - A * K)")));
		}

		if (ID(action) == FP_PLANAR_EDGE_FLIP) {
			parlst.addParam(RichBool("selection", m.cm.sfn > 0, tr("Update selection"), tr("Apply edge flip optimization on selected faces only")));

		parlst.addParam(RichFloat("pthreshold", 1.0f,
											tr("Planar threshold (deg)"),
											tr("angle threshold for planar faces (degrees)")));

		QStringList pmetrics;
		pmetrics.push_back("area/max side");
		pmetrics.push_back("inradius/circumradius");
		pmetrics.push_back("mean ratio");
		pmetrics.push_back("delaunay");
		pmetrics.push_back("topology");
		parlst.addParam(RichEnum("planartype", 0, pmetrics, tr("Planar metric"),
				tr("<p style=\'white-space:pre\'>"
					"Choose a metric to define the planar flip operation<br><br>"
					"Triangle quality based<br>"
					"1: minimum ratio height/edge among the edges<br>"
					"2: ratio between radii of incenter and circumcenter<br>"
					"3: 2*sqrt(a, b)/(a+b), a, b the eigenvalues of M^tM,<br>"
					"     M transform triangle into equilateral<br><br>"
					"Others<br>"
					"4: Fix the Delaunay condition between two faces<br>"
					"5: Do the flip to improve local topology<br>")));
		parlst.addParam(RichInt("iterations", 1, "Post optimization relax iter", tr("number of a planar laplacian smooth iterations that have to be performed after every run")));

	}

	if (ID(action) == FP_NEAR_LAPLACIAN_SMOOTH) {
		parlst.addParam(RichBool("selection", false, tr("Update selection"),	tr("Apply laplacian smooth on selected faces only")));
		parlst.addParam(RichFloat("AngleDeg", 0.5f,	tr("Max Normal Dev (deg)"),	tr("maximum mean normal angle displacement (degrees) from old to new faces")));
		parlst.addParam(RichInt("iterations", 1, "Iterations", tr("number of laplacian smooth iterations in every run")));
	}
	return parlst;
}

// The Real Core Function doing the actual mesh processing.
// Run mesh optimization
std::map<std::string, QVariant> TriOptimizePlugin::applyFilter(
		const QAction *filter,
		const RichParameterList & par,
		MeshDocument &md,
		unsigned int& /*postConditionMask*/,
		vcg::CallBackPos *cb)
{
	MeshModel &m=*(md.mm());
	float limit = -std::numeric_limits<float>::epsilon();

	if (ID(filter) == FP_CURVATURE_EDGE_FLIP) {
		int delvert = tri::Clean<CMeshO>::RemoveUnreferencedVertex(m.cm);
		if (delvert)
			log(
						"Pre-Curvature Cleaning: Removed %d unreferenced vertices",
						delvert);

		tri::Allocator<CMeshO>::CompactVertexVector(m.cm);
		tri::Allocator<CMeshO>::CompactFaceVector(m.cm);
		m.updateDataMask(MeshModel::MM_FACEFACETOPO);
		vcg::tri::UpdateFlags<CMeshO>::FaceBorderFromFF(m.cm);

		if ( tri::Clean<CMeshO>::CountNonManifoldEdgeFF(m.cm) >0) {
			throw MLException("Mesh has some not 2-manifold faces, edge flips requires manifoldness");
		}
		vcg::tri::PlanarEdgeFlipParameter pp;

		vcg::LocalOptimization<CMeshO> optimiz(m.cm,&pp);
		float pthr = par.getFloat("pthreshold");
		time_t start = clock();

		if (par.getBool("selection")) {
			// Mark not writable un-selected faces
			for (CMeshO::FaceIterator fi = m.cm.face.begin(); fi != m.cm.face.end(); ++fi) {
				if (!(*fi).IsS()) (*fi).ClearW();
				else (*fi).SetW();
			}

			// select vertices with at least one incident face selected
			tri::UpdateSelection<CMeshO>::VertexFromFaceLoose(m.cm);

			// Mark not writable un-selected vertices
			for (CMeshO::VertexIterator vi = m.cm.vert.begin(); vi != m.cm.vert.end(); ++vi){
				if (!(*vi).IsS()) (*vi).ClearW();
				else (*vi).SetW();
			}
		}

		// VF adjacency needed for edge flips based on vertex curvature
		vcg::tri::UpdateTopology<CMeshO>::VertexFace(m.cm);
		vcg::tri::UpdateTopology<CMeshO>::TestVertexFace(m.cm);

		int metric = par.getEnum("curvtype");
		pp.CoplanarAngleThresholdDeg = pthr;
		switch (metric) {
		case 0: optimiz.Init<MeanCEFlip>(); break;
		case 1: optimiz.Init<NSMCEFlip>();  break;
		case 2: optimiz.Init<AbsCEFlip>();  break;
		}

		// stop when flips become harmful
		optimiz.SetTargetMetric(limit);
		//optimiz.SetTargetOperations(10);
		optimiz.DoOptimization();
		optimiz.h.clear();

		log( "%d curvature edge flips performed in %.2f sec.",  optimiz.nPerformedOps, (clock() - start) / (float) CLOCKS_PER_SEC);
	}
	else if (ID(filter) == FP_PLANAR_EDGE_FLIP) {
		if ( tri::Clean<CMeshO>::CountNonManifoldEdgeFF(m.cm) >0) {
			throw MLException("Mesh has some not 2-manifold faces, edge flips requires manifoldness");
		}

		bool selection = par.getBool("selection");

		tri::Allocator<CMeshO>::CompactVertexVector(m.cm);
		tri::Allocator<CMeshO>::CompactFaceVector(m.cm);
		vcg::tri::UpdateTopology<CMeshO>::FaceFace(m.cm);
		vcg::tri::UpdateFlags<CMeshO>::FaceBorderFromFF(m.cm);
		vcg::tri::PlanarEdgeFlipParameter pp;
		vcg::LocalOptimization<CMeshO> optimiz(m.cm,&pp);

		float planarThrDeg = par.getFloat("pthreshold");
		pp.CoplanarAngleThresholdDeg=planarThrDeg;
		time_t	start = clock();


		int metric = par.getEnum("planartype");
		switch (metric) {
		case 0: optimiz.Init<QEFlip>(); break;
		case 1: optimiz.Init<QRadiiEFlip>(); break;
		case 2: optimiz.Init<QMeanRatioEFlip>(); break;
		case 3: optimiz.Init<MyTriEFlip>(); break;
		case 4: optimiz.Init<MyTopoEFlip>(); 	break;
		}
		// stop when flips become harmful
		optimiz.SetTargetMetric(limit);
		optimiz.DoOptimization();
		optimiz.h.clear();

		log( "%d planar edge flips performed in %.2f sec.", optimiz.nPerformedOps, (clock() - start) / (float) CLOCKS_PER_SEC);
		int iternum = par.getInt("iterations");

		tri::Smooth<CMeshO>::VertexCoordPlanarLaplacian(m.cm, iternum, math::ToRad(planarThrDeg), selection,cb);


		vcg::tri::UpdateNormal<CMeshO>::PerVertexNormalizedPerFace(m.cm);

		if (par.getBool("selection")) {
			// Clear Writable flags (faces)
			CMeshO::FaceIterator fi;
			for (fi = m.cm.face.begin(); fi != m.cm.face.end(); ++fi)
				if (!(*fi).IsD())
					(*fi).SetW();

			// Clear Writable flags (vertices)
			CMeshO::VertexIterator vi;
			for (vi = m.cm.vert.begin(); vi != m.cm.vert.end(); ++vi)
				if (!(*vi).IsD())
					(*vi).SetW();

			// restore "default" selection  for vertices
			vcg::tri::UpdateSelection<CMeshO>::VertexFromFaceStrict(m.cm);
		}
	}

	else if (ID(filter) == FP_NEAR_LAPLACIAN_SMOOTH) {
		bool selection = par.getBool("selection");
		if (selection)
			vcg::tri::UpdateSelection<CMeshO>::VertexFromFaceStrict(m.cm);

		int iternum = par.getInt("iterations");
		Scalarm dthreshold = par.getFloat("AngleDeg");
		tri::Smooth<CMeshO>::VertexCoordPlanarLaplacian(m.cm, iternum, math::ToRad(dthreshold), selection,cb);
		tri::UpdateNormal<CMeshO>::PerVertexNormalizedPerFace(m.cm);
	}
	else {
		wrongActionCalled(filter);
	}

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


MESHLAB_PLUGIN_NAME_EXPORTER(TriOptimizePlugin)
