/*
 * ParamOutputDataMining.cc
 *
 *  Created on: 23 août 2013
 *      Author: CS
 */

#include <string>
#include <iostream>
#include <sstream>

#include <iomanip>
#include <string.h>
#include <time.h>
#include <map>
#include <set>
#include <limits>

#include "ParamOutputDataMining.hh"

#include "DicError.hh"

// Parameters
#include "ParameterManager.hh"
#include "Parameter.hh"
#include "ParamData.hh"
#include "TimeUtil.hh"
#include "Helper.hh"
#include "Process.hh"

#include "Config.hh"
#include "DD_time.hh"

// TimeTable
#include "AbstractWriter.hh"
#include "AsciiWriter.hh"
#include "InternalXMLWriter.hh"
#include "VOTableWriter.hh"
#include "TimeTableCatalogUtil.hh"

// DD_Client_r_Lib
#include "TimeUtil.hh"

#include "ParamMgr.hh"

using namespace AMDA::Parameters;
using namespace TimeTableCatalog;

namespace AMDA {
	namespace ParamOutputImpl {
		namespace DataMining {


			const std::map<std::string, TimeTable::TIME_FORMAT> ParamOutputDataMining::TIME_FORMAT_MAP = {
				{"", TimeTable::TIME_FORMAT::UNKNOWN},
				{"ISO", TimeTable::TIME_FORMAT::YYYYMMDDThhmmss}
			};

			const std::map<std::string, std::string> ParamOutputDataMining::FILE_FORMAT_MAP = {
				{"ASCII", AsciiWriter::FORMAT},
				{"XML", InternalXMLWriter::FORMAT},
				{"VOT", VOTableWriter::FORMAT}
			};

			static const std::string ONE_FILE = "one-file";
			static const std::string ONE_FILE_PER_INTERVAL = "one-file-per-interval";

			ParamOutputDataMining::ParamOutputDataMining(ParameterManager& pParameterManager) :
					ParamOutput(pParameterManager),
					_fileFormat(FILE_FORMAT_MAP.find("ASCII")->second),
					_timeFormat(TIME_FORMAT_MAP.find("ISO")->second),
					_outputStructure(ONE_FILE),
					_fileName("timeTable_PARAMID_XXX"),
					_firstVisit(true),
					_taskPriority(PRIORITY::PARAM) {

			}

			ParamOutputDataMining::~ParamOutputDataMining() {
			}

			void ParamOutputDataMining::establishConnection() {
				// TODO check if _paramName is set otherwise throw an error
				LOG4CXX_DEBUG(_logger, "ParamOutputDataMining::establishConnection : " << "Retrieve parameter");
				_parameter = _parameterManager.getSampledParameter(_paramName, _samplingMode, _samplingValue, _gapThreshold, true);
				if(_parameter == NULL)  {
					LOG4CXX_ERROR(_logger,"ParamOutput::init parameter : \""<< _paramName <<"\" Not Exist" );
					BOOST_THROW_EXCEPTION( ParamOutput_exception());
				} else {
					_parameter->openConnection(this);
					_samplingValue = getSamplingInTreeParameter(_parameter);
					_gapThreshold = _parameter->getGapThreshold();
				}
			}

			void ParamOutputDataMining::init() {
				if(_timeIntervalList->size() != 0) {
					getParameter()->init(this, _timeIntervalList);
				} else {
					// No need to initialize parameter when there is no time interval to process.
				}

				AMDA::Info::ParamInfoSPtr paramInfo = AMDA::Info::ParamMgr::getInstance()->getParamInfoFromId(_parameter->getInfoId());
				if (paramInfo != nullptr) {
					_expression = paramInfo->getProcessDescription();
				}
				if (_expression.empty()) {
					_expression = "Unknown";
				}
			}

			/**
			 * @note Create instance of DataMiningType
			 */
			void ParamOutputDataMining::createDataMiningWriter() {
				// DataMiningTooSmall.
				DataMiningInvalidInterval lDataMiningTooSmall = DataMiningTooSmall(this);
				lDataMiningTooSmall.setWorkPath(_workPath);
				// Set the biggest time interval because there is no need to parse each time interval.
				if (!_timeIntervalListTooSmall->empty())
					lDataMiningTooSmall.setTimeInterval(*_currentTimeInterval);

				// Calculated digit number must be greater than minimum digit number constant.
				std::stringstream buf;
				buf << _timeIntervalListTooSmall->size();
				unsigned int lDigitNumber = buf.str().size();
				if(lDigitNumber < DataMiningType::MIN_DIGIT_NUMBER) {
					lDigitNumber = DataMiningType::MIN_DIGIT_NUMBER;
				}

				// DataMiningTrue
				DataMiningValidInterval lDataMiningTrue = DataMiningTrue(this);
				lDataMiningTrue.setWorkPath(_workPath);
				lDataMiningTrue.setDigitNumber(lDigitNumber);

				// DataMiningNoData
				DataMiningValidInterval lDataMiningNoData = DataMiningNoData(this);
				lDataMiningNoData.setWorkPath(_workPath);
				lDataMiningNoData.setDigitNumber(lDigitNumber);

				// Define first time interval.
//				if(_outputStructure == ONE_FILE) {
					// We must give start time of the first time interval and end time of the last time interval
//					lDataMiningTrue.setTimeInterval(TimeInterval(_timeIntervalList->front()._startTime, _timeIntervalList->back()._stopTime));
//					lDataMiningNoData.setTimeInterval(TimeInterval(_timeIntervalList->front()._startTime, _timeIntervalList->back()._stopTime));
//				} else {
					lDataMiningTrue.setTimeInterval(*_currentTimeInterval);
					lDataMiningNoData.setTimeInterval(*_currentTimeInterval);

//				}

				// Push in valid interval list
				_dataMiningValidIntervalList.push_back(lDataMiningTrue);
				_dataMiningValidIntervalList.push_back(lDataMiningNoData);

				// Push in invalid inteval list
				_dataMiningInvalidIntervalList.push_back(lDataMiningTooSmall);
			}

			/**
			 * @note Process data where time interval list is valid (greater or equal than sampling time).
			 */
			void ParamOutputDataMining::processInvalidInterval() {
				for (DataMiningInvalidIntervalList::iterator it = _dataMiningInvalidIntervalList.begin(); it != _dataMiningInvalidIntervalList.end(); ++it) {
					// Store invalid time interval in time table.
					(*it).createInterval(_timeIntervalListTooSmall.get());

					// Write information in time table.
					(*it).fillInformation(_timeFormat, _fileName, 0);
				}
			}

			/**
			 * @note Process data where time interval list is invalid (less than sampling time).
			 */
			void ParamOutputDataMining::processValidInterval() {
				// Index of created file
				int lFileIndex = 0;

				do {
					try {
						_paramDataIndexInfo = getParameter()->getAsync(this).get();
						getParameter()->getParamData(this)->accept(*this);

						if (_paramDataIndexInfo._timeIntToProcessChanged) {

							// Set next time interval.
							LOG4CXX_DEBUG(_logger, "ParamOutputDataMining => Next Time Interval");
							++_currentTimeInterval;

							if (_currentTimeInterval == _timeIntervalList->end()) {
								_paramDataIndexInfo._timeIntToProcessChanged = false;
								continue;
							}

							// Increase index for the filename.
							if(_outputStructure == ONE_FILE_PER_INTERVAL) {
								lFileIndex++;
							}

							// For each DataMiningValidInterval write time table content and change time interval.
							for(DataMiningValidIntervalList::iterator it = _dataMiningValidIntervalList.begin(); it != _dataMiningValidIntervalList.end(); ++it) {
								if(_outputStructure == ONE_FILE_PER_INTERVAL) {
									(*it).fillInformation(_timeFormat, _fileName, lFileIndex);
								}

								// Change time interval
								(*it).setTimeInterval(*_currentTimeInterval);
							}

						}
					} catch(AMDA::AMDA_exception & e) {
						e << AMDA::errno_code(AMDA_PARAM_OUTPUT_ERR);
						throw;
					}
				} while (!_paramDataIndexInfo._noMoreTimeInt);

				// Increase index for the filename.
				if(_outputStructure == ONE_FILE_PER_INTERVAL) {
					lFileIndex++;
				}

				// For each DataMiningValidInterval fill information of time table (description, history, filename, ...).
				for(DataMiningValidIntervalList::iterator it = _dataMiningValidIntervalList.begin(); it != _dataMiningValidIntervalList.end(); ++it) {
					(*it).fillInformation(_timeFormat, _fileName, lFileIndex);
				}
			}

			void ParamOutputDataMining::apply() {
				// Check if all needed parameter are set

				// If sampling value is set to 0 that's to say
				// time_resolution tag was not set in param xml file.
				if(_samplingValue == 0) {
					// Retrieve default sampling time for this param
					if(_parameter->getTimeResolution() == 0) {
						_samplingValue = _parameter->getParamData(this)->getMinSampling();
					} else {
						_samplingValue = _parameter->getTimeResolution();
					}
				}

				// Check if intervals creation must be canceled.
				if (!cancelIntervalCreation()) {
					// Create DataMiningTrue, DataMiningNoData, DataMiningTooSmall
					createDataMiningWriter();

					// Process invalid interval
					processInvalidInterval();

					// Process valid interval
					if(_timeIntervalList->size() != 0) {
						processValidInterval();
					} else {
						// Nothing to do.
					}

				} else {
					LOG4CXX_DEBUG(_logger, "ParamOutputDataMining => The output treatment with priority '" << _taskPriority << "' for param name '" << _paramName << "' is aborted because there is an other treatment with an higher priority");
					_parameter->closeConnection(this);
				}
			}

			bool ParamOutputDataMining::cancelIntervalCreation() {
				// The output is aborted only if there is an other output on the same param
				// that have an higher priority
				bool lAbortTreatment = false;

				// If this output has the highest priority don't check other output
				if(_taskPriority == (PRIORITY::HIGHER_PRIORITY_VALUE - 1)) {
					// Nothing to do
				} else {
					// Search for other output that are of type ParamOutputDataMining which work on the same param.
					// If the found output has an higher priority than the current output, the current output task is aborted.

					ParamOutputList lParamOutputList = _parameterManager.getParamOutputList();
					for(std::list<ParamOutputSPtr>::iterator it=lParamOutputList.begin(); it != lParamOutputList.end(); ++it) {
						if (typeid((*(it->get()))) == typeid(ParamOutputDataMining)) {
							ParamOutputDataMining* lOtherOutput =  dynamic_cast<ParamOutputDataMining*>(it->get());

							// Check if the other output work on the same param and has an higher priority
							if(lOtherOutput->getParamName() == _paramName && lOtherOutput->getOutputPriority() > _taskPriority) {
								lAbortTreatment = true;
							}
						}
					}
				}

				return lAbortTreatment;
			}

			void ParamOutputDataMining::terminate() {
				std::vector<std::string> lFilenames;

				// For each DataMiningValidInterval write time table content.
				for(DataMiningValidIntervalList::iterator it = _dataMiningValidIntervalList.begin(); it != _dataMiningValidIntervalList.end(); ++it) {
					lFilenames = (*it).write(_fileFormat);
					_files.insert(_files.end(), lFilenames.begin(), lFilenames.end());
				}

				for(DataMiningInvalidIntervalList::iterator it = _dataMiningInvalidIntervalList.begin(); it != _dataMiningInvalidIntervalList.end(); ++it) {
					lFilenames = (*it).write(_fileFormat);
					_files.insert(_files.end(), lFilenames.begin(), lFilenames.end());
				}

				// Call post processing
				ParamOutput::terminate();
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireShort *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataScalaireShort * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireFloat *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataScalaireFloat * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireDouble *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataScalaireDouble * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireLongDouble *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataScalaireLongDouble * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireInt *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataScalaireInt * pParamData) {
				LOG4CXX_DEBUG(_logger, "ParamOutputDataMining => create output");
				for(DataMiningValidIntervalList::iterator it = _dataMiningValidIntervalList.begin(); it != _dataMiningValidIntervalList.end(); ++it) {
					(*it).createInterval<AMDA::Parameters::ParamDataScalaireInt>(pParamData, _paramDataIndexInfo);
				}
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataLogicalData *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataLogicalData * pParamData) {
				LOG4CXX_DEBUG(_logger, "ParamOutputDataMining => create output");
				for(DataMiningValidIntervalList::iterator it = _dataMiningValidIntervalList.begin(); it != _dataMiningValidIntervalList.end(); ++it) {
					(*it).createInterval<AMDA::Parameters::ParamDataLogicalData>(pParamData, _paramDataIndexInfo);
				}
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DShort *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab1DShort * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DFloat *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab1DFloat * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DDouble *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab1DDouble * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DLongDouble *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab1DLongDouble * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DInt *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab1DInt * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DLogicalData *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab1DLogicalData * /*pParamData*/) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DShort *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab2DShort *) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DFloat *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab2DFloat *) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DDouble *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab2DDouble *) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DLongDouble *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab2DLongDouble *) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DInt *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab2DInt *) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DLogicalData *)
			 */
			void ParamOutputDataMining::visit(AMDA::Parameters::ParamDataTab2DLogicalData *) {
				ERROR_EXCEPTION("Not Implemented!");
			}

			// Access method

			/**
			 * @brief Set the file format value. This function do a mapping between value in the output request XML file
			 * and Time Table writer format.
			 */
			void ParamOutputDataMining::setFileFormat(const std::string& pFileFormat) {
				std::map<std::string, std::string>::const_iterator it;
				it = FILE_FORMAT_MAP.find(pFileFormat);

				std::string lFileFormatMapValue;
				if(it != FILE_FORMAT_MAP.end()) {
					lFileFormatMapValue = it->second;
				} else {
					lFileFormatMapValue = FILE_FORMAT_MAP.find("ASCII")->second;
				}


				_fileFormat = lFileFormatMapValue;
			}

			/**
			 * @brief Set the time format value. This function do a mapping between value in the output request XML file
			 * and Time Table time format.
			 */
			void ParamOutputDataMining::setTimeFormat(std::string pTimeFormat) {
				std::map<std::string, TimeTable::TIME_FORMAT>::const_iterator it;
				it = TIME_FORMAT_MAP.find(pTimeFormat);

				TimeTable::TIME_FORMAT lTimeFormatMapValue;
				if(it != TIME_FORMAT_MAP.end()) {
					lTimeFormatMapValue = it->second;
				} else {
					lTimeFormatMapValue = TIME_FORMAT_MAP.find("")->second;
				}

				_timeFormat = lTimeFormatMapValue;
			}

		} // namespace DataMining
	} // namespace ParamOutputImpl
} /* namespace AMDA */