#include "../base/libconfig/libconfig.h++"
#include <itpp/itbase.h>
#include <itpp/itmex.h>
#include <stdlib.h>

using namespace itpp;
using namespace std;
using namespace libconfig;

class UImxArray : public Config {
public:
	UImxArray(const mxArray *mxarray) : Config() {
		Setting & setting = this->getRoot(); //setting is a group
		if (!mxIsStruct(mxarray)) { mexErrMsgTxt("Given mxArray is not a struct."); };
		//mexCallMATLAB(0, NULL, 1, (mxArray **) &mxarray, "dump");
		fillGroup(setting, mxarray);
		setAutoConvert(true);
	}
	UImxArray() : Config() {		
		setAutoConvert(true);
	}	
	void addList(const mxArray *mxarray, const char* name){
		Setting & setting = this->getRoot(); //setting is a group
		Setting & child = setting.add(name,Setting::TypeList);
		fillList(child,mxarray);
	}
	void addGroup(const mxArray *mxarray, const char* name){
		Setting & setting = this->getRoot(); //setting is a group
		Setting & child = setting.add(name,Setting::TypeGroup);
		fillGroup(child,mxarray);
	}
	operator Setting&()
	{
		 return getRoot();
	}
private:
	void storeNumeric(Setting &setting, const mxArray *value, string key = "") {
		//TODO: integer matrices
		if (!mxIsNumeric(value)) { mexErrMsgTxt("Given mxArray is not numeric."); };
		mat val = mxArray2mat(value);
		if ((val.rows() == 1) && (val.cols() == 1)) {
			Setting &child = (key=="") ? setting.add(Setting::TypeFloat)
			                           : setting.add(key, Setting::TypeFloat);
			child = val(0,0);
		} else {
			Setting &child = (key=="") ? setting.add(Setting::TypeList)
			                           : setting.add(key, Setting::TypeList);
			Setting &label = child.add(Setting::TypeString);
			label = "matrix";
			Setting &rows = child.add( Setting::TypeInt );
			Setting &cols = child.add( Setting::TypeInt );
			Setting &elements = child.add( Setting::TypeArray );
			cols = val.cols();
			rows = val.rows();
			for (int i=0; i<val.rows(); i++) {
				for (int j=0; j<val.cols(); j++) {
					Setting &el = elements.add(Setting::TypeFloat);
					el = val(i,j);
				}
			}
		}
	}

	void fillGroup(Setting &setting, const mxArray *mxarray) {
		if (!mxIsStruct(mxarray)) { mexErrMsgTxt("Given mxArray is not a struct."); };
		for(int i=0; i < mxGetNumberOfFields(mxarray); i++) {
			const char *key = mxGetFieldNameByNumber(mxarray, i);
			mxArray *value = mxGetField(mxarray, 0, key);
			if (mxIsChar(value)) {
				Setting &child = setting.add(key, Setting::TypeString);
				child = mxArray2string(value);
			}
			if (mxIsLogical(value)) {
				Setting &child = setting.add(key, Setting::TypeBoolean);
				child = (bool) mxArray2bin(value);
			}
			if (mxIsStruct(value)) {
				Setting &child = setting.add(key, Setting::TypeGroup);
				fillGroup(child, value);
			}
			if (mxIsCell(value)) {
				Setting &child = setting.add(key, Setting::TypeList);
				fillList(child, value);
			}
			if (mxIsNumeric(value)) {
				storeNumeric(setting, value, (string)key);
			}
		}
	}
	
	void fillList(Setting &setting, const mxArray *mxarray) {
		if (!mxIsCell(mxarray)) { mexErrMsgTxt("Given mxArray is not a cell."); };
		for(uint i=0; i < mxGetNumberOfElements(mxarray); i++) {
			mxArray *value = mxGetCell(mxarray, i);
			if (mxIsChar(value)) {
				Setting &child = setting.add(Setting::TypeString);
				child = mxArray2string(value);
			}
			if (mxIsLogical(value)) {
				Setting &child = setting.add(Setting::TypeBoolean);
				child = (bool) mxArray2bin(value);
			}
			if (mxIsStruct(value)) {
				Setting &child = setting.add(Setting::TypeGroup);
				fillGroup(child, value);
			}
			if (mxIsCell(value)) {
				Setting &child = setting.add(Setting::TypeList);
				fillList(child, value);
			}
			if (mxIsNumeric(value)) {
				storeNumeric(setting, value);
			}
		}
	}
};
