#ifndef UIBUILD
#define UIBUILD

#include <itpp/itbase.h>
#include "stat/libBM.h"
#include "libconfig/libconfig.h++"

namespace bdm {

using namespace libconfig;
using namespace std;

#define CHECK_UITYPE(S,Type) it_assert_debug(S.getType()==Setting::Type, string("Wrong input path \"")+string(S.getPath())+string("\""));
#define UIREGISTER(UI) UI* UI##_global_instance = new UI();

//!Standard catches for UIs, use as: catch UICATCH
#define UICATCH  ( SettingTypeException e ) {it_error ( "Setting " +string ( e.getPath() ) +" is of incorrect Type" );}	catch ( SettingNotFoundException e ) {it_error ( "Setting " + string ( e.getPath() ) +" was not found" );}

////////// GLOBAL VAriables

class UIbuilder;
//! Internal structure mapping strings to UIBuilder objects
typedef map<const string, const UIbuilder*> UImap;
extern UImap __uimap__;

class UIFile : public Config {
public:
	UIFile ( const char * fname ) :Config() {
		try{Config::readFile ( fname );}
		catch ( FileIOException f ) {it_error ( "File " + string ( fname ) + " not found" );}
		catch ( ParseException& P ) {
			char msg[200];
			sprintf ( msg,"Error in file %s  on line %d.", fname, P.getLine() );
			it_error ( msg );
		}
		setAutoConvert(true);
	}
};

//! \name elem Elementary build functions
//!@{

//! construct itpp::vec from Setting of type Array
inline vec getvec ( Setting& S ) {
	vec vector;
	if (S.getType() == Setting::TypeArray) {	
		vector.set_size ( S.getLength() );
		for ( int i=0;i<S.getLength();i++ ) {
			switch ( S[i].getType() ) {
				case Setting::TypeFloat :
					vector[i]=double ( S[i] );break;
				case Setting::TypeInt :
					vector[i]=int ( S[i] );break;
				case Setting::TypeBoolean :
					vector[i]=bool ( S[i] );break;
				default: it_error ( "libconfig error?" );
			}
		}
	} else if (S.getType() == Setting::TypeGroup) {
		vector = getvec(S["elements"]);
		int cols = S["cols"];
		if (vector.length() != cols) { 
			it_error("requested vector is a matrix");
		}
	} else {
		it_error("requested vector has invalid type");
	}
	return vector;
};

//! construct itpp::mat from Setting of type Array, number of columns must be given
inline mat getmat ( Setting& S , int ncols ) {
	CHECK_UITYPE ( S,TypeArray );
	mat tmp;
	int nrows=S.getLength() /ncols;
	int r=0,c=0;
	tmp.set_size ( nrows, ncols );
	// Build matrix row-wise
	for ( int i=0;i<S.getLength();i++ ) {
		switch ( S[i].getType() ) {
			case Setting::TypeFloat :
				tmp ( r,c ) =double ( S[i] );break;
			case Setting::TypeInt :
				tmp ( r,c ) =int ( S[i] );break;
			case Setting::TypeBoolean :
				tmp ( r,c ) =bool ( S[i] );break;
			default: it_error ( "libconfig error?" );
		}
		c++; if ( c==ncols ) {c=0;r++;}
	}
	return tmp;
};

//! construct itpp::mat from Setting of type Group containing settings
//! "elements" and "cols"
inline mat getmat (Setting & S) {
	CHECK_UITYPE (S, TypeGroup);
	return getmat(S["elements"], S["cols"]);
}

//! construct itpp::ivec from Setting of type Array

inline ivec getivec ( Setting& S ) {
	CHECK_UITYPE ( S,TypeArray );
	ivec tmp;
	tmp.set_size ( S.getLength() );
	for ( int i=0;i<S.getLength();i++ ) {
		switch ( S[i].getType() ) {
			case Setting::TypeFloat :
				tmp[i]=double ( S[i] );break;
			case Setting::TypeInt :
				tmp[i]=int ( S[i] );break;
			case Setting::TypeBoolean :
				tmp[i]=bool ( S[i] );break;
			default: it_error ( "libconfig error?" );
		}
	}
	return tmp;
};

//! construct itpp::Array<string> from Setting of type Array

inline Array<string> get_as ( Setting& S ) {
	//CHECK_UITYPE ( S,TypeArray );
	Array<string> tmp;
	tmp.set_size ( S.getLength() );
	for ( int i=0;i<S.getLength();i++ ) {tmp ( i ) = ( const char* ) S[i];}
	return tmp;
};

//!@}

/*!\brief Builds computational object from a UserInfo structure

Return value is a pointer to the created object (memory management issue?)
*/
class UIbuilder {
protected:
public:
	//!Constructor needs to be run only once macro UIREGISTER
	UIbuilder ( const string &typ ) {__uimap__.insert ( make_pair ( typ,this ) );}
	//! Function building the computational object
	virtual bdmroot* build ( Setting &S ) const =0;
	//! Virtual destructor for future use;
	virtual ~UIbuilder(){};
};

/*! Recursive build of objects defined in external file

\code 
{type="external";
filename="my_file.cfg";       // name of file from which to read
path="system.profile.[0]";    // Path in the external file
};
\endcode
*/
class UIexternal:public UIbuilder {
public:
	UIexternal() :UIbuilder ( "external" ) {}
	bdmroot* build ( Setting &S ) const;
};

/*! Recursive build of objects defined in the same file

\code 
{type="internal";
path="system.profile.[0]";    // Path from the root 
};
\endcode
 */
class UIinternal:public UIbuilder {
public:
	UIinternal() :UIbuilder ( "internal" ) {}
	bdmroot* build ( Setting &S ) const;
};

//! [Debugging] Print values in current S to cout
void UI_DBG ( Setting &S, const string &spc );

//! Prototype of a UI builder. Return value is by the second argument since it type checking via \c dynamic_cast.
template<class T>
void UIbuild ( Setting &S, T* &ret ) {
	CHECK_UITYPE ( S,TypeGroup );
	// Check if field "type" is present, if not it is not a valid UI
	it_assert_debug ( S.exists ( "type" ), string ( S.getPath() ) +" is not a valid UI!" );

	const string typ=S["type"];
	// Find "type" in list of registred UI builders
	UImap::const_iterator iter = __uimap__.find ( typ );
	if ( iter == __uimap__.end() ) {
		it_error ( "UI of type \"" + typ + "\" is not registered!" );
	}

	//BUILD the result
	try {
		ret = dynamic_cast<T*> ( iter->second->build ( S ) );
	}
	catch UICATCH
};

//! Auxiliary function allowing recursivity in S (too complex, remove?)
template<class T>
void UIcall ( Setting &S, void ( *func ) ( Setting&, T ), T Tmp ) {
	CHECK_UITYPE ( S,TypeGroup );
	// Check if field "type" is present, if not it is not a valid UI
	it_assert_debug ( S.exists ( "type" ), string ( S.getPath() ) +" is not a valid UI!" );

	const string typ=S["type"];
	if ( typ=="internal" ) {
		try {
			Setting* Stmp = &S;
			do {Stmp=& ( Stmp->getParent() );}
			while ( !Stmp->isRoot() );
			Setting& intS=Stmp->lookup ( ( const char* ) S["path"] );
			func ( intS, Tmp ); // <======== calling func
			return;
		}
		catch ( ... ) {
			it_error ( "Internal field " + string ( S.getPath() ) + " not valid" );
		}
	}
	if ( typ=="external" ) {
		UIFile C ( S["filename"] );
		try {
			func ( C.lookup ( ( const char* ) S["path"] ), Tmp );
		}
		catch ( ... ) {
			it_error ( "External field " + string ( S.getPath() ) + " not valid" );
		}
		return;
	}

	// v======================= calling final func
	func ( S, Tmp );
};

}
#endif //UIBUILD
