#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 UImxConfig : public Config {
public:
	mxArray *mxconfig;
	UImxConfig(const char * filename) {
		Config config;
		config.readFile(filename);
		mxconfig = group2mxstruct(config.getRoot());
	}
	UImxConfig(const Setting &setting) {
		mxconfig = group2mxstruct(setting);
	}

private:

	mxArray* array2mxvector(const Setting &setting)  {
		if (!setting.isArray()) mexErrMsgTxt("Given setting is not an array");
		double *elements = new double[setting.getLength()];
		for (int i=0; i<setting.getLength(); i++) {
			if (setting.getType() == Setting::TypeInt) { //TODO: tady je chyba -- zaporna cisla nejsou TypeInt
				elements[i] = setting[i];
			} else {
				elements[i] = (int) setting[i];
			}
		}
		vec &v = *(new vec(elements, setting.getLength()));
		mxArray *result = mxCreateDoubleMatrix(1, setting.getLength(), mxREAL);
		vec2mxArray(v, result);
		delete &v;
		delete [] elements;
		return result;
	}

	mxArray* list2mxmatrix(const Setting &setting)  {
		if (!setting.isList() || ("matrix" != setting[0]))
			mexErrMsgTxt("Given setting is not a matrix");
		int rows = setting[1];
		int cols = setting[2];
		if (setting[3].getLength() != rows*cols)
			mexErrMsgTxt("Matrix elements do not fit to rows*cols");
		double *elements = new double[rows*cols];
		for (int i=0; i<rows*cols; i++) {
			elements[i] = setting[3][i];
		}
		mat &m = *(new mat(elements, rows, cols));
		mxArray *result = mxCreateDoubleMatrix(rows, cols, mxREAL);
		mat2mxArray(m, result);
		delete &m;
		delete [] elements;
		return result;
	}

	mxArray* group2mxstruct(const Setting &setting) {
		if (!setting.isGroup()) mexErrMsgTxt("Given setting is not a group.");
		const char ** keys = new const char*[setting.getLength()];
		for (int i=0; i < setting.getLength(); i++) {
			keys[i] = setting[i].getName();
		}
		mxArray *result = mxCreateStructMatrix(1, 1, setting.getLength(), keys);
		delete keys;
		for (int i=0; i < setting.getLength(); i++) {
			Setting &value = setting[i];
			mxArray *old = mxGetFieldByNumber(result, 0, i);
			if (old) mxDestroyArray(old);
			switch (value.getType()) {
			case Setting::TypeString:
				mxSetFieldByNumber(result, 0, i, mxCreateString(value));
				break;
			case Setting::TypeBoolean:
				mxSetFieldByNumber(result, 0, i, mxCreateLogicalScalar(value));
				break;
			case Setting::TypeGroup:
				mxSetFieldByNumber(result, 0, i, group2mxstruct(value));
				break;
			case Setting::TypeList:
				mxSetFieldByNumber(result, 0, i, list2mxcell(value));
				break;
			case Setting::TypeArray:
				mxSetFieldByNumber(result, 0, i, array2mxvector(value));
				break;
			case Setting::TypeInt:
			case Setting::TypeInt64:
				mxSetFieldByNumber(result, 0, i, mxCreateDoubleScalar((int) value));
				break;
			case Setting::TypeFloat:
				mxSetFieldByNumber(result, 0, i, mxCreateDoubleScalar(value));
				break;
			default:
				//this should never happen
				mexErrMsgTxt("Unknown type of a setting.");
			}
		}
		return result;

	}
	
	mxArray* list2mxcell(const Setting &setting)  {
		if (!setting.isList()) mexErrMsgTxt("Given setting is not a list.");
		if (setting.getLength() == 0) {
			mxArray *result = mxCreateCellMatrix(1, 0);
			return result;
		}
		if ((setting[0].getType() == Setting::TypeString) && ("matrix" == setting[0])) {
			return list2mxmatrix(setting);
		}
		mxArray *result = mxCreateCellMatrix(1, setting.getLength());
		for (int i=0; i < setting.getLength(); i++) {
			Setting &value = setting[i];
			mxArray *old = mxGetCell(result, i);
			if (old) mxDestroyArray(old);
			switch (value.getType()) {
			case Setting::TypeString:
				mxSetCell(result, i, mxCreateString(value));
				break;
			case Setting::TypeBoolean:
				mxSetCell(result, i, mxCreateLogicalScalar(value));
				break;
			case Setting::TypeGroup:
				mxSetCell(result, i, group2mxstruct(value));
				break;
			case Setting::TypeList:
				mxSetCell(result, i, list2mxcell(value));
				break;
			case Setting::TypeArray:
				mxSetCell(result, i, array2mxvector(value));
				break;
			case Setting::TypeInt:
			case Setting::TypeInt64:
				mxSetCell(result, i, mxCreateDoubleScalar((int) value));
				break;
			case Setting::TypeFloat:
				mxSetCell(result, i, mxCreateDoubleScalar(value));
				break;
			default:
				//this should never happen
				mexErrMsgTxt("Unknown type of a setting.");
			}
		}
		return result;
	}
};
