#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() );
	}
}
