/*
 * Scheduler.hh
 *
 *  Created on: Dec 3, 2012
 *      Author: f.casimir
 */

#ifndef Scheduler_HH_
#define Scheduler_HH_

#include "vector"

#include <boost/algorithm/string.hpp>

#include "AMDA_exception.hh"
#include "DicError.hh"

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

using namespace std;
using namespace boost;
using namespace AMDA::Parameters;

namespace AMDA {
	namespace Merge {

	/**
	 * name space of base class, implementation are template class
	 */
		namespace Base {

		/**
		 * @brief abstract class can write in paramDatas output
		 * @details is the strategy interface in the Strategy pattern
		 * the implementation arc made by template Pusher class
		 */
		class Pusher {
			public:
				virtual ~Pusher() {}
				virtual void  write(unsigned int /*index*/) {
					 BOOST_THROW_EXCEPTION(AMDA::AMDA_exception() << AMDA::errno_code(AMDA_ERROR_UNKNOWN) << AMDA::ex_msg("Pusher operation not supported"));
				}
			};

		/**
		 * @brief the operation of MergeProcess
		 * @detail write one line by paramData input
		 * The implementation are made by template class.
		 */
		 class Scheduler : public Operation {
			public:
			     /**
			     * @brief Constructor
			    */
				Scheduler(Process& pProcess) : Operation(pProcess) {}
				/**
				 * @brief Destructor
				 */
				virtual ~Scheduler() { for(auto operation : _operationList) { delete operation; } }
				/**
				 * @brief add an input paramData
				 */
				virtual void push_back(ParamData*) = 0;

			protected:
				/**
				 * @brief store a Base::Pusher by ParamDataInput
				 */
				  std::vector<Base::Pusher*> _operationList;
			};

		} // Base;


		/**
		 * Definition of Pucher class template
		 */
		template<typename ParamOutput, typename ParamInput>
		class Pusher : public Base::Pusher {
		public:

		     /**
		     * @brief Constructor
		    */
			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

		private:
			/**
			 * @brief paramData Output
			 */
			ParamOutput& _paramOutput;

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

		/**
		 * @brief vector implementation of Pusher template
		 * @detail ParamOutput and ParamInput are ParamDataSpec<std::vector<ElType> with ElType are standard C++ type as float
		 */
		template<typename ElType1, typename ElType2>
		class Pusher<ParamDataSpec<std::vector<ElType1> >,ParamDataSpec<std::vector<ElType2> > > : public Base::Pusher {
		public:
			typedef	ParamDataSpec<std::vector<ElType1> > ParamOutput;
			typedef	ParamDataSpec<std::vector<ElType2> > ParamInput;

			/**
			 * @brief Constructor
			 */
			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

			/**
			 * @overload operation::write(unsigned int index)
			 */
			void  write(unsigned int index) {
				  _paramOutput.pushTime(_paramInput.getTime(index));
				  const std::vector<ElType2>& lData = _paramInput.getDataList()[index];
				  _paramOutput.getDataList().push_back(std::vector<ElType1>(lData.begin(),lData.end()));
			}

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};
		/**
		 * @brief vector implementation of Pusher template
		 * @detail ParamOutput and ParamInput are ParamDataSpec<std::vector<LogicalData>
		 */
		template<>
		class Pusher<ParamDataSpec<std::vector<LogicalData> >,ParamDataSpec<std::vector<LogicalData> > > : public Base::Pusher {
		public:
			typedef	ParamDataSpec<std::vector<LogicalData> > ParamOutput;
			typedef	ParamDataSpec<std::vector<LogicalData> > ParamInput;

			/**
			 * @brief Constructor
			 */
			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

			/**
			 * @overload operation::write(unsigned int index)
			 */
			void  write(unsigned int index) {
				  _paramOutput.pushTime(_paramInput.getTime(index));
				  _paramOutput.getDataList().push_back(_paramInput.getDataList()[index]);
			  }
			

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};

		/**
		 * @brief vector implementation of Pusher template
		 * @detail ParamOutput are ParamDataSpec<std::vector<LogicalData>
		 * and ParamInput are ParamDataSpec<std::vector<ElType> with ElType are standard C++ type as float
		 * throw an exception if use @see Base::Pusher::write()
		 */
		template<typename ElType2>
		class Pusher<ParamDataSpec<std::vector<LogicalData> >,ParamDataSpec<std::vector<ElType2> > > : public Base::Pusher {
		public:
			typedef	ParamDataSpec<std::vector<LogicalData> > ParamOutput;
			typedef	ParamDataSpec<std::vector<ElType2> > ParamInput;

			/**
			 * @brief Constructor
			 */
			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};

		/**
		 * @brief vector implementation of Pusher template
		 * @detail ParamOutput are ParamDataSpec<std::vector<ElType> with ElType are standard C++ type as float
		 * and ParamInput are  ParamDataSpec<std::vector<LogicalData>
		 * throw an exception if use @see Base::Pusher::write()
		 */
		template<typename ElType1>
		class Pusher<ParamDataSpec<std::vector<ElType1> >,ParamDataSpec<std::vector<LogicalData> > > : public Base::Pusher {
		public:
			typedef	ParamDataSpec<std::vector<ElType1> > ParamOutput;
			typedef	ParamDataSpec<std::vector<LogicalData> > ParamInput;

			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};


		/**
		 * @brief vector implementation of Pusher template
		 * @detail ParamOutput and ParamInput are ParamDataSpec<std::vector<ElType> with ElType are standard C++ type as float
		 */
		template<typename ElType>
		class Pusher<ParamDataSpec<std::vector<ElType> >,ParamDataSpec<std::vector<ElType> > > : public Base::Pusher {
		public:
			typedef	ParamDataSpec<std::vector<ElType> > ParamOutput;
			typedef	ParamDataSpec<std::vector<ElType> > ParamInput;

			/**
			 * @brief Constructor
			 */
			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

			/**
			 * @overload operation::write(unsigned int index)
			 */
			void  write(unsigned int index) {
				_paramOutput.pushTime(_paramInput.getTime(index));
				_paramOutput.getDataList().push_back(_paramInput.getDataList()[index]);
			}

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};

		/**
		 * @brief vector implementation of Pusher template
		 * @detail ParamOutput and ParamInput are ParamDataSpec<ElType> with ElType are standard C++ type as float
		 */
		template<typename ElType>
		class Pusher<ParamDataSpec<ElType>,ParamDataSpec<ElType> > : public Base::Pusher {
		public:
			typedef	ParamDataSpec<ElType> ParamOutput;
			typedef	ParamDataSpec<ElType> ParamInput;

			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

			/**
			 * @overload operation::write(unsigned int index)
			 */
			void  write(unsigned int index) {
				_paramOutput.pushTime(_paramInput.getTime(index));
				_paramOutput.getDataList().push_back(_paramInput.getDataList()[index]);
			  }			

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};

		/**
		 * @brief vector implementation of Pusher template
		 * @detail  ParamOutput is is ParamDataSpec<LogicalData>
		 * and ParamInput is any
		 * throw an exception if use @see Base::Pusher::write()
		 */
		template<typename TParamInput>
		class Pusher<ParamDataSpec<LogicalData>,TParamInput> : public Base::Pusher {
		public:
			typedef	ParamDataSpec<LogicalData> ParamOutput;
			typedef	TParamInput ParamInput;

			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};

		/**
		 * @brief vector implementation of Pusher template
		 * @detail ParamOutput is any
		 * and ParamInput are ParamDataSpec<LogicalData>
		 * throw an exception if use @see Base::Pusher::write()
		 */
		template<typename TParamOutput>
		class Pusher<TParamOutput,ParamDataSpec<LogicalData> > : public Base::Pusher {
		public:
			typedef	TParamOutput ParamOutput;
			typedef	ParamDataSpec<LogicalData> ParamInput;

			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};

		/**
		 * @brief vector implementation of Pusher template
		 * @detail ParamOutput and ParamInput are ParamDataSpec<LogicalData>
		 * throw an exception if use @see Base::Pusher::write()
		 */
		template<>
		class Pusher<ParamDataSpec<LogicalData>,ParamDataSpec<LogicalData> > : public Base::Pusher {
		public:
			typedef	ParamDataSpec<LogicalData> ParamOutput;
			typedef	ParamDataSpec<LogicalData> ParamInput;

			Pusher(ParamOutput& pParamOutput, ParamInput&  pParamInput) : _paramOutput(pParamOutput), _paramInput(pParamInput) {}

			/**
			 * @overload operation::write(unsigned int index)
			 */
			void  write(unsigned int index) {
				_paramOutput.pushTime(_paramInput.getTime(index));
				_paramOutput.getDataList().push_back(_paramInput.getDataList()[index]);
			  }
			

		private:
			ParamOutput& _paramOutput;
			ParamInput&  _paramInput;
		};

		/**
		 * @brief Create the specific Pusher class
		 * @details used the visitor pattern for find the good Pusher in function of ParamInput type
		 */
		template<typename TParamOutput>
		class PusherCreator : public VisitorOfParamData {
		public:
			/**
			 * @brief Constructor.
			 */
			PusherCreator(TParamOutput& pOutputParamData,ParamData& pInputParamData)
				: _outputParamData(pOutputParamData), _operation(nullptr) {

				pInputParamData.accept(*this);
			}

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireShort *)
			 */
			void visit(ParamDataScalaireShort *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataScalaireShort>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireFloat *)
			 */
			void visit(ParamDataScalaireFloat *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataScalaireFloat>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireDouble *)
			 */
			void visit(ParamDataScalaireDouble *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataScalaireDouble>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireLongDouble *)
			 */
			void visit(ParamDataScalaireLongDouble *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataScalaireLongDouble>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataScalaireInt *)
			 */
			void visit(ParamDataScalaireInt *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataScalaireInt>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataLogicalData *)
			 */
			void visit(ParamDataLogicalData *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataLogicalData>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DShort *)
			 */
			void visit(ParamDataTab1DShort *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab1DShort>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DFloat *)
			 */
			void visit(ParamDataTab1DFloat *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab1DFloat>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DDouble *)
			 */
			void visit(ParamDataTab1DDouble *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab1DDouble>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DLongDouble *)
			 */
			void visit(ParamDataTab1DLongDouble *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab1DLongDouble>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DInt *)
			 */
			void visit(ParamDataTab1DInt *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab1DInt>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab1DLogicalData *)
			 */
			void visit(ParamDataTab1DLogicalData *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab1DLogicalData>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DShort *)
			 */
			void visit(ParamDataTab2DShort *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab2DShort>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DFloat *)
			 */
			void visit(ParamDataTab2DFloat *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab2DFloat>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DDouble *)
			 */
			void visit(ParamDataTab2DDouble *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab2DDouble>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DLongDouble *)
			 */
			void visit(ParamDataTab2DLongDouble *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab2DLongDouble>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DInt *)
			 */
			void visit(ParamDataTab2DInt *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab2DInt>( _outputParamData,*pInputParamData); }

			/**
			 * @overload VisitorOfParamData::visit(ParamDataTab2DLogicalData *)
			 */
			void visit(ParamDataTab2DLogicalData *pInputParamData) { _operation = new Pusher<TParamOutput,ParamDataTab2DLogicalData>( _outputParamData,*pInputParamData); }

			Base::Pusher *getPusher() { return _operation; }

		private:
			TParamOutput &_outputParamData;
			Base::Pusher *_operation;
		};


		/**
		 * @brief Base::Scheduler implementation
		 */
	template<typename TParamOutput>
	class Scheduler : public Base::Scheduler {
		public:
			Scheduler(Process& pProcess) : Base::Scheduler(pProcess), _paramOutput(new TParamOutput()) {
				_paramDataOutput=_paramOutput;
			}
			/**
			* @overload  Base::Scheduler::write(ParamDataIndexInfo &pParamDataIndexInfo)
			 */
			void push_back(ParamData* pInputParam) {
				PusherCreator<TParamOutput> lPusherCreator(*_paramOutput,*pInputParam);
				_operationList.push_back(lPusherCreator.getPusher());
			}

		  /**
			 * @overload Operation::write(ParamDataIndexInfo &pParamDataIndexInfo)
			 */
		  void  write(ParamDataIndexInfo &pParamDataIndexInfo) {
		   unsigned int index = pParamDataIndexInfo._startIndex;
			  for (; index< pParamDataIndexInfo._startIndex + pParamDataIndexInfo._nbDataToProcess; index++) {
			     for(auto operation : _operationList) { operation->write(index); }
			  }
		  }

		protected:
			/**
			 * @brief It is the channel of the data shifted.
			 */
		  TParamOutput *_paramOutput;
		};


} /* namespace Merge */
} /* namespace AMDA */
#endif /* Scheduler_HH_ */