//==============================================================================
/*! \file
 * OpenMesh Toolkit for mesh analysis    \n
 * Copyright (c) 2010 by Rostislav Hulik     \n
 *
 * Author:  Rostislav Hulik, rosta.hulik@gmail.com  \n
 * Date:    2010/11/21                          \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:
 * - Module rasterizes vertex neighbourhood on a tangent raster
 * - Result is sent to connected MDSTk channel
 */

#include "FilterMesh.h"

#include <OMToolkit\IO\OMIO.h>
#include <OpenMesh\Core\IO\exporter\ExporterT.hh>
#include <OMToolkit\OMTypes.h>
#include <Eigen\Geometry>
#include <Eigen\LU>
#include <Eigen\Array>
#include <OMToolkit\OMProjector.h>
#include <OpenMesh\Tools\Utils\Timer.hh>
#include <OMToolkit\OMTriIterator.h>
#include <OMToolkit\OMFilter.h>
///////////////////////////////////////////////////////////////////////////////////////////////////
// Module constants
///////////////////////////////////////////////////////////////////////////////////////////////////

// Module description
const std::string MODULE_DESCRIPTION    = "Module that rasterizes vertex neighbourhood on a tangent raster";

// Additional command line arguments
const std::string MODULE_ARGUMENTS      = "filter:weight:min:max:mean:median:inputF:inputW:distance:variance";

// Additional arguments
const std::string MODULE_ARG_FILEF		= "inputF";
const std::string MODULE_ARG_FILEW		= "inputW";

const std::string MODULE_ARG_FILTER		= "filter";
const std::string MODULE_ARG_WEIGHT		= "weight";
const std::string MODULE_ARG_MIN		= "min";
const std::string MODULE_ARG_MAX		= "max";
const std::string MODULE_ARG_MEAN		= "mean";
const std::string MODULE_ARG_MEDIAN		= "median";
const std::string MODULE_ARG_DIST		= "distance";
const std::string MODULE_ARG_VARIANCE	= "variance";

const int FLAG_FILTER	= 2;
const int FLAG_MIN		= 4;
const int FLAG_MAX		= 8;
const int FLAG_MEAN		= 16;
const int FLAG_MEDIAN	= 32;
const int FLAG_DIST		= 64;
const int FLAG_VARIANCE	= 128;
const int FLAG_WEIGHT	= 256;

const std::string MODULE_FILE_DEFAULT	= "";

// Type of accepted mesh
typedef OMToolkit::Types::ModuleMeshd	MeshT;

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

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

///////////////////////////////////////////////////////////////////////////////////////////////////
// Do on startup
///////////////////////////////////////////////////////////////////////////////////////////////////
bool OMFilter::startup()
{
	// Disable all OpenMesh errorlogs (for not mix MDSTk log)
	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_filters = 0;
	if (m_Arguments.exists(MODULE_ARG_FILTER))
	{
		m_filters |= FLAG_FILTER;
		m_filenameF = MODULE_FILE_DEFAULT;
		m_Arguments.value(MODULE_ARG_FILEF, m_filenameF);
		if (m_filenameF == MODULE_FILE_DEFAULT)
		{
			MDS_CERR('<' << m_sFilename << "> If matrix filter is selected, input file must be specified." << std::endl);
			return false;
		}
	}

	if (m_Arguments.exists(MODULE_ARG_WEIGHT))
	{
		m_filters |= FLAG_WEIGHT;
		m_filenameW = MODULE_FILE_DEFAULT;
		m_Arguments.value(MODULE_ARG_FILEW, m_filenameW);
		if (m_filenameW == MODULE_FILE_DEFAULT)
		{
			MDS_CERR('<' << m_sFilename << "> If weighting is selected, input file must be specified." << std::endl);
			return false;
		}
	}

	if (m_Arguments.exists(MODULE_ARG_MIN))
		m_filters |= FLAG_MIN;

	if (m_Arguments.exists(MODULE_ARG_MAX))
		m_filters |= FLAG_MAX;

	if (m_Arguments.exists(MODULE_ARG_MEAN))
		m_filters |= FLAG_MEAN;

	if (m_Arguments.exists(MODULE_ARG_MEDIAN))
		m_filters |= FLAG_MEDIAN;

	if (m_Arguments.exists(MODULE_ARG_DIST))
		m_filters |= FLAG_DIST;

	if (m_Arguments.exists(MODULE_ARG_VARIANCE))
		m_filters |= FLAG_VARIANCE;

    // O.K.
    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Main module loop
///////////////////////////////////////////////////////////////////////////////////////////////////
bool OMFilter::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
		OMToolkit::OMFilter<MeshT, MeshT::VertexMatrix> filter(&mesh);
		if (OMToolkit::IO::readMesh(mesh, *pIChannel, opt))
		{
			mesh.request_face_normals();
			mesh.request_vertex_normals();
			mesh.update_normals();

			if (m_filters & FLAG_WEIGHT)
			{
				if (!filter.LoadFile(m_filenameW))
				{
					MDS_CERR('<' << m_sFilename << "> Error in matrix file." << std::endl);
					return false;
				}
				if (!filter.WeightAll(mesh.getMatrixHandle()))
				{
					MDS_CERR('<' << m_sFilename << "> Weighting error - have the mesh the same matrix size as a weighting filter?" << std::endl);
					return false;
				}
			}

			if (m_filters & FLAG_FILTER)
			{
				if (!filter.LoadFile(m_filenameF))
				{
					MDS_CERR('<' << m_sFilename << "> Error in matrix file." << std::endl);
					return false;
				}
				if (!filter.FilterAll(mesh.getMatrixHandle()))
				{
					MDS_CERR('<' << m_sFilename << "> Filtering error - have the mesh the same matrix size as a filter?" << std::endl);
					return false;
				}
			}

			if (m_filters & FLAG_MIN)
			{
				filter.MinAll(mesh.getMatrixHandle());
			}

			if (m_filters & FLAG_MAX)
			{
				filter.MaxAll(mesh.getMatrixHandle());
			}

			if (m_filters & FLAG_MEAN)
			{
				filter.MeanAll(mesh.getMatrixHandle());
			}

			if (m_filters & FLAG_MEDIAN)
			{
				filter.MedianAll(mesh.getMatrixHandle());
			}

			if (m_filters & FLAG_DIST)
			{
				filter.AbsAll(mesh.getMatrixHandle());
			}

			if (m_filters & FLAG_VARIANCE)
			{
				filter.VarianceAll(mesh.getMatrixHandle());
			}

			// write output
			if (!OMToolkit::IO::writeMesh(mesh, *pOChannel))
			{
				MDS_CERR('<' << m_sFilename << "> Failed to write output 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 false;	
}

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

///////////////////////////////////////////////////////////////////////////////////////////////////
// Writes extended use of this module
///////////////////////////////////////////////////////////////////////////////////////////////////
void OMFilter::writeExtendedUsage(std::ostream& Stream)
{
    MDS_CERR("Necessary arguments: [-weight] [-inputW filename] [-filter] [-InputF filename] [-min] [-max] [-mean] [-median]" << std::endl);
    MDS_CERR("Options:" << std::endl);
	MDS_CERR("  -weight Includes matrix weighting - matrix of the same size multiplying each element." << std::endl);
	MDS_CERR("  -inputW Necessary argument if weighting is present - specifies file with filter kernel." << std::endl);
	MDS_CERR("         For format of file please see the documentation." << std::endl);
	MDS_CERR("  -filter Includes matrix convolution into list of filters to be computed." << std::endl);
	MDS_CERR("  -inputF Necessary argument if filter is present - specifies file with filter kernel." << std::endl);
	MDS_CERR("         For format of file please see the documentation." << std::endl);
	MDS_CERR("  -min Includes minimal element computation into list of filters to be computed." << std::endl);
	MDS_CERR("  -max Includes maximal element computation into list of filters to be computed." << std::endl);
	MDS_CERR("  -mean Includes mean computation into list of filters to be computed." << std::endl);
	MDS_CERR("  -median Includes median computation into list of filters to be computed." << std::endl);
	MDS_CERR("  -distance Includes abs(min, max) computation into list of filters to be computed." << std::endl);
	MDS_CERR("  -variance Includes variance computation into list of filters to be computed." << std::endl);
	MDS_CERR("All results are put into vertex attribute vector always in order seen in necessary arguments." << std::endl);
    MDS_CERR(std::endl);
}

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

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

    // Console application finished
    return 0;
}

