//
// C++ Implementation: user_info.cpp
//
// Description: UI (user info) class for loading/saving objects from/to configuration files.
//
//
// Author: smidl <smidl@utia.cas.cz>, (C) 2009
//
// Copyright: See COPYING file that comes with this distribution
//
//

#include "user_info.h"

namespace bdm {

string UIException::format_message( const string &reason, const string &path ) {
	stringstream ss;
	ss << reason;
	ss << " Check path \"" << path << "\".";
	return ss.str();
}

///////////////////////////// Class UIFile /////////////////////////////////////////////

UIFile::UIFile() {
}

UIFile::UIFile ( const string &file_name ) {
	try {
		readFile ( file_name.c_str() );
		// this flag has to be set AFTER each file load, that is why it is right here
		setAutoConvert ( true );
	} catch ( FileIOException f ) {
		it_error ( "UI error: file " + file_name + " not found." );
	} catch ( ParseException& P ) {
		stringstream msg;
		msg << "UI error: parsing error """ << P.getError() << """ in file " << file_name << " on line " <<  P.getLine() << ".";
		it_error ( msg.str() );
	}
}


void UIFile::save ( const string &file_name ) {
	try {
		writeFile ( file_name.c_str() );
	} catch ( FileIOException f ) {
		it_error ( "UI error: file " + file_name + " is inacessible." );
	}
}

UIFile::operator Setting&() {
	return getRoot();
}

///////////////////////////// Class UI::MappedUI /////////////////////////////////////////////

UI::MappedUI::StringToUIMap& UI::MappedUI::mapped_strings() {
	// this way it is ensured that there is only one instance of StringTpUIMap, and
	// what is more, this declaration leaves its allocation/deallocation on the compiler
	static StringToUIMap var;
	return var;
}

UI::MappedUI::TypeInfoToStringMap& UI::MappedUI::mapped_type_infos() {
	// this way it is ensured that there is only one instance of TypeInfoToStringMap, and
	// what is more, this declaration leaves its allocation/deallocation on the compiler
	static TypeInfoToStringMap var;
	return var;
}

void UI::MappedUI::add_class ( const string &class_name, const type_info * const class_type_info, const UI* const ui ) {
	pair< StringToUIMap::iterator, bool> inres =
		mapped_strings().insert (
			StringToUIMap::value_type( class_name, ui ) );
	if ( inres.second ) {
		mapped_type_infos().insert (
			TypeInfoToStringMap::value_type (
				class_type_info, class_name ) );
	}
}

void UI::MappedUI::unregistered_class_error ( const string &unregistered_class_name ) {
	stringstream msg;
	msg << "UI error: class " + unregistered_class_name + " was not properly registered. Use the macro ""UIREGISTER([class name]);"" within your code." << endl;

	if ( mapped_strings().size() ) {
		StringToUIMap::const_iterator iter = mapped_strings().begin();
		msg << "These classes are already registered: " << iter->first;
		for ( iter++; iter != mapped_strings().end(); iter++ )
			msg << ", " << iter->first;
		msg << "." << endl;
	} else
		msg << "There is not any registered class yet!" << endl;

	it_error ( msg.str() );
}

const UI& UI::MappedUI::retrieve_ui ( const string &class_name ) {
	StringToUIMap::const_iterator iter = mapped_strings().find ( class_name );
	if ( iter == mapped_strings().end() )
		unregistered_class_error ( class_name );

	return *iter->second;
}

const string& UI::MappedUI::retrieve_class_name ( const type_info * const class_type_info ) {
	TypeInfoToStringMap::const_iterator iter = mapped_type_infos().find ( class_type_info );
	if ( iter == mapped_type_infos().end() )
		unregistered_class_error ( "with RTTI name " + string ( class_type_info->name() ) );
	return iter->second;
}

///////////////////////////// Class SettingResolver /////////////////////////////////////////////

SettingResolver::SettingResolver ( const Setting &potential_link )
		: result ( initialize_reference ( file, potential_link ) ) {
}

const Setting& SettingResolver::initialize_reference ( UIFile *&file, const Setting &potential_link ) {
	file = NULL;

	if ( potential_link.getType() !=  Setting::TypeString )
		return potential_link;

	string link = ( const char* ) potential_link;
	size_t aerobase = link.find ( '@' );

	const Setting *result;
	if ( aerobase != string::npos ) {
		string file_name = link.substr ( aerobase + 1, link.length() );
		file = new UIFile ( file_name );
		result = & ( Setting& ) ( *file );
		link = link.substr ( 0, aerobase );
	} else {
		result = &potential_link;
		while ( !result->isRoot() )
			result = &result->getParent();
	}

	if ( !result->exists ( link ) )
		throw UISettingException ( "UIException: linked setting was not found.", string ( ( const char* ) potential_link ) );

	return ( *result ) [link];
}

SettingResolver::~SettingResolver() {
	if ( file ) delete file;
}

///////////////////////////// Class UI /////////////////////////////////////////////

void UI::assert_type ( const Setting &element, Setting::Type type ) {
	if ( element.getType() != type )
		throw UISettingException ( "UIException: wrong setting type.", element );
}

const Setting& UI::to_child_setting ( const Setting &element, const int index ) {
	if ( !element.isList() )
		throw UISettingException ( "UIException: only TypeList elements could be indexed by integers.", element );

	return element[index];
}

const Setting& UI::to_child_setting ( const Setting &element, const string &name ) {
	if ( !element.isGroup() )
		throw UISettingException ( "UIException: only TypeGroup elements could be indexed by strings.", element );

	return element[name];
}

void UI::save ( const int &integer, Setting &element, const string &name ) {
	Setting &set = ( name == "" ) ? element.add ( Setting::TypeInt )
	               : element.add ( name, Setting::TypeInt );
	set = integer;
}

void UI::save ( const double &real, Setting &element, const string &name ) {
	Setting &set = ( name == "" ) ? element.add ( Setting::TypeFloat )
	               : element.add ( name, Setting::TypeFloat );
	set = real;
}

void UI::save ( const string &str, Setting &element, const string &name ) {
	Setting &set = ( name == "" ) ? element.add ( Setting::TypeString )
	               : element.add ( name, Setting::TypeString );
	set = str;
}

void UI::save ( const mat &matrix, Setting &element, const string &name ) {
	Setting &set = ( name == "" ) ? element.add ( Setting::TypeList )
	               : element.add ( name, Setting::TypeList );

	Setting &cols = set.add ( Setting::TypeInt );
	cols = matrix.cols();

	Setting &rows = set.add ( Setting::TypeInt );
	rows = matrix.rows();

	Setting &elements = set.add ( Setting::TypeArray );

	// build matrix row-wise
	for ( int i = 0; i < matrix.rows(); i++ )
		for ( int j = 0; j < matrix.cols(); j++ ) {
			Setting &new_field = elements.add ( Setting::TypeFloat );
			new_field = matrix ( i, j );
		}
}

void UI::save ( const ivec &vector, Setting &element, const string &name ) {
	Setting &set = ( name == "" ) ? element.add ( Setting::TypeArray )
	               : element.add ( name, Setting::TypeArray );
	for ( int i = 0; i < vector.length(); i++ ) {
		Setting &new_field = set.add ( Setting::TypeInt );
		new_field = vector ( i );
	}
}

void UI::save ( const vec &vector, Setting &element, const string &name ) {
	Setting &set = ( name == "" ) ? element.add ( Setting::TypeArray )
	               : element.add ( name, Setting::TypeArray );
	for ( int i = 0; i < vector.length(); i++ ) {
		Setting &new_field = set.add ( Setting::TypeFloat );
		new_field = vector ( i );
	}
}

void UI::from_setting ( mat& matrix, const Setting &element ) {
	const SettingResolver link ( element );

	if ( link.result.isNumber() ) {
		matrix.set_size ( 1, 1 );
		matrix ( 0, 0 ) = link.result;
		return;
	}

	if ( link.result.isList() ) {
		int data_offset;

		if ( link.result.getLength() == 3 )
			data_offset = 0;
		else if ( link.result.getLength() == 4 ) {
			assert_type ( link.result[0], Setting::TypeString );
			const char* elem1 = ( const char* ) link.result[0];
			if ( ( strcmp ( elem1, "matrix" ) ) )
				throw UISettingException ( "UIException: the setting supposed to represent a matrix element has wrong syntax.", link.result );

			data_offset = 1;
		} else
			throw UISettingException ( "UIException: the setting supposed to represent a matrix element has wrong syntax.", link.result );

		Setting &rows_setting = link.result[0 + data_offset];
		Setting &cols_setting = link.result[1 + data_offset];
		Setting &elements = link.result[2 + data_offset];

		assert_type ( cols_setting, Setting::TypeInt );
		assert_type ( rows_setting, Setting::TypeInt );
		assert_type ( elements, Setting::TypeArray );

		int cols = cols_setting;
		int rows = rows_setting;

		if ( cols < 0 || rows < 0 )
			throw UISettingException ( "UIException: the dimensions of a matrix has to be non-negative.", link.result );

		if ( elements.getLength() != cols * rows )
			throw UISettingException ( "UIException: the count of the matrix elements is incompatible with matrix dimension.", elements );

		matrix.set_size ( rows, cols );

		if ( cols == 0 || rows == 0 )
			return;

		if ( !elements[0].isNumber() )
			throw UISettingException ( "UIException: matrix elements have to be numbers.", elements[0] );

		// build matrix row-wise
		int k = 0;
		for ( int i = 0; i < rows; i++ )
			for ( int j = 0; j < cols; j++ )
				matrix ( i, j ) = elements[k++];
		return;
	}

	throw UISettingException ( "UIException: only numeric types or TypeList are supported as matrix values.", link.result );
}

void UI::from_setting ( vec &vector, const Setting &element ) {
	const SettingResolver link ( element );

	if ( link.result.isNumber() ) {
		vector.set_length ( 1 );
		vector ( 0 ) = link.result;
		return;
	}

	if ( link.result.isList() ) {
		mat matrix;
		from_setting ( matrix, link.result );

		if ( matrix.cols() != 1 && matrix.rows() != 1 )
			throw UISettingException ( "UIException: the vector length is invalid, it seems to be rather a matrix.", link.result );

		int len = matrix.rows() * matrix.cols();
		vector.set_length ( len );
		if ( len == 0 ) return;

		if ( matrix.cols() == 1 )
			for ( int i = 0; i < len; i++ )
				vector ( i ) = matrix ( i, 0 );
		else
			for ( int i = 0; i < len; i++ )
				vector ( i ) = matrix ( 0, i );
		return;
	}

	if ( link.result.isArray() ) {
		int len = link.result.getLength();
		vector.set_length ( len );
		if ( len == 0 ) return;

		if ( !link.result[0].isNumber() )
			throw UISettingException ( "UIException: a vector element has to be a number.", link.result[0] );

		for ( int i = 0; i < len; i++ )
			vector ( i ) = link.result[i];

		return;
	}

	throw UISettingException ( "UIException: only numeric types, TypeArray or TypeList are supported as vector values.", link.result );
}

void UI::from_setting ( ivec &vector, const Setting &element ) {
	vec double_vector;
	from_setting ( double_vector, element );
	int len = double_vector.length();
	vector.set_length ( len );
	for ( int i = 0; i < len; i++ )
		vector ( i ) = ( int ) double_vector ( i );
}

void UI::from_setting ( string &str, const Setting &element ) {
	assert_type ( element, Setting::TypeString );
	str = ( const char* ) element;
}

void UI::from_setting ( int &integer, const Setting &element ) {
//	assert_type ( element, Setting::TypeInt );
	integer = element;
}

void UI::from_setting ( double &real, const Setting &element ) {
//	assert_type ( element, Setting::TypeFloat );
	real = element;
}

}
