// Copyright (C) 2012 Deltares
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

/**
 * @file
 * @brief rtcToolsRuntime.h
 * @author Camiel van Breugelen, Dirk Schwanenberg
 * @version 1.0
 * @date 2012
 */


#ifndef RTCTOOLSRUNTIME_H
#define RTCTOOLSRUNTIME_H

#define HAVE_CONFIG_H

#include "rtcRuntimeConfigSettings.h"
#include "rtcToolsOptimizer.h"

#include "rtcToolsDLLDefs.h"
#include "timeseries/timeSeriesModel.h"
#include "optimizationProblem/objectiveFunction.h"
#include "timeseries/scenarioTreeInterface.h"
#include "rtcToolsSimulator.h"
#include "schematization/parameterInterface.h"
#include "schematization/schematisation.h"
#include "dataBinding/pi_timeseries.hxx"
#include "dataBinding/rtcRuntimeConfig.hxx"
#include "utilities/utils.h"
#include "version.h"

#include <string>
#include <iostream>
#include <exception>
#include <ctime>

using namespace rtctools::timeseries;
using namespace rtctools::optimizationProblem;
using namespace std;

namespace rtctools
{

struct versionInfo {
	bool isProprietary;
	string version;
	string revision;
	versionInfo() :
		isProprietary(false), version(OSS_Version), revision(OSS_Revision) {};
	versionInfo(bool isProprietary, string version, string revision) :
		isProprietary(isProprietary), version(version), revision(revision) {};
};

/**
  * @brief This is the main class of RTC Tools.
  */
class RTCTOOLS_DLL_API rtcToolsRuntime
{
	private:
		bool watchdogInitialization;
		int iStart;
		int iEnd;
		int iForecastTime;
		bool eval_g_new;
		versionInfo info;

		/**
		 * @brief time series model with 3D matrix of
		 * input and output time series, dimensions are representing
		 * ensembles, time steps, and series
		 */
		timeSeriesModel *tsModel;

		/**
		 * @brief interface with external parameters (accessible for model calibration)
		 */
		parameterInterface *parInt;

		/**
		 * @brief model schematisation
		 */
		schematisation *schema;

		/**
		 * @brief objective function for MPC or model calibration
		 */
		objectiveFunction *obj;

		/**
		 * @brief scenario tree for MPC
		 */
		scenarioTreeInterface *treeInt;

		/**
		 * @brief array with simulator objects for each ensemble
		 */
		rtcToolsSimulator *rtcSim;

        vector<rtcToolsOptimizer*> optimizers;

		// period
		// number of milliseconds after January 1, 1970
		long long t1, t2;
		long long T0;
		long long dt;

		int nEnsemble;
		vector<int> ensembleMap;
		vector<double> pVec;

		// timing statistics
		clock_t cpu1;
		clock_t cpu2;

		//
		double J;
		bool executeObjectiveFunction;
		bool executeConstraints;

		rtcRuntimeConfigSettings runtimeSettings;

		//vector<double> getPVec();
		static void assertFile(string filename);
		long long getTime(fews::DateTimeComplexType::DateType date, fews::DateTimeComplexType::TimeType time);
		long long getTimeStep(fews::TimeStepComplexType::UnitType unit);

	public:

		static int main(int argc, const char * const argv[], versionInfo info = versionInfo());

		/**
		 * @brief Constructor
		 *
 		 * @param schemaDir Optional definition of directory with XSD schemas
 		 * @param workDir Optional definition of working directory 
 		 * @param isProprietary Flag for printing the open source or proprietary header
		 */
        rtcToolsRuntime(const char schemaDir[] = 0, const char workDir[] = 0, versionInfo info = versionInfo());

		/**
		 * @brief Destructor
		 */
		~rtcToolsRuntime(void);

		/**
		 * @brief Function for parsing the rtcRuntimeConfig.xml
		 */
		void parseRuntimeConfigFile();

		/**
		 * @brief Function for parsing all files the rtcRuntimeConfig.xml refers to
		 */
        void parseInputFiles();

		rtcRuntimeConfigSettings::modeInfoContainer getModeInfo(fews::RtcRuntimeConfigComplexType::ModeType m);

		/**
		 * @brief Sets externalized parameters
		 *
 		 * @param n Number of external inputs
		 * @param id Array with ids of external parameters
		 * @param inputArray Array with values of external parameters
		 */
		void setParameters(int n, string* id, double* inputArray);

		/**
		 * @brief Gets the optimization variables
		 *
 		 * @param n Array length
		 * @param x Array of optimization variables
		 */
		void getInput(int n, double *x);

		/**
		 * @brief Sets the optimization variables
		 *
 		 * @param n Array length
		 * @param x Array of optimization variables
		 */
		void setInput(int n, const double *x);


		/**
		 * @brief Gets number of dimensions of the optimization problem in DA mode.
		 */
		int getN_DA() { return parInt->getNDblParameter(); };


		/**
		 * @brief Gets number of dimensions of the optimization problem.
		 */
		int getN();

		void getN_metadata(vector<metadata> &var_md);
		void getM_metadata(vector<metadata> &var_md);


		/**
		 * @brief Gets number of constraints of the optimization problem.
		 */
		int getM();


		/**
		 * @brief Gets number of non-zero elements in the constraint Jacobian
		 */
		int getNNZ();


		/**
		 * @brief Returns the bounds of optimization variables and constraints
		 * 
		 * @param n Number of optimization variables (input)
		 * @param x_l Array with lower bounds of the optimization variables (output)
		 * @param x_u Array with upper bounds of the optimization variables (output)
		 * @param m Number of constraints (input)
		 * @param g_l Array with lower bounds of the constraints (output)
		 * @param g_u Array with upper bounds of the constraints (output)
		 */
		void get_bounds_info(int n, double* x_l, double* x_u, int m, double* g_l, double* g_u);


		/**
		 * @brief Evaluates the constraints
		 * 
		 * @param n Number of optimization variables (input)
		 * @param x Array of optimization variables (input)
		 * @param m Number of constraints (input)
		 * @param g Values of constraints (output)
		 **/
		void eval_g(int n, const double* x, int m, double* g);


		/**
		 * @brief Evaluates the constraint Jacobian
		 * 
		 * @param m Number of constraints (input)
		 * @param nnz Number of non-zero elements in constraint Jacobian (input)
		 * @param iRow Row indices of non-zero elements (output)
		 * @param iCol Column indices of non-zero elements (output)
		 * @param values Values of non-zero elements
		 **/
		void eval_jac_g(int m, int nnz, int* iRow, int* jCol, double* values);


		/**
		 * @brief Gets the number of simulation time steps
		 **/
		int getNTimeStep() { return tsModel->getTimeSeriesTensor()->getNTimeStep(); };


		/**
		 * @brief Gets the number of time series
		 **/
		int getNSeries() { return tsModel->getTimeSeriesTensor()->getNSeries(); };


		/**
		 * @brief Performs a complete simulation
		 */
		double simulate(void);


		/**
		 * @brief Performs a simulation with a single time step
		 *
		 * @param i Time step index.
		 */
		void simulate(int i);


   		/**
		 * @brief Performs a complete simulation under consideration of the external input x
		 *
		 * @param n Number of external inputs
		 * @param x Array with external input data.
		 */
		double simulate(int n, const double *x, int iTerm = -1, double*** JInc3DArray = (double***)0);


		/**
		 * @brief Evaluates and outputs the objective function gradient
		 *
		 * @param n Length of the gradient vector
		 * @param dJ Array with gradient vector elements
		 */
		void eval_grad_f(int n, double *grad_f, int iTerm = -1);

		double eval_grad_f_num(int n, double *x, double EPS, int iX, int iTerm = -1);
		bool check_grad_f_ridders(int n, double *x, double EPS, double TOL);
		bool check_grad_f_central(int n, double *x, double EPS, double TOL);

        /**
         * @brief Opens the output files before the computation starts
         */
        void openOutput();


        /**
         * @brief Writes the output for the current (or final) timestep
		 *
		 * @param timeStep Time step
		 * @param isFinalTimeStep Flag for the final time step for closing the files
         */
        void writeOutput(int timeStep, bool isFinalTimeStep);

		void reportMetaData();
		void reportObjectives();
		void reportConstraints();

		/**
		 * @brief Saves data, frees memory etc.
		 */
		void finish(int timeStep = 0);


		/**
		 * @brief Gets time series model
		 */
		timeSeriesModel* getTimeSeriesModel() { return this->tsModel; };


		/**
		 * @brief Gets the objective function class
		 */
		objectiveFunction* getObjectiveFunction() { return this->obj; };


		/**
		 * @brief Gets the runtime settings class
		 */
		rtcRuntimeConfigSettings* getRuntimeSettings() { return &this->runtimeSettings; };


		/**
		 * @brief Gets a time series matrix from the time series model
		 * 
		 * @param nTimeStep Number of time steps
		 * @param nSeries Number of time series
		 * @param tsm One-dimensional array for storing the time series matrix according to Matlab conventions
		 */
		void getTimeSeriesMatrix(int nTimeStep, int nSeries, double *tsm);


		/**
		 * @brief Puts an external time series matrix into the time series model
		 * 
		 * @param nTimeStep Number of time steps
		 * @param nSeries Number of time series
		 * @param tsm One-dimensional array with the time series matrix according to Matlab conventions
		 */
		void setTimeSeriesMatrix(int nTimeStep, int nSeries, double *tsm);


		/**
		 * @brief Info if the diagnostics writer is in debug mode
		 */
		bool showDebug();
	
		int getIStart() { return iStart; }
		int getIEnd() { return iEnd; }

		void setPeriod(simPeriodEnum period);
		void setExecuteObjectiveFunction(bool flag) { this->executeObjectiveFunction = flag; };

		/**
		 * @brief Top-level call to perform algorithm according to settings
		 */
        bool execute(int timeStep = 0);

		double getJ() { return J; };
};

}
#endif //RTCTOOLSRUNTIME_H
