/*
 * SumIntoTableRange.hh
 *
 *  Created on: Oct 6, 2016
 *      Author: AKKA IS
 */

#ifndef SUMINTOTABLERANGE_HH_
#define SUMINTOTABLERANGE_HH_

#include "Parameter.hh"
#include "ParamData.hh"
#include "DataTypeMath.hh"
#include "Operation.hh"

namespace AMDA {
namespace Parameters {
namespace SumIntoTableRange {

namespace Base {
class TableDataTransform {
public:
	virtual ~TableDataTransform() {}
	virtual std::vector<double>  transformData(unsigned int /*index*/) = 0;
};
}

template<typename ParamInput>
class TableDataTransform : public Base::TableDataTransform {
public:

	/**
	 * @brief Constructor
	 */
	TableDataTransform(ParamInput&  pParamInput) : _paramInput(pParamInput) {}

private:
	/**
	 * @brief paramData Input
	 */
	ParamInput&  _paramInput;
};

template<typename ElType>
class TableDataTransform<ParamDataSpec<std::vector<ElType> >> : public Base::TableDataTransform {
public:
	typedef	ParamDataSpec<std::vector<ElType> > ParamInput;

	/**
	 * @brief Constructor
	 */
	TableDataTransform(ParamInput&  pParamInput) : _paramInput(pParamInput) {}

	/**
	 * @overload Base::TableDataTransform::transformData(unsigned int index)
	 */
	std::vector<double>  transformData(unsigned int index) {
		const std::vector<ElType>& lData = _paramInput.getDataList()[index];
		std::vector<double> transData;
		for (typename std::vector<ElType>::const_iterator it = lData.begin(); it != lData.end(); ++it) {
			transData.push_back((double)*it);
		}
		return transData;
	}

private:
	ParamInput&  _paramInput;
};



class TableParamDataVisitor : public VisitorOfParamData {
public:
	/**
	 * @brief Constructor.
	 */
	TableParamDataVisitor(ParamData& pInputParamData) : _dataTransform(NULL) {
		pInputParamData.accept(*this);
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataScalaireShort *)
	 */
	void visit(ParamDataScalaireShort *) { BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported")); }

	/**
	 * @overload VisitorOfParamData::visit(ParamDataScalaireFloat *)
	 */
	void visit(ParamDataScalaireFloat *) { BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported")); }

	/**
	 * @overload VisitorOfParamData::visit(ParamDataScalaireDouble *)
	 */
	void visit(ParamDataScalaireDouble *) { BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported")); }

	/**
	 * @overload VisitorOfParamData::visit(ParamDataScalaireLongDouble *)
	 */
	void visit(ParamDataScalaireLongDouble *) { BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported")); }

	/**
	 * @overload VisitorOfParamData::visit(ParamDataScalaireInt *)
	 */
	void visit(ParamDataScalaireInt *) { BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported")); }

	/**
	 * @overload VisitorOfParamData::visit(ParamDataLogicalData *)
	 */
	void visit(ParamDataLogicalData *) { BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported")); }

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab1DShort *)
	 */
	void visit(ParamDataTab1DShort *pInputParamData) {
		_dataTransform = new TableDataTransform<ParamDataTab1DShort>(*pInputParamData);
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab1DFloat *)
	 */
	void visit(ParamDataTab1DFloat *pInputParamData) {
		_dataTransform = new TableDataTransform<ParamDataTab1DFloat>(*pInputParamData);
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab1DDouble *)
	 */
	void visit(ParamDataTab1DDouble *pInputParamData) {
		_dataTransform = new TableDataTransform<ParamDataTab1DDouble>(*pInputParamData);
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab1DLongDouble *)
	 */
	void visit(ParamDataTab1DLongDouble *pInputParamData) {
		_dataTransform = new TableDataTransform<ParamDataTab1DLongDouble>(*pInputParamData);
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab1DInt *)
	 */
	void visit(ParamDataTab1DInt *pInputParamData) {
		_dataTransform = new TableDataTransform<ParamDataTab1DInt>(*pInputParamData);
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab1DLogicalData *)
	 */
	void visit(ParamDataTab1DLogicalData *) {
		BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported"));	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab2DShort *)
	 */
	void visit(ParamDataTab2DShort *) {
		BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported"));
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab2DFloat *)
	 */
	void visit(ParamDataTab2DFloat *) {
		BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported"));
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab2DDouble *)
	 */
	void visit(ParamDataTab2DDouble *) {
		BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported"));
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab2DLongDouble *)
	 */
	void visit(ParamDataTab2DLongDouble *) {
		BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported"));
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab2DInt *)
	 */
	void visit(ParamDataTab2DInt *) {
		BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported"));
	}

	/**
	 * @overload VisitorOfParamData::visit(ParamDataTab2DLogicalData *)
	 */
	void visit(ParamDataTab2DLogicalData *) {
		BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("TableParamDataPusher operation not supported"));
	}

	/**
	 * @brief get the Deriv parameterized operation.
	 */
	std::vector<double> transformData(unsigned int index) {

		return _dataTransform->transformData(index);
	}

private:
	Base::TableDataTransform* _dataTransform;
};


class SumIntoTableRangeTool {
public:
	SumIntoTableRangeTool(Process& pProcess, AMDA::Info::ParamTable* tablePtr, double minRange, double maxRange) : _process(pProcess), _tablePtr(tablePtr), _minRange(minRange), _maxRange(maxRange)  {
	}

	virtual ~SumIntoTableRangeTool() {
		for (std::map<std::string, TableParamDataVisitor*>::iterator it = _tableParamDataVisitor.begin(); it != _tableParamDataVisitor.end(); ++it)
			delete it->second;
		_tableParamDataVisitor.clear();
	}

	std::list<unsigned int> getIndexesList(double time, unsigned int size) {
		std::list<unsigned int> indexes;

		if (!_tablePtr->isVariable(&_process.getParameterManager())) {
			AMDA::Info::t_TableBound crtBound;

			for (unsigned int i = 0; i < size; ++i) {
				crtBound = _tablePtr->getBound(&_process.getParameterManager(), i);
				if (crtBound.min >= _minRange && crtBound.max <= _maxRange)
					indexes.push_back(i);
			}
		}
		else {
			std::map<std::string, std::vector<double>> paramsTableData;
			for (std::map<std::string, std::string>::iterator it = _tablePtr->getTableParams(&_process.getParameterManager()).begin(); it != _tablePtr->getTableParams(&_process.getParameterManager()).end(); ++it) {
				ParameterSPtr crtParam = _process.getParameterManager().getParameter(it->second);
				ParamData* ltableParamInput = crtParam->getParamData(&_process).get();
				
				//Retrieve related index for this "table" parameter
				unsigned int crtTimeTableParamIndex = 0;
				for (unsigned int tableParamindex = ltableParamInput->getIndexInfo()._startIndex ; tableParamindex < ltableParamInput->getDataNumber(); ++tableParamindex) {
					if (time < ltableParamInput->getTime(tableParamindex))
						break;
					crtTimeTableParamIndex = tableParamindex;
				}

				if (_tableParamDataVisitor.find(it->first) == _tableParamDataVisitor.end()) {
					_tableParamDataVisitor[it->first] = new TableParamDataVisitor(*ltableParamInput);
				}

				paramsTableData[it->first] = _tableParamDataVisitor[it->first]->transformData(crtTimeTableParamIndex);

				AMDA::Info::t_TableBound crtBound;

				for (unsigned int i = 0; i < size; ++i) {

					crtBound = _tablePtr->getBound(&_process.getParameterManager(), i, &paramsTableData);

					if ((crtBound.min >= _minRange) && (crtBound.max <= _maxRange)) {
						indexes.push_back(i);
					}
				}
			}
		}

		return indexes;
	}

private:
	Process   &_process;

	AMDA::Info::ParamTable* _tablePtr;

	double _minRange;

	double _maxRange;


	std::map<std::string, TableParamDataVisitor*> _tableParamDataVisitor;
};

/**
 * @class SumIntoTableRange1D
 * @brief It is responsible to compute the sum of 1D parameter data into a table range.
 * @details This class implement the interface Operation.
 */
template<typename DataType>
class SumIntoTableRange1D : public Operation {

public:
	/**
	 * @brief Constructor.
	 * @details Create the ParamData type of the input ParamData.
	 */
	SumIntoTableRange1D(Process& pProcess, ParamDataSpec<std::vector<DataType> >& paramInput, double minRange, double maxRange, AMDA::Info::ParamTable* tablePtr)
	: Operation(pProcess),
	  _paramInput(paramInput),
	  _paramOutput(new ParamDataSpec<DataType>()),
	  _tableTool(pProcess, tablePtr, minRange, maxRange) {
	  _paramDataOutput=_paramOutput;
	}

	virtual ~SumIntoTableRange1D() {
	}

	/**
	 * @overload Operation::write(ParamDataIndexInfo &pParamDataIndexInfo)
	 */
  void  write(ParamDataIndexInfo &pParamDataIndexInfo) {
	  for (unsigned int _index = pParamDataIndexInfo._startIndex ;
			  _index < pParamDataIndexInfo._startIndex + pParamDataIndexInfo._nbDataToProcess;
			  ++_index)
	  {
		  double crtTime = _paramInput.getTime(_index);
		  std::vector<DataType> inputElt = _paramInput.get(_index);

		  std::list<unsigned int> indexes = _tableTool.getIndexesList(crtTime, inputElt.size());

		  DataType output = 0;

		  for (std::list<unsigned int>::iterator it = indexes.begin(); it != indexes.end(); ++it) {
			  if (isNAN(inputElt[*it]))
				  continue;
			  output += inputElt[*it];
		  }

		  _paramOutput->pushTime(crtTime);
		  _paramOutput->getDataList().push_back(output);
	  }
  }

private:
	/**<
	 * @brief It is the channel of data derived
	 */
  ParamDataSpec<std::vector<DataType> > &_paramInput;

	/**<
	 * @brief It is the channel of the data derived
	 */
  ParamDataSpec<DataType> *_paramOutput;

  SumIntoTableRangeTool _tableTool;
};

/**
 * @class SumIntoTableRange2DOneRange
 * @brief It is responsible to compute the sum of 2D parameter data into one table range.
 * @details This class implement the interface Operation.
 */
template<typename DataType>
class SumIntoTableRange2DOneRange : public Operation {

public:

	/**
	 * @brief Constructor.
	 * @details Create the ParamData type of the input ParamData when only one range is defined for one dimension (the output data is a vector).
	 */
	SumIntoTableRange2DOneRange(Process& pProcess, ParamDataSpec<Tab2DData<DataType> >& paramInput, double minRange, double maxRange, AMDA::Info::ParamTable* tablePtr, int tableRelatedDim)
	: Operation(pProcess),
	  _paramInput(paramInput),
	  _paramOutput(new ParamDataSpec<std::vector<DataType>>()),
	  _tableTool(new SumIntoTableRangeTool(pProcess, tablePtr, minRange, maxRange)),
	  _tableRelatedDim(tableRelatedDim) {
		_paramDataOutput = _paramOutput;
        }


	virtual ~SumIntoTableRange2DOneRange() {
		if (_tableTool != NULL)
			delete _tableTool;
	}

	/**
	 * @overload Operation::write(ParamDataIndexInfo &pParamDataIndexInfo)
	 */
	void  write(ParamDataIndexInfo &pParamDataIndexInfo) {
		for (unsigned int _index = pParamDataIndexInfo._startIndex ;
			_index < pParamDataIndexInfo._startIndex + pParamDataIndexInfo._nbDataToProcess;
			++_index)
		{
			double crtTime = _paramInput.getTime(_index);
			Tab2DData<DataType> inputElt = _paramInput.get(_index);

			std::vector<DataType> outputElt;
			if (_tableRelatedDim == 0) {
				std::list<unsigned int> indexesTable = _tableTool->getIndexesList(crtTime, inputElt.getDim1Size());
				outputElt.resize(inputElt.getDim2Size(), 0);

				for (std::list<unsigned int>::iterator it1 = indexesTable.begin(); it1 != indexesTable.end(); ++it1) {
					for (int i = 0; i < inputElt.getDim2Size(); ++i) {
						if (isNAN(inputElt[*it1][i]))
							continue;
						outputElt[i] += inputElt[*it1][i];
					}
				}
			}
			else {
				std::list<unsigned int> indexesTable = _tableTool->getIndexesList(crtTime, inputElt.getDim2Size());
				outputElt.resize(inputElt.getDim1Size(), 0);

				for (std::list<unsigned int>::iterator it1 = indexesTable.begin(); it1 != indexesTable.end(); ++it1) {
					for (int i = 0; i < inputElt.getDim1Size(); ++i) {
						if (isNAN(inputElt[i][*it1]))
							continue;
						outputElt[i] += inputElt[i][*it1];
					}
				}
			}

			_paramOutput->pushTime(crtTime);
			_paramOutput->getDataList().push_back(outputElt);
		}
	}

private:
	/**<
	 * @brief It is the channel of data derived
	 */
	ParamDataSpec<Tab2DData<DataType> > &_paramInput;

	ParamDataSpec<DataType> *_paramOutputScalar;

	ParamDataSpec<std::vector<DataType>> *_paramOutput;

	SumIntoTableRangeTool* _tableTool;

	int _tableRelatedDim;
};

/**
 * @class SumIntoTableRange2DTwoRanges
 * @brief It is responsible to compute the sum of 2D parameter data into two tables ranges.
 * @details This class implement the interface Operation.
 */
template<typename DataType>
class SumIntoTableRange2DTwoRanges : public Operation {

public:

	/**
 	 * @brief Constructor.
	 * @details Create the ParamData type of the input ParamData when a range is defined for each dimensions (the output data is a scalar).
	 */
	SumIntoTableRange2DTwoRanges(Process& pProcess, ParamDataSpec<Tab2DData<DataType> >& paramInput, double minRange1, double maxRange1, AMDA::Info::ParamTable* table1Ptr, int table1RelatedDim, double minRange2, double maxRange2, AMDA::Info::ParamTable* table2Ptr)
	: Operation(pProcess),
	  _paramInput(paramInput),
	  _paramOutput(new ParamDataSpec<DataType>()),
	  _table1Tool(new SumIntoTableRangeTool(pProcess, table1Ptr, minRange1, maxRange1)),
	  _table2Tool(new SumIntoTableRangeTool(pProcess, table2Ptr, minRange2, maxRange2)),
	  _table1RelatedDim(table1RelatedDim) {
		_paramDataOutput = _paramOutput;
	}

	virtual ~SumIntoTableRange2DTwoRanges() {
		if (_table1Tool != NULL)
			delete _table1Tool;
		if (_table2Tool != NULL)
			delete _table2Tool;
	}

	/**
 	 * @overload Operation::write(ParamDataIndexInfo &pParamDataIndexInfo)
 	 */
	void  write(ParamDataIndexInfo &pParamDataIndexInfo) {
		for (unsigned int _index = pParamDataIndexInfo._startIndex ;
			_index < pParamDataIndexInfo._startIndex + pParamDataIndexInfo._nbDataToProcess;
			++_index)
		{
			double crtTime = _paramInput.getTime(_index);
			Tab2DData<DataType> inputElt = _paramInput.get(_index);

			std::list<unsigned int> indexesTable1 = _table1Tool->getIndexesList(crtTime, inputElt.getDim1Size());
			std::list<unsigned int> indexesTable2 = _table2Tool->getIndexesList(crtTime, inputElt.getDim2Size());

			DataType outputElt = 0;

			for (std::list<unsigned int>::iterator it1 = indexesTable1.begin(); it1 != indexesTable1.end(); ++it1) {
				for (std::list<unsigned int>::iterator it2 = indexesTable2.begin(); it2 != indexesTable2.end(); ++it2) {
					if (_table1RelatedDim == 0) {
						if (isNAN(inputElt[*it1][*it2]))
							continue;
						outputElt += inputElt[*it1][*it2];
					}
					else {
						if (isNAN(inputElt[*it2][*it1]))
							continue;
						outputElt += inputElt[*it2][*it1];
					}
				}
			}

			_paramOutput->pushTime(crtTime);
			_paramOutput->getDataList().push_back(outputElt);
                }
        }	

private:
	/**<
	 * @brief It is the channel of data derived
	 */
	ParamDataSpec<Tab2DData<DataType> > &_paramInput;

	ParamDataSpec<DataType> *_paramOutput;

	SumIntoTableRangeTool* _table1Tool;

	SumIntoTableRangeTool* _table2Tool;

	int _table1RelatedDim;
};


} /* namespace SumIntoTableRange */
} /* namespace Parameters */
} /* namespace AMDA */
#endif /* SUMINTOTABLERANGE_HH_ */