#include "Smooth.h"

#include <smoothers/CBMDSSmoother.h>
#include <smoothers/CEigenSmoother.h>
#include <smoothers/CHCSmoother.h>
#include <smoothers/CTaubinSmoother.h>
#include <smoothers/CLaplacianSmoother.h>

#include <features/CVertexInfoGarland.h>

//==============================================================================
/*
 * Global constants.
 */

//! Module description
const std::string MODULE_DESCRIPTION        = "Mesh smoothing module";

//! Additional command line arguments
const std::string MODULE_ARGUMENTS          = "method:p1:p2:iterations:input:output";

//! Additional arguments
const std::string MODULE_ARGUMENT_INPUT		 = "input";
const std::string MODULE_ARGUMENT_OUTPUT	 = "output";
const std::string MODULE_ARGUMENT_METHOD     = "method";
const std::string MODULE_ARGUMENT_P1		 = "p1";
const std::string MODULE_ARGUMENT_P2		 = "p2";
const std::string MODULE_ARGUMENT_ITERATIONS = "iterations";

//! Method names
const std::string METHOD_EIGENSMOOTH		 = "eigensmooth";
const std::string METHOD_LAPLACIAN			 = "laplacian";
const std::string METHOD_BMDS				 = "bmds";
const std::string METHOD_HC					 = "hc";
const std::string METHOD_TAUBIN				 = "taubin";

//! Default method
const std::string DEFAULT_METHOD			 = METHOD_EIGENSMOOTH;

//! Default parameters p1 and p2 according to selected method
const double DEFAULT_EIGENSMOOTH_P1			 = 0.2;
const double DEFAULT_EIGENSMOOTH_P2			 = 0.0;

const double DEFAULT_LAPLACIAN_P1			 = 0.2;
const double DEFAULT_LAPLACIAN_P2			 = 0.0;

const double DEFAULT_BMDS_P1				 = 0.5;
const double DEFAULT_BMDS_P2				 = 0.25;

const double DEFAULT_HC_P1					 = 0.2;
const double DEFAULT_HC_P2					 = 0.2;

const double DEFAULT_TAUBIN_P1				 = 0.5;
const double DEFAULT_TAUBIN_P2				 = 1.0;

//! Default numbers of iterations according to seleced methods
const int	 DEFAULT_EIGENSMOOTH_ITERATIONS	= 4;
const int	 DEFAULT_LAPLACIAN_ITERATIONS	= 10;
const int	 DEFAULT_BMDS_ITERATIONS		= 1;
const int	 DEFAULT_HC_ITERATIONS			= 5;
const int	 DEFAULT_TAUBIN_ITERATIONS		= 3;


//==============================================================================
/*
 * Global variables.
 */


//==============================================================================
/*
 * Implementation of the class CSmooth.
 */
CSmooth::CSmooth(const std::string& sDescription)
    : mds::mod::CModule(sDescription)
{
    allowArguments(MODULE_ARGUMENTS);
}


CSmooth::~CSmooth()
{
}


bool CSmooth::startup()
{
    // Note
    MDS_LOG_NOTE("Module startup");

	m_sInputFile	=	"";
	m_Arguments.value( MODULE_ARGUMENT_INPUT, m_sInputFile );

	// Test if input filename is specified
	if ( m_sInputFile == "" )
	{
        MDS_CERR('<' << m_sFilename << "> Input file not specified: type -h for help" << std::endl);
        printUsage();
        return false;
	}

	m_sOutputFile   =   "";
	m_Arguments.value( MODULE_ARGUMENT_OUTPUT, m_sOutputFile );

	// Test if output filename is specified
	if ( m_sOutputFile == "" )
	{
        MDS_CERR('<' << m_sFilename << "> Output file not specified: type -h for help" << std::endl);
        printUsage();
        return false;
	}

	// Test method parameter
	m_sMethod = DEFAULT_METHOD;
	m_Arguments.value( MODULE_ARGUMENT_METHOD, m_sMethod );

	// Set method specific defaults
	if		( m_sMethod	==	METHOD_EIGENSMOOTH )
	{
		m_dP1 = DEFAULT_EIGENSMOOTH_P1;
		m_dP2 = DEFAULT_EIGENSMOOTH_P2;
		m_iIterations = DEFAULT_EIGENSMOOTH_ITERATIONS;
	}
	else if ( m_sMethod == METHOD_LAPLACIAN )
	{
		m_dP1 = DEFAULT_LAPLACIAN_P1;
		m_dP2 = DEFAULT_LAPLACIAN_P2;
		m_iIterations = DEFAULT_LAPLACIAN_ITERATIONS;
	}
	else if ( m_sMethod == METHOD_BMDS )
	{
		m_dP1 = DEFAULT_BMDS_P1;
		m_dP2 = DEFAULT_BMDS_P2;
		m_iIterations = DEFAULT_BMDS_ITERATIONS;
	}
	else if ( m_sMethod	== METHOD_HC )
	{
		m_dP1 = DEFAULT_HC_P1;
		m_dP2 = DEFAULT_HC_P2;
		m_iIterations = DEFAULT_HC_ITERATIONS;
	}
	else if ( m_sMethod == METHOD_TAUBIN )
	{
		m_dP1 = DEFAULT_TAUBIN_P1;
		m_dP2 = DEFAULT_TAUBIN_P2;
		m_iIterations = DEFAULT_TAUBIN_ITERATIONS;
	}
	else
	{
        MDS_CERR('<' << m_sFilename << "> Wrong method specified: type -h for help" << std::endl);
        printUsage();
        return false;
	}

	m_Arguments.value( MODULE_ARGUMENT_P1, m_dP1 );
	m_Arguments.value( MODULE_ARGUMENT_P2, m_dP2 );
	m_Arguments.value( MODULE_ARGUMENT_ITERATIONS, m_iIterations );

    // O.K.
    return true;
}


bool CSmooth::main()
{
    // Note
    MDS_LOG_NOTE("Module main function");

    // Create a new slice
	CSmoothingMesh::tSmartPtr	sp_Mesh	=	new CSmoothingMesh();

	// Load 
	if ( !sp_Mesh->loadFile( m_sInputFile ) )
	{
        MDS_CERR('<' << m_sFilename << "> Unable to load input file" << std::endl);
        return false;	
	}

	// Prepare mesh features 
	if ( m_sMethod == METHOD_EIGENSMOOTH )
	{
		CVertexInfoGeneratorGarland::tSmartPtr	sp_Feature = 0;
		sp_Feature = new CVertexInfoGeneratorGarland( 
			CVertexInfoGeneratorGarland::TYPE_GARLAND, 
			CVertexInfoGeneratorGarland::FROM_TRIANGLES, 
			3 );

		if ( !sp_Feature->generate( sp_Mesh.get() ) )
		{
	        MDS_CERR('<' << m_sFilename << "> Unable create eigensmooth features" << std::endl);
		    return false;	
		}
	}

	// Create smoother
	CSmoother::tSmartPtr		sp_Smoother = 0;

	// Choose smoothing method
	if ( m_sMethod == METHOD_EIGENSMOOTH )
	{
		// eigensmoother
		sp_Smoother	= new CEigensmoother( m_dP1, true, true, m_iIterations, 1.5 );
	}
	else if	( m_sMethod == METHOD_LAPLACIAN )
	{
		// laplacian smoother
		sp_Smoother = new CLaplacianSmoother( m_dP1, m_iIterations );
	}
	else if ( m_sMethod == METHOD_BMDS )
	{
		// bmds smoother
		sp_Smoother = new CBMDSSmoother( m_dP1, m_dP2, m_iIterations );
	}
	else if ( m_sMethod == METHOD_HC )
	{
		// hc smoother
		sp_Smoother = new CHCSmoother( m_dP1, m_dP2, m_iIterations );
	}
	else if ( m_sMethod == METHOD_TAUBIN )
	{
		// taubin smoother
		sp_Smoother = new CTaubinSmoother( m_dP1, m_dP2, m_iIterations );
	}

	// Apply smoother to the mesh
	sp_Smoother->smooth( sp_Mesh.get() );

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


void CSmooth::shutdown()
{
    // Note
    MDS_LOG_NOTE("Module shutdown");
}


void CSmooth::writeExtendedUsage(std::ostream& Stream)
{
    MDS_CERR(std::endl);
    MDS_CERR("Extended usage:" << std::endl);
    MDS_CERR("Options:" << std::endl);
	MDS_CERR("  -method      Smoothing method. Possible values :" << std::endl);
    MDS_CERR("               " << METHOD_EIGENSMOOTH << std::endl);
	MDS_CERR("               " << METHOD_LAPLACIAN << std::endl);
	MDS_CERR("               " << METHOD_BMDS << std::endl);
	MDS_CERR("               " << METHOD_HC << std::endl);
	MDS_CERR("               " << METHOD_TAUBIN << std::endl);

    MDS_CERR(std::endl);
    MDS_CERR("  -input       Input .stl file name." << std::endl);

    MDS_CERR(std::endl);
    MDS_CERR("  -output      Output .stl file name." << std::endl);

    MDS_CERR(std::endl);
    MDS_CERR("  -p1          First smoothing parameter." << std::endl);

    MDS_CERR(std::endl);
    MDS_CERR("  -p2          Second smoothing parameter." << std::endl);

    MDS_CERR(std::endl);
    MDS_CERR("  -iterations  Number of iterations of basic algorithm." << std::endl);

}


//==============================================================================
/*
 * Function main() which creates and executes console application.
 */
int main(int argc, char *argv[])
{
    // Creation of a module using smart pointer
    CSmoothPtr spModule(new CSmooth(MODULE_DESCRIPTION));

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

    // Console application finished
    return 0;
}

