//
// C++ Implementation: itpp_ext
//
// Description:
//
//
// Author: smidl <smidl@utia.cas.cz>, (C) 2008
//
// Copyright: See COPYING file that comes with this distribution
//
//

#include "user_info.h"

namespace bdm
{
///////////////////////// UI FILE /////////////////////////////////////////////

UI_File::UI_File ()
{
    setAutoConvert( true );
}

//! loads root element from a file
UI_File::UI_File ( const string &file_name )
{
    try
    {
        readFile( file_name.c_str()  );
	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() );
    }
}


//! save UserInfo to the file (typically with an XML extension)
void UI_File::save(  const string &file_name )
{
    try
    {
        writeFile ( file_name.c_str()  );
    }
    catch ( FileIOException f )
    {
        it_error( "UI error: file " + file_name + " is inacessible." );
    }
}

UI_File::operator Setting&()
{
    return getRoot();
}
///////////////////////// INTERNAL MAPPED_UI /////////////////////////////////////////////

UI::Mapped_UI::String_To_UI_Map& UI::Mapped_UI::mapped_strings()
{
    static String_To_UI_Map var;
    return var;
}

UI::Mapped_UI::Type_Info_To_String_Map& UI::Mapped_UI::mapped_type_infos()
{
    static Type_Info_To_String_Map var;
    return var;
}

void UI::Mapped_UI::add_class( const string &class_name, const type_info * const class_type_info, const UI* const ui )
{
    pair< const string, const UI* const > new_pair = make_pair( class_name, ui );
    mapped_strings().insert( new_pair );
    mapped_type_infos().insert( make_pair( class_type_info, new_pair.first ) );
}

const UI& UI::Mapped_UI::retrieve_ui( const string &class_name )
{
    String_To_UI_Map::const_iterator iter = mapped_strings().find( class_name );
    if ( iter == mapped_strings().end())
		// TODO dat sem vypis seznamu registrovanych trid
        it_error ( "UI error: class " + class_name + " was not properly registered. Use the macro ""UIREGISTER([class name]);"" within your code." );
    return *iter->second;
}

const string& UI::Mapped_UI::retrieve_class_name( const type_info * const class_type_info )
{
    Type_Info_To_String_Map::const_iterator iter = mapped_type_infos().find( class_type_info );
    if ( iter == mapped_type_infos().end())
        it_error ( "UI error: class with RTTI name " + string(class_type_info->name()) + " was not properly registered. Use the macro ""UIREGISTER([class name]);"" within your code." );
    return iter->second;
}

///////////////////////// INTERNAL LINK EXPANDER /////////////////////////////////////////////

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

const Setting& UI::SettingResolver::initialize_reference(UI_File *&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 UI_File( file_name );
        result = &(Setting&)(*file);
        link = link.substr( 0, aerobase );
    }
    else
	{
		result = &potential_link;
        while ( !result->isRoot() )
            result = &result->getParent();
	}

    if ( !result->exists( link ) )
        ui_error( "linked Setting was not found", potential_link );

    return (*result)[link];
}

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

///////////////////////// UI /////////////////////////////////////////////

void UI::ui_error( string message, const Setting &element )
{
    stringstream error_message;
    error_message << "UI error: " << message << "! Check path """ << element.getPath() << """, source line " << element.getSourceLine() << ".";
    it_error ( error_message.str() );
}

//! This methods - kvuli ukladani pole stringu, dat jen privatne?
void UI::save( const string &str, Setting &element )
{
    Setting &set = element.add( 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);
        }
}


//! This methods tries to save a integer vector
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);
    }
}


//! This methods tries to save a double vector
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);
    }
}


//! This methods tries to build a new double matrix

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() )
    {
        if ( link.result.getLength() != 3 )
            ui_error( "the setting supposed to represent a matrix element has wrong syntax", link.result );

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

        ASSERT_UITYPE(cols_setting,TypeInt);
        ASSERT_UITYPE(rows_setting,TypeInt);
        ASSERT_UITYPE(elements,TypeArray);

        int cols = cols_setting;
        int rows = rows_setting;

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

        if ( elements.getLength() != cols * rows )
            ui_error( "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() )
            ui_error( "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;
    }

    ui_error( "only numeric types or TypeList are supported as matrix values", link.result );
}

//! This methods tries to build a new integer vector
void UI::from_setting( ivec &vector, const Setting &element )
{
    const SettingResolver link( element );

    if ( link.result.isNumber() )
    {
        ASSERT_UITYPE(link.result,TypeInt);
        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)
			ui_error( "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;

		Setting &elements = link.result[2];
        ASSERT_UITYPE(elements[0],TypeInt);


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

    }

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

        ASSERT_UITYPE(link.result[0],TypeInt);
        for ( int i=0; i < len; i++ )
            vector(i) = link.result[i];
        return;
    }

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

//! This methods tries to build a new double vector
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)
			ui_error( "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,1);
		else
			for ( int i=0; i<len; i++ )
				vector(i) = matrix(1,i);
        return;
    }

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

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

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

		return;
    }

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


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


///////////////////////// UI FILE /////////////////////////////////////////////
//! This methods tries to save an instance of type T (or some of its descendant types)
//! and build DOM tree accordingly. Then, it creates a new DOMNode named according class_name
//! and connecti it to the passed Setting as a new child node.
const Setting& UI::to_child_setting( const Setting &element, const int index )
{
    if ( !element.isList())
        ui_error( "only TypeList elements could be indexed by integers", element );

    if ( element.getLength() <= index )
        ui_error( "there is not any child with index " + index, element );

    return element[index];
}

const Setting& UI::to_child_setting( const Setting &element, const string &name )
{
    ASSERT_UITYPE(element,TypeGroup);
    if ( !element.exists( name ) )
        ui_error( "there is not any child named """ + name, element );
    return element[name];
}


}

