#ifndef MXDS_H
#define MXDS_H


#include "../bdm/bdmerror.h"
#include "../bdm/base/datasources.h"
#include "mex_parser.h"

namespace bdm {
/*!
* \brief Memory storage of off-line data column-wise

The data are stored in an internal matrix \c Data . Each column of Data corresponds to one discrete time observation \f$t\f$. Access to this matrix is via indices \c rowid and \c delays.

The data can be loaded from a file.
*/
class mxArrayDS : public MemDS {
public:
	//!Default constructor
	mxArrayDS () : MemDS() {};

	/*! \brief Create memory data source from mxArray

	\code
	system={
		type="mexDS";
		varname="";                // name of workspace variable
		row_rv = {class='RV',...}  // definition of
		};
	\endcode

	MemDS with the above fields will be created;

	*/
	void from_setting ( const Setting &set ) {
		Data = mxArray2mat ( mexGetVariable ( "base", set["varname"] ) );
		/*		UI::get ( rowid, set, "rids" , UI::compulsory );
				bdm_assert_debug ( max ( rowid ) <= Data.rows(), "MemDS rowid is too high for given Dat." );

				UI::get ( delays, set, "tds", UI::compulsory );
				time = max ( delays );
				bdm_assert_debug ( time < Data.cols(), "MemDS delays are too high." );
		*/
		//set MemDS
		rowid = linspace ( 0, Data.rows() - 1 );
		dtsize = rowid.length();
		utsize = 0;

		shared_ptr<RV> r = UI::build<RV> ( set, "rv", UI::optional );
		RV ru = RV();
		if ( r ) {
			set_drv ( *r, ru );
		} else {
			RV def ( ( const char* ) set["varname"], Data.rows() );
			set_drv ( def, ru );
		}
	}


	// TODO dodelat void to_setting( Setting &set ) const;
};

UIREGISTER ( mxArrayDS );
SHAREDPTR ( mxArrayDS );

/*!
* \brief Matlab wrapper for DS mapping functions step() to a matlab function

The identifier of matlab function is stored in attribute \c name.
This identifier defines:
\li function to call to do a step(): name_step.m
\li workspace variable to write input to: name_input
*/
class mexDS : public DS {
protected:
	//! identifier of matlab function
	string step_name;
	//! identifier of matlab input variabel
	string input_name;
	//! cache of results from name_step
	vec dt;
	//! cache of inputs
	vec ut;
public:
	//!Default constructor
	mexDS () : DS() {};

	/*! \brief Data source computed by matlab function

	\code
	system={
		class="mexDS";
		step_name="";              // name of function to call
		input_name="";             // name of workspace variable where inputs are written
		drv = {class='RV',...}  // identification of outputs
		urv = {class='RV',...}   // identification of inputs
		};
	\endcode

	MemDS with the above fields will be created;

	*/
	void from_setting ( const Setting &set ) {
		UI::get ( step_name, set, "step_name", UI::compulsory );
		UI::get ( input_name, set, "input_name", UI::compulsory );

		shared_ptr<RV> rd = UI::build<RV> ( set, "drv", UI::compulsory );
		shared_ptr<RV> ru = UI::build<RV> ( set, "urv", UI::compulsory );

		dtsize = rd->_dsize();
		utsize = ru->_dsize();

		set_drv ( *rd, *ru );
		validate();
	}

	virtual void getdata ( vec &dt, const ivec &indices ) NOT_IMPLEMENTED_VOID;

	virtual void write ( const vec &ut, const ivec &indices ) NOT_IMPLEMENTED_VOID;


	void step() {
		mxArray* tmp;
		mxArray* dummy = NULL;
		// write inputs to variable input_name
		mxArray* mxinp = mexGetVariable ( "global", input_name.c_str() ) ;
		if ( mxinp ) {
			if ( ( int ) mxGetM ( mxinp ) != utsize || ( int ) mxGetN ( mxinp ) != utsize ) {
				// mxinp is invalid - create new one
				mxDestroyArray ( mxinp );
				mxinp = mxCreateDoubleMatrix ( utsize, 1, mxREAL );
			}

		} else {
			mxinp = mxCreateDoubleMatrix ( utsize, 1, mxREAL );
		}
		vec2mxArray ( ut, mxinp );
		mexPutVariable ( "global", input_name.c_str(), mxinp );
		// call function step_name
		mexCallMATLAB ( 1, &tmp, 0, ( mxArray ** ) &dummy, step_name.c_str() );
		// save its results
		bdm_assert_debug ( ( int ) mxGetM ( tmp ) == dtsize || ( int ) mxGetN ( tmp ) == dtsize, "mexDS.step() expected return vector of length " + num2str ( dtsize ) +
		                   "got vector " + num2str ( ( int ) mxGetM ( tmp ) ) + "x" + num2str ( ( int ) mxGetN ( tmp ) ) );
		//write  y
		dt.set_subvector ( 0, mxArray2vec ( tmp ) );
		//write u
	}
	void write ( const vec &ut0 ) {
		bdm_assert_debug ( ut0.length() == ut.length(), "mexDS: Incompatible input vector" );
		ut = ut0;
	}
	void getdata ( vec &dt_out ) const {
		bdm_assert_debug ( dt_out.length() == dt.length(), "mexDS: Incompatible output vector" );
		dt_out = dt;
	}

	void validate() {
		dt = zeros ( dtsize );
		ut = zeros ( utsize );
	}

	// TODO dodelat void to_setting( Setting &set ) const;
};

UIREGISTER ( mexDS );
SHAREDPTR ( mexDS );

}
#endif //MXDS_H
