//==============================================================================
/*! \file
 * OpenMesh Toolkit for mesh analysis    \n
 * Copyright (c) 2010 by Rostislav Hulik     \n
 *
 * Author:  Rostislav Hulik, rosta.hulik@gmail.com  \n
 * Date:    2010/10/25                          \n
 *
 * This file is part of software developed for support of Rostislav Hulik's dissertation thesis at dcgm-robotics@FIT group.
 *
 * This file is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This file 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this file.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Module description:
 * - Gets a mesh from input channel
 * - Proceedes with mesh decimation with metric given by input arguments
 * - Writes an output mesh into output channel
 */

#include "DecimateMesh.h"

#include <OMToolkit\IO\OMIO.h>
#include <OMToolkit\OMTypes.h>
#include <OpenMesh\Tools\Decimater\DecimaterT.hh>
#include <OpenMesh\Tools\Decimater\ModRoundnessT.hh>
#include <OpenMesh\Tools\Decimater\ModNormalFlippingT.hh>
#include <OpenMesh\Tools\Decimater\ModQuadricT.hh>
#include <OpenMesh\Tools\Decimater\ModIndependentSetsT.hh>

///////////////////////////////////////////////////////////////////////////////////////////////////
// Module constants
///////////////////////////////////////////////////////////////////////////////////////////////////

// Module description
const std::string MODULE_DESCRIPTION    = "Module that decimates mesh data";

// Additional command line arguments (output file)
const std::string MODULE_ARGUMENTS      = "method:minR:maxE:maxND:vertices:collapses:indS";

// Additional arguments (output file)
const std::string MODULE_ARG_METHOD		= "method";
const std::string MODULE_ARG_MINIMUM_R	= "minR";
const std::string MODULE_ARG_MAXIMUM_E	= "maxE";
const std::string MODULE_ARG_NORMAL_DEV	= "maxND";
const std::string MODULE_ARG_INDEPENDENT= "indS";
const std::string MODULE_ARG_VERTICES	= "vertices";
const std::string MODULE_ARG_COLLAPSES	= "collapses";

// Method possibilities
const std::string ROUDNESS			= "roundness";
const std::string QUADRIC			= "quadric";
const std::string METHOD_DEFAULT	= QUADRIC;

// Other defaults
const double METRIC_DEFAULT	= -1.0;
const double DEFAULT_PERCENT	= 0.5;
const int NUM_VERTICES_DEFAULT		= -1;
const int NUM_COLLAPSES_DEFAULT		= -1;
const bool INDEPENDENT_SETS			= false;

// mesh type
typedef OMToolkit::Types::ModuleMeshd			MeshT;

///////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////////////
OMDecimateMesh::OMDecimateMesh(const std::string& sDescription) : mds::mod::CModule(sDescription)
{
    allowArguments(MODULE_ARGUMENTS);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Destructor
///////////////////////////////////////////////////////////////////////////////////////////////////
OMDecimateMesh::~OMDecimateMesh()
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Do on startup
///////////////////////////////////////////////////////////////////////////////////////////////////
bool OMDecimateMesh::startup()
{
	// Disable OpenMesh logging
	omlog().disable();
	omerr().disable();
	omout().disable();
    
	// Note
    MDS_LOG_NOTE("Module startup");

    // Test of existence of input and output channel
    if( getNumOfInputs() != 1 || getNumOfOutputs() != 1 )
    {
        MDS_CERR('<' << m_sFilename << "> Wrong number of input and output channels" << std::endl);
        return false;
    }

	m_collapse_metrics = METHOD_DEFAULT;
	m_Arguments.value(MODULE_ARG_METHOD, m_collapse_metrics);
	
	// decision based on used metric - roundness collapse metric
	if (m_collapse_metrics == ROUDNESS)
	{
		m_minimum_roundness = METRIC_DEFAULT;
		m_Arguments.value(MODULE_ARG_MINIMUM_R, m_minimum_roundness);

		if ((m_minimum_roundness < 0.0 || m_minimum_roundness > 1.0) && m_minimum_roundness != METRIC_DEFAULT)
		{
			MDS_CERR('<' << m_sFilename << "> Wrong minimum roundness value, type -h for help." << std::endl);
			printUsage();
			return false;
		}
	}

	// decision based on used metric - quadric error collapse metric
	else if (m_collapse_metrics == QUADRIC)
	{
		m_max_error = METRIC_DEFAULT;
		m_Arguments.value(MODULE_ARG_MAXIMUM_E, m_max_error);
	}

	// error
	else
	{
		MDS_CERR('<' << m_sFilename << "> Wrong decimation method specified, type -h for help." << std::endl);
		printUsage();
        return false;
	}

	// should use independent sets?
	m_independent_sets = INDEPENDENT_SETS;
	if (m_Arguments.exists(MODULE_ARG_INDEPENDENT)) m_independent_sets = true;
	
	// is max_collapses specified?
	m_max_collapses = NUM_COLLAPSES_DEFAULT;
	m_Arguments.value(MODULE_ARG_COLLAPSES, m_max_collapses);

	// is max_vertices specified?
	m_max_vertices = NUM_VERTICES_DEFAULT;
	m_Arguments.value(MODULE_ARG_VERTICES, m_max_vertices);

	m_max_norm_dev = METRIC_DEFAULT;
	m_Arguments.value(MODULE_ARG_NORMAL_DEV, m_max_norm_dev);

    // O.K.
    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Main module loop
///////////////////////////////////////////////////////////////////////////////////////////////////
bool OMDecimateMesh::main()
{
    //// Note
    MDS_LOG_NOTE("Module main function");

    //// I/O channels
    mds::mod::CChannel *pIChannel = getInput(0);
    mds::mod::CChannel *pOChannel = getOutput(0);

	 // Is any input?
    if( !pIChannel->isConnected() )
    {
        return false;
    }

    // Wait for data
    if( pIChannel->wait(1000) )
    {
		// Mesh specification and read options
		MeshT mesh;
		OMToolkit::IO::Options opt = OMToolkit::IO::Options::Default;
	
		// Read and save mesh
		if (OMToolkit::IO::readMesh(mesh, *pIChannel, opt))
		{
			// if vertices and collapses are default, we compute number of vertices 
			if (m_max_vertices == NUM_VERTICES_DEFAULT && m_max_collapses == NUM_COLLAPSES_DEFAULT)
				m_max_vertices = mesh.n_vertices() * DEFAULT_PERCENT;

			OpenMesh::Decimater::DecimaterT<MeshT> decimater(mesh);

			// handles to modules
			OpenMesh::Decimater::ModRoundnessT<OpenMesh::Decimater::DecimaterT<MeshT>>::Handle roundness;
			OpenMesh::Decimater::ModQuadricT<OpenMesh::Decimater::DecimaterT<MeshT>>::Handle quadric;
			OpenMesh::Decimater::ModNormalFlippingT<OpenMesh::Decimater::DecimaterT<MeshT>>::Handle normal;
			OpenMesh::Decimater::ModIndependentSetsT<OpenMesh::Decimater::DecimaterT<MeshT>>::Handle independent;

			// add roundness metric
			if (m_collapse_metrics == ROUDNESS)
			{
				decimater.add(roundness);
				if (m_minimum_roundness != METRIC_DEFAULT)
					decimater.module(roundness).set_min_roundness(m_minimum_roundness);
			}

			// add quadric error metric
			else if (m_collapse_metrics == QUADRIC)
			{
				decimater.add(quadric);
				if (m_max_error != METRIC_DEFAULT)
					decimater.module(quadric).set_max_err(m_max_error);
			}

			// maximum normal deviation
			if (m_max_norm_dev != METRIC_DEFAULT)
			{
				decimater.add(normal);
				decimater.module(normal).set_max_normal_deviation(m_max_norm_dev);
			}
			
			// independent sets?
			if (m_independent_sets)
				decimater.add(independent);

			MDS_LOG_NOTE("Mesh vertices number before decimation: " << mesh.n_vertices());

			decimater.initialize();
			// do decimation
			if (m_max_collapses != NUM_COLLAPSES_DEFAULT)
					decimater.decimate(m_max_collapses);
				else
					decimater.decimate_to(m_max_vertices);

			// take out the trash
			mesh.garbage_collection();

			MDS_LOG_NOTE("Mesh vertices number after decimation: " << mesh.n_vertices());

			if (!OMToolkit::IO::writeMesh(mesh, *pOChannel))
			{
				MDS_CERR('<' << m_sFilename << "> Failed to write output mesh data" << std::endl);
				return false;
			}
		}
		// Error on input
		else 
		{
			MDS_CERR('<' << m_sFilename << "> Failed to read input mesh data" << std::endl);
			return false;
		}

		return false;
	}
    else
    {
        MDS_LOG_NOTE("Wait timeout");
    }

    // Returning 'true' means to continue processing the input channel
    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// On module shutdown
///////////////////////////////////////////////////////////////////////////////////////////////////
void OMDecimateMesh::shutdown()
{
    // Note
    MDS_LOG_NOTE("Module shutdown");
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Writes extended use of this module
///////////////////////////////////////////////////////////////////////////////////////////////////
void OMDecimateMesh::writeExtendedUsage(std::ostream& Stream)
{
    MDS_CERR("Arguments possibilities:" << std::endl);
	MDS_CERR("	-method quadric [-maxE maximumError] [-maxND maximumNormalDeviation] [-indS]" << std::endl);
	MDS_CERR("	-method roundness [-minR minimumRoundness] [-maxND maximumNormalDeviation] [-indS]" << std::endl);
    MDS_CERR("Options:" << std::endl);
	MDS_CERR("  -method Specifies error metric." << std::endl);
	MDS_CERR("	  -quadric: It is for quadric error collapse metric." << std::endl);
	MDS_CERR("	  -roundness: It is for maximum roundness metric." << std::endl);
	MDS_CERR("  -maxE specifies maximum error (quadric only)." << std::endl);
	MDS_CERR("  -minR specifies minimum roundness [0..1] (roundness only)." << std::endl);
	MDS_CERR("  -maxND adds maximum normal deviation (before collapse and after) in degrees." << std::endl);
	MDS_CERR("  -indS specifies computing with independent sets (all neighbours of collapsed edge are locked)" << std::endl);
    MDS_CERR(std::endl);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Main - executing a module
///////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[])
{
    // Creation of a module using smart pointer
    OMDecimateMeshPtr spModule(new OMDecimateMesh(MODULE_DESCRIPTION));

    // Initialize and execute the module
    if( spModule->init(argc, argv) )
    {
        spModule->run();
    }

    // Console application finished
    return 0;
}

