#include <string>
#include <vector>
#include <string.h>
#include "base/user_info.h"
#include "../test_util.h"
#include "UnitTest++.h"

using namespace std;
using namespace bdm;

class Passenger : public root {
public:

    Passenger() {
    }
};

class Human : public Passenger {
public:
    string name;

    Human() {
        name = "none";
    }

    virtual void from_setting ( const Setting &set ) {
        UI::get ( name, set, "name" );
    }

    virtual void to_setting ( Setting &set ) const {
        Setting &name_setting = set.add ( "name", Setting::TypeString );
        name_setting = name;
    }

    string to_string()  const {
        return name;
    }
};

UIREGISTER ( Human );

class Robot : public Passenger {
public:
    int number;
    Array<string> software;

    Robot() : software() {
        number = -1;
    }

    virtual void from_setting ( const Setting &set ) {
        UI::get ( number, set, "number" );
        UI::get ( software, set, "software" );
    }

    virtual void to_setting ( Setting &set ) const {
        Setting &number_setting = set.add ( "number", Setting::TypeInt );
        number_setting = number;

        UI::save ( software, set, "software" );
    }

    string to_string()  const {
        stringstream stream;
        stream << number;
        for ( int i = 0; i < software.length(); i++ )
            stream << "_" + software ( i );
        return stream.str();
    }
};

UIREGISTER ( Robot );

class Transport : public root {
public:
    int year;
    string manufacturer;

    Transport() : year ( 1900 ), manufacturer ( "unknown" ) {
    }

    Transport ( int year, string manufacturer ) :
        year ( year ), manufacturer ( manufacturer ) {
    }

    virtual void from_setting ( const Setting &set ) {
        UI::get ( year, set, "year" );
        UI::get ( manufacturer, set, "manufacturer" );
    }

    virtual void to_setting ( Setting &set ) const {
        Setting &year_setting = set.add ( "year", Setting::TypeInt );
        year_setting = year;

        Setting &manufacturer_setting = set.add ( "manufacturer", Setting::TypeString );
        manufacturer_setting = manufacturer;
    }

    virtual string to_string() const
    {
        return "the method to_string() is not implemented";
    }
};

class Car : public Transport {
public:
    int kilometers;
    Array<shared_ptr<Passenger> > passengers;

    Car() : Transport() {
        kilometers = 0;
    }


    Car ( int year, string manufacturer, int kilometers )
        : Transport ( year, manufacturer ), kilometers ( kilometers ) {
    }

    virtual void from_setting ( const Setting &set ) {
        Transport::from_setting ( set );

        UI::get ( kilometers, set, "kilometers" );
        UI::get ( passengers, set, "passengers" );
    }

    virtual void to_setting ( Setting &set ) const {
        Transport::to_setting ( set );

        Setting &kilometers_setting = set.add ( "kilometers", Setting::TypeInt );
        kilometers_setting = kilometers;

        UI::save ( passengers, set, "passengers" );
    }

    string to_string()  const {
        stringstream stream;
        stream << "A car made in " << year << " by " << manufacturer << ", having " << kilometers << " kilometers on the clock.";
        if ( passengers.length() ) {
            stream << "The names of passengers are as follows: ";
            for ( int i = 0; i < passengers.length(); i++ )
                stream << passengers ( i )->to_string() << " ";
        }
        return stream.str();
    }
};

UIREGISTER ( Car );

class Bike : public Transport {
public:
    bool electricLights;
    mat matr;

    Bike() : Transport(), matr ( "2,2;3,4" ) {
        electricLights = false;
    }

    Bike ( int year, string manufacturer, bool electricLights )
        : Transport ( year, manufacturer ), electricLights ( electricLights ) {
    }

    ~Bike() {
    }

    void from_setting ( const Setting &set ) {
        Transport::from_setting ( set );

        int elights;
        if ( UI::get ( elights, set, "electricLights", UI::optional ) ) {
            electricLights = !!elights;
        }

        UI::get ( matr, set, "matr" );
    }

    void to_setting ( Setting &set ) const {
        Transport::to_setting ( set );

        Setting &electricLights_setting = set.add ( "electricLights", Setting::TypeBoolean );
        electricLights_setting = electricLights;

        UI::save ( matr, set, "matr" );
    }

    string to_string()  const {
        stringstream stream;
        stream << "a bike made in " << year << " by " << manufacturer;
        if ( electricLights ) stream << " with electric lights included";
        return stream.str();
    }
};

UIREGISTER ( Bike );

class Teleport : public Transport {
public:
    void to_setting ( Setting &set ) const {
        set.add ( "this throws", Setting::TypeNone );
    }
};

UIREGISTER ( Teleport );

TEST ( load_test ) {
    UIFile in ( "user_info_valid.cfg" );
    shared_ptr<Transport> pepikovo ( UI::build<Transport> ( in, "pepikovo", UI::compulsory ) );
    CHECK_EQUAL ( string ( "A car made in 1998 by audi, having 25000 kilometers on the clock.The names of passengers are as follows: Karlos Novak -1_CygWin_Matlab_Aimsun Karlosik Novacek " ), pepikovo->to_string() );

    Transport pepikovo_static;
    UI::get( pepikovo_static, in, "pepikovo", UI::compulsory );
    CHECK_EQUAL ( string ( "the method to_string() is not implemented" ), pepikovo_static.to_string() );

    shared_ptr<Transport> jardovo ( UI::build<Transport> ( in, "jardovo", UI::compulsory ) );
    CHECK_EQUAL ( string ( "A car made in 1992 by liaz, having 1555000 kilometers on the clock." ), jardovo->to_string() );

    shared_ptr<Transport> ondrejovo ( UI::build<Transport> ( in, "ondrejovo", UI::compulsory ) );
    CHECK_EQUAL ( string ( "a bike made in 1996 by author with electric lights included" ), ondrejovo->to_string() );

    shared_ptr<Transport> elisky ( UI::build<Transport> ( in, "elisky", UI::compulsory ) );
    CHECK_EQUAL ( string ( "A car made in 1992 by liaz, having 1555000 kilometers on the clock." ), elisky->to_string() );

    shared_ptr<Transport> kati ( UI::build<Transport> ( in, "kati", UI::compulsory ) );
    CHECK_EQUAL ( string ( "A car made in 1980 by vecernicek, having 250000 kilometers on the clock." ), kati->to_string() );
}

TEST ( load_error_test ) {
    UIFile in ( "user_info_valid.cfg" );

    try {
        shared_ptr<Robot> failing ( UI::build<Robot> ( in, "elisky", UI::compulsory ) );
        CHECK ( false );
    } catch ( UIClassException &exc ) {
        CHECK ( exc.what() );
    }

    try {
        shared_ptr<Robot> failing ( UI::build<Robot> ( in, "kati", UI::compulsory ) );
        CHECK ( false );
    } catch ( UIException &exc ) {
        CHECK ( exc.what() );
    }

    UIFile erroneous ( "user_info_erroneous.cfg" );
    try {
        shared_ptr<Transport> jardovo ( UI::build<Transport> ( erroneous, "jardovo", UI::compulsory ) );
        CHECK ( false );
    } catch ( UISettingException &exc ) {
        CHECK ( exc.what() );
    }
}

TEST ( save_test ) {
    UIFile in ( "user_info_valid.cfg" );
    shared_ptr<Transport> pepikovo ( UI::build<Transport> ( in, "pepikovo", UI::compulsory ) );

    Car audi ( 1968, "zyl", 200 );
    Car liaz ( 1989, "skoda", 1000 );
    Bike author ( 2001, "noname", false );
    UIFile out;

    UI::save ( &audi, out, "marty" );
    UI::save ( &liaz, out, "bohousovo" );
    UI::save ( &author, out, "karlovo" );
    UI::save ( pepikovo, out, "pepikovo" );
    out.save ( "user_info_output.cfg" );

    string expected ( load_test_file ( "user_info_matrix.cfg" ) );
    string actual ( load_test_file ( "user_info_output.cfg" ) );
    CHECK_EQUAL ( expected, actual );
}


TEST ( save_error_test ) {
    UIFile out;

    Teleport teleport;
    try {
        UI::save ( &teleport, out, "unsaveable" );
        CHECK ( false );
    } catch ( UIException &exc ) {
        CHECK ( exc.what() );
    }
}
