/**
 * @file TestTable.cpp
 * @date Jul 14, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx classes Column, Table, and template ColumnT
 */

#include "TestData.h"
#include <exception>

#if defined(_MSC_VER)
#undef min
#undef max
#endif
#include <limits>

#define MIN_VAL(Type) ((Type)std::numeric_limits<Type>::min())
#define MAX_VAL(Type) ((Type)std::numeric_limits<Type>::max())


/// @cond DOXYGEN_SKIP
SUITE( Data )
{
/// @endcond
	typedef ReportTable::Column Column;

	/**
	 * @struct TableFixture
	 * @brief Helper struct providing set-up/tear-down of Table, Column tests
	 *
	 * @copydetails EventFixture
	 */
	struct TableFixture
	{
		ReportTable::Definition tableDefinition; // a TableDefinition containing all column types
		ReportTable::Definition tableDefINTEGER; // a TableDefinition containing integer column types
		ReportTable::Definition tableDefREAL; // a TableDefinition containing real column types
		ReportTable::Definition tableDefSIMTIME; // a TableDefinition containing base::SimTime column types
		ReportTable::Definition tableDefBAR; // a TableDefinition containing graphical representation column types
		ReportTable::Definition tableDefSTRING; // a TableDefinition containing string column types
		ReportTable* table;

		TableFixture()
		:	table( 0 )
		{
			tableDefinition.addColumn( "TableTestColumn INTEGER", Column::INTEGER );
			tableDefinition.addColumn( "TableTestColumn REAL", Column::REAL );
			tableDefinition.addColumn( "TableTestColumn SIMTIME", Column::SIMTIME );
			tableDefinition.addColumn( "TableTestColumn BAR", Column::BAR );
			tableDefinition.addColumn( "TableTestColumn STRING", Column::STRING );

			table = new ReportTable( "TableTest", tableDefinition );

			tableDefINTEGER.addColumn( "TableTestColumn - int", Column::INTEGER );
			tableDefINTEGER.addColumn( "TableTestColumn - uint", Column::INTEGER );
			tableDefINTEGER.addColumn( "TableTestColumn - long", Column::INTEGER );
			tableDefINTEGER.addColumn( "TableTestColumn - ulong", Column::INTEGER );

			tableDefREAL.addColumn( "TableTestColumn - float", Column::REAL );
			tableDefREAL.addColumn( "TableTestColumn - double", Column::REAL );

			tableDefSIMTIME.addColumn( "TableTestColumn - base::SimTime", Column::SIMTIME );

			tableDefBAR.addColumn( "TableTestColumn - long", Column::BAR );

			tableDefSTRING.addColumn( "TableTestColumn - string", Column::STRING );
			tableDefSTRING.addColumn( "TableTestColumn - const char*", Column::STRING );
		}

		~TableFixture()
		{
			if( table ) delete table;
		}
	};

	/**
	 * @test odemx::Table construction
	 *
	 * Test correct initialization:
	 * @li label
	 * @li number of columns
	 * @li number of lines
	 * @li column labels
	 * @li column types
	 */
	TEST_FIXTURE( TableFixture, ConstructionDestruction )
	{
		CHECK_EQUAL( "TableTest", table->getLabel() );

		CHECK_EQUAL( (std::size_t)5, table->getNumberOfColumns() );
		CHECK_EQUAL( (std::size_t)0, table->getNumberOfLines() );

		CHECK_EQUAL( "TableTestColumn INTEGER", table->getLabelOfColumn( 0 ) );
		CHECK_EQUAL( "TableTestColumn REAL", table->getLabelOfColumn( 1 ) );
		CHECK_EQUAL( "TableTestColumn SIMTIME", table->getLabelOfColumn( 2 ) );
		CHECK_EQUAL( "TableTestColumn BAR", table->getLabelOfColumn( 3 ) );
		CHECK_EQUAL( "TableTestColumn STRING", table->getLabelOfColumn( 4 ) );

		CHECK_EQUAL( Column::INTEGER, table->getTypeOfColumn( 0 ) );
		CHECK_EQUAL( Column::REAL, table->getTypeOfColumn( 1 ) );
		CHECK_EQUAL( Column::SIMTIME, table->getTypeOfColumn( 2 ) );
		CHECK_EQUAL( Column::BAR, table->getTypeOfColumn( 3 ) );
		CHECK_EQUAL( Column::STRING, table->getTypeOfColumn( 4 ) );
	}

	/**
	 * @test odemx::Table input operators for integer values
	 *
	 * Expected function call effects:
	 * @li entering a value of wrong type sends an error to \c errorStream()
	 * @li all integer values are found in the table
	 * @li the table contains one completely filled line
	 * @li the values have been correctly inserted into the table cells
	 */
	TEST_FIXTURE( TableFixture, InputOperatorINTEGER )
	{
		ReportTable table( "ReportTableTestInput INTEGER", tableDefINTEGER );
		CHECK_EQUAL( (std::size_t)4, table.getNumberOfColumns() );


		CHECK_THROW( table << static_cast< long long >( MAX_VAL(long long) ), odemx::DataException );
//		CHECK_EQUAL( "ODEMx ERROR: ReportTable operator <<(long long): wrong column type at input position; cannot enter data into Table\n",
//				errorStream().str() );

		table << MIN_VAL(int) << MAX_VAL(unsigned int)
			  << MIN_VAL(long) << (unsigned long)999;

		CHECK_EQUAL( (std::size_t)1, table.getNumberOfLines() );

		CHECK_EQUAL( MIN_VAL(int), (int)table.getINTEGER( 0, 0 ) );
		CHECK_EQUAL( MAX_VAL(unsigned int), (unsigned int)table.getINTEGER( 1, 0 ) );
		CHECK_EQUAL( MIN_VAL(long), table.getINTEGER( 2, 0 ) );
		CHECK_EQUAL( (unsigned long)999, (unsigned long)table.getINTEGER( 3, 0 ) );
	}

	/**
	 * @test odemx::Table input operators for real values
	 *
	 * Expected function call effects:
	 * @li entering a value of wrong type sends an error to \c errorStream()
	 * @li all real values are found in the table
	 * @li the table contains one completely filled line
	 * @li the values have been correctly inserted into the table cells
	 */
	TEST_FIXTURE( TableFixture, InputOperatorREAL )
	{
		ReportTable table( "ReportTableTestInput REAL", tableDefREAL );
		CHECK_EQUAL( (std::size_t)2, table.getNumberOfColumns() );

		CHECK_THROW( table << static_cast< std::string >( "999" ), odemx::DataException );

		table << MIN_VAL(float) << MAX_VAL(double);

		CHECK_EQUAL( (std::size_t)1, table.getNumberOfLines() );

		CHECK_EQUAL( MIN_VAL(float), (float)table.getREAL( 0, 0 ) );
		CHECK_EQUAL( MAX_VAL(double), (double)table.getREAL( 1, 0 ) );
	}

	/**
	 * @test odemx::Table input operator for base::SimTime values
	 *
	 * Expected function call effects:
	 * @li entering a value of wrong type sends an error to \c errorStream()
	 * @li all base::SimTime values are found in the table
	 * @li the table contains two completely filled lines
	 * @li the values have been correctly inserted into the table cells
	 */
	TEST_FIXTURE( TableFixture, InputOperatorSIMTIME )
	{
		ReportTable table( "TableTestInput SIMTIME", tableDefSIMTIME );
		CHECK_EQUAL( (std::size_t)1, table.getNumberOfColumns() );

		CHECK_THROW( table << static_cast< std::string >( "999" ), odemx::DataException );

		table << static_cast< base::SimTime >( 100000000 );
		table << static_cast< base::SimTime >( 987654321 );

		CHECK_EQUAL( (std::size_t)2, table.getNumberOfLines() );

		CHECK_EQUAL( 100000000, table.getSIMTIME( 0, 0 ) );
		CHECK_EQUAL( 987654321, table.getSIMTIME( 0, 1 ) );
	}

	/**
	 * @test odemx::Table input operator for graphical representation values
	 *
	 * Expected function call effects:
	 * @li entering a value of wrong type sends an error to \c errorStream()
	 * @li all values are found in the table
	 * @li the table contains two completely filled lines
	 * @li the values have been correctly inserted into the table cells
	 */
	TEST_FIXTURE( TableFixture, InputOperatorBAR )
	{
		ReportTable table( "TableTestInput BAR", tableDefBAR );
		CHECK_EQUAL( (std::size_t)1, table.getNumberOfColumns() );

		CHECK_THROW( table << static_cast< double >( 999.01 ), odemx::DataException );

		table << static_cast< long >( 12345 );
		table << static_cast< long >( 67890 );

		CHECK_EQUAL( (std::size_t)2, table.getNumberOfLines() );

		CHECK_EQUAL( 12345, table.getBAR( 0, 0 ) );
		CHECK_EQUAL( 67890, table.getBAR( 0, 1 ) );
	}

	/**
	 * @test odemx::Table input operators for string values
	 *
	 * Expected function call effects:
	 * @li entering a value of wrong type sends an error to \c errorStream()
	 * @li all values are found in the table
	 * @li the table contains one completely filled line
	 * @li the values have been correctly inserted into the table cells
	 */
	TEST_FIXTURE( TableFixture, InputOperatorSTRING )
	{
		ReportTable table( "TableTestInput STRING", tableDefSTRING );
		CHECK_EQUAL( (std::size_t)2, table.getNumberOfColumns() );

		CHECK_THROW( table << static_cast< long >( MAX_VAL(long) ), odemx::DataException );

		table << std::string( "Test std::string" ) << "Test C-string";

		CHECK_EQUAL( (std::size_t)1, table.getNumberOfLines() );

		CHECK_EQUAL( "Test std::string", table.getSTRING( 0, 0 ) );
		CHECK_EQUAL( "Test C-string", table.getSTRING( 1, 0 ) );
	}

	/**
	 * @test odemx::Table input operator for TableDefinition control codes
	 *
	 * Expected function call effects:
	 * @li all default values are found in the table
	 * @li the table contains two completely filled lines with the same contents
	 * @li the values have been correctly inserted into the table cells
	 */
	TEST_FIXTURE( TableFixture, InputOperatorControlCode )
	{
		*table << ReportTable::Definition::ENDL;

		*table
		<< ReportTable::Definition::TAB
		<< ReportTable::Definition::TAB
		<< ReportTable::Definition::TAB
		<< ReportTable::Definition::TAB
		<< ReportTable::Definition::TAB;

		CHECK_EQUAL( (std::size_t)2, table->getNumberOfLines() );

		CHECK_EQUAL( 0l, table->getINTEGER( 0, 0 ) );
		CHECK_EQUAL( 0.0f, table->getREAL( 1, 0 ) );
		CHECK_EQUAL( 0, table->getSIMTIME( 2, 0 ) );
		CHECK_EQUAL( 0l, table->getBAR( 3, 0 ) );
		CHECK_EQUAL( "", table->getSTRING( 4, 0 ) );

		CHECK_EQUAL( table->getINTEGER( 0, 0 ), table->getINTEGER( 0, 1 ) );
		CHECK_EQUAL( table->getREAL( 1, 0 ), table->getREAL( 1, 1 ) );
		CHECK_EQUAL( table->getSIMTIME( 2, 0 ), table->getSIMTIME( 2, 1 ) );
		CHECK_EQUAL( table->getBAR( 3, 0 ), table->getBAR( 3, 1 ) );
		CHECK_EQUAL( table->getSTRING( 4, 0 ), table->getSTRING( 4, 1 ) );
	}

	/**
	 * @test odemx::Table::addDefaultValue()
	 *
	 * Expected function call effects:
	 * @li all default values are found in the table
	 * @li the table contains two completely filled lines with the same contents
	 * @li the values have been correctly inserted into the table cells
	 */
	TEST_FIXTURE( TableFixture, AddDefaultValue )
	{
		for( std::size_t i = 0; i < table->getNumberOfColumns(); ++i )
		{
			table->addDefaultValue();
		}

		CHECK_EQUAL( (std::size_t)1, table->getNumberOfLines() );

		CHECK_EQUAL( 0l, table->getINTEGER( 0, 0 ) );
		CHECK_EQUAL( 0.0f, table->getREAL( 1, 0 ) );
		CHECK_EQUAL( 0, table->getSIMTIME( 2, 0 ) );
		CHECK_EQUAL( 0l, table->getBAR( 3, 0 ) );
		CHECK_EQUAL( "", table->getSTRING( 4, 0 ) );
	}

	/**
	 * @test odemx::Table::getSIMTIME(std::size_t,std::size_t)
	 *
	 * Expected function call effects:
	 * @li if column is out of range, an error is sent to \c errorStream()
	 * @li if line is out of range, an error is sent to \c errorStream()
	 * @li if the column type does not match, an error is sent to \c
	 * errorStream()
	 * @li correct calls return the value stored in the given table cell
	 */
	TEST_FIXTURE( TableFixture, GetSIMTIME )
	{
		*table << (long)1 << (double)2.0 << (base::SimTime)3 << (long)4 << "5";
		CHECK_EQUAL( (std::size_t)1, table->getNumberOfLines() );

		// column out of range
		CHECK_THROW( table->getSIMTIME( 5, 0 ), std::out_of_range );
		// line out of range
		CHECK_THROW( table->getSIMTIME( 0, 1 ), std::out_of_range );
		// wrong column type
		CHECK_THROW( table->getSIMTIME( 4, 0 ), odemx::DataException );

		CHECK_EQUAL( 3, table->getSIMTIME( 2, 0 ) );
	}

	/**
	 * @test odemx::Table::getBAR(std::size_t,std::size_t)
	 *
	 * Expected function call effects:
	 * @li if column is out of range, an error is sent to \c errorStream()
	 * @li if line is out of range, an error is sent to \c errorStream()
	 * @li if the column type does not match, an error is sent to \c
	 * errorStream()
	 * @li correct calls return the value stored in the given table cell
	 */
	TEST_FIXTURE( TableFixture, GetBAR )
	{
		*table << (long)1 << (double)2.0 << (base::SimTime)3 << (long)4 << "5";
		CHECK_EQUAL( (std::size_t)1, table->getNumberOfLines() );

		// column out of range
		CHECK_THROW( table->getBAR( 5, 0 ), std::out_of_range );
		// line out of range
		CHECK_THROW( table->getBAR( 0, 1 ), std::out_of_range );
		// wrong column type
		CHECK_THROW( table->getBAR( 4, 0 ), odemx::DataException );

		CHECK_EQUAL( 4, table->getBAR( 3, 0 ) );
	}

	/**
	 * @test odemx::Table::getINTEGER(std::size_t,std::size_t)
	 *
	 * Expected function call effects:
	 * @li if column is out of range, an error is sent to \c errorStream()
	 * @li if line is out of range, an error is sent to \c errorStream()
	 * @li if the column type does not match, an error is sent to \c
	 * errorStream()
	 * @li correct calls return the value stored in the given table cell
	 */
	TEST_FIXTURE( TableFixture, GetINTEGER )
	{
		*table << (long)1 << (double)2.0 << (base::SimTime)3 << (long)4 << "5";
		CHECK_EQUAL( (std::size_t)1, table->getNumberOfLines() );

		// column out of range
		CHECK_THROW( table->getINTEGER( 5, 0 ), std::out_of_range );
		// line out of range
		CHECK_THROW( table->getINTEGER( 0, 1 ), std::out_of_range );
		// wrong column type
		CHECK_THROW( table->getINTEGER( 4, 0 ), odemx::DataException );

		CHECK_EQUAL( 1, table->getINTEGER( 0, 0 ) );
	}


	/**
	 * @test odemx::Table::getREAL(std::size_t,std::size_t)
	 *
	 * Expected function call effects:
	 * @li if column is out of range, an error is sent to \c errorStream()
	 * @li if line is out of range, an error is sent to \c errorStream()
	 * @li if the column type does not match, an error is sent to \c
	 * errorStream()
	 * @li correct calls return the value stored in the given table cell
	 */
	TEST_FIXTURE( TableFixture, GetREAL )
	{
		*table << (long)1 << (double)2.0 << (base::SimTime)3 << (long)4 << "5";
		CHECK_EQUAL( (std::size_t)1, table->getNumberOfLines() );

		// column out of range
		CHECK_THROW( table->getREAL( 5, 0 ), std::out_of_range );
		// line out of range
		CHECK_THROW( table->getREAL( 0, 1 ), std::out_of_range );
		// wrong column type
		CHECK_THROW( table->getREAL( 4, 0 ), odemx::DataException );

		CHECK_EQUAL( 2.0, table->getREAL( 1, 0 ) );
	}

	/**
	 * @test odemx::Table::getSTRING(std::size_t,std::size_t)
	 *
	 * Expected function call effects:
	 * @li if column is out of range, an error is sent to \c errorStream()
	 * @li if line is out of range, an error is sent to \c errorStream()
	 * @li all column types are converted to a string return value
	 */
	TEST_FIXTURE( TableFixture, GetSTRING )
	{
		*table << (long)1 << (double)2.0 << (base::SimTime)3 << (long)4 << "5";
		CHECK_EQUAL( (std::size_t)1, table->getNumberOfLines() );

		// column out of range
		CHECK_THROW( table->getSTRING( 5, 0 ), std::out_of_range );
		// line out of range
		CHECK_THROW( table->getSTRING( 0, 1 ), std::out_of_range );

		CHECK_EQUAL( toString( 1 ), table->getSTRING( 0, 0 ) );
		CHECK_EQUAL( toString( 2.0 ), table->getSTRING( 1, 0 ) );
		CHECK_EQUAL( toString( 3 ), table->getSTRING( 2, 0 ) );
		CHECK_EQUAL( toString( 4 ), table->getSTRING( 3, 0 ) );
		CHECK_EQUAL( "5", table->getSTRING( 4, 0 ) );
	}

	/**
	 * @test odemx::Table output streaming
	 *
	 * Expected function call effects:
	 * @li output stream wraps at last table cell
	 * @li in case of wrong column type, all output operators send error
	 * messages to \c errorStream()
	 * @li all column types are converted to a string return value
	 * by \c operator>>(string)
	 * @li the string representation of the actual values and the streamed
	 * strings are equal
	 */
	TEST_FIXTURE( TableFixture, OutputStreaming )
	{
		*table << (long)1 << (double)2.0 << (base::SimTime)3 << (long)4 << "5";
		CHECK_EQUAL( (std::size_t)1, table->getNumberOfLines() );

		long column0;
		double column1;
		base::SimTime column2;
		long column3;
		std::string column4;
		long wrap0;

		// get all column data and check for correct wrap
		*table >> column0 >> column1 >> column2 >> column3 >> column4 >> wrap0;
		CHECK_EQUAL( column0, wrap0 );

		long advance0;
		double advance1;
		base::SimTime advance2;
		long advance3;
		std::string advance4;

		// try to extract long, but find double
		CHECK_THROW( *table >> advance0, odemx::DataException );

		// move forward to the string column
		*table >> advance1 >> advance2 >> advance3;

		// expects base::SimTime, finds string
		CHECK_THROW( *table >> advance2, odemx::DataException );

		// expects double, finds string
		CHECK_THROW( *table >> advance1, odemx::DataException );

		*table >> advance4; // move output stream to beginning

		// operator >>(string) should convert all column types
		std::string s0, s1, s2, s3, s4;
		*table >> s0 >> s1 >> s2 >> s3 >> s4;

		CHECK_EQUAL( toString( column0 ), s0 );
		CHECK_EQUAL( toString( column1 ), s1 );
		CHECK_EQUAL( toString( column2 ), s2 );
		CHECK_EQUAL( toString( column3 ), s3 );
		CHECK_EQUAL( toString( column4 ), s4 );
	}

	// ----------------------------------------------------------------- COLUMNS

	/**
	 * @test odemx::Column/ColumnT construction
	 *
	 * Tests correct member initialization:
	 * @li column label
	 * @li column type
	 */
	TEST( ColumnConstructionDestruction )
	{
		data::Label label = "ColumnTestSimTime";
		ColumnTest< base::SimTime > col( label, Column::SIMTIME );
		CHECK_EQUAL( label, col.getLabel() );
		CHECK_EQUAL( "SIMTIME", Column::typeToString( col.getType() ) );
	}

	/**
	 * @test odemx::Column::typeToString()
	 *
	 * Expected function call effects:
	 * @li return of a string representation of the column type
	 */
	TEST( ColumnTypeToString )
	{
		CHECK_EQUAL( "INTEGER", Column::typeToString( Column::INTEGER ) );
		CHECK_EQUAL( "BAR", Column::typeToString( Column::BAR ) );
		CHECK_EQUAL( "REAL", Column::typeToString( Column::REAL ) );
		CHECK_EQUAL( "SIMTIME", Column::typeToString( Column::SIMTIME ) );
		CHECK_EQUAL( "STRING", Column::typeToString( Column::STRING ) );
		CHECK_EQUAL( "UNKNOWN COLUMN TYPE", Column::typeToString( Column::INVALID ) );
	}

	/**
	 * @test odemx::ColumnT<DataType>::addData(DataType)
	 *
	 * Expected function call effects:
	 * @li data is appended to the internal vector, i.e. put into the next line
	 */
	TEST( ColumnTAddData )
	{
		ColumnTest<int> column( "ColumnTTest", Column::INTEGER );
		column.addData( MIN_VAL(int) );
		column.addData( MAX_VAL(int) );

		CHECK_EQUAL( MIN_VAL(int), column.getData( 0 ) );
		CHECK_EQUAL( MAX_VAL(int), column.getData( 1 ) );
	}

	/**
	 * @test odemx::ColumnT<DataType>::getData(DataType)
	 *
	 * Expected function call effects:
	 * @li data is returned correctly
	 * @li out of range access causes an exception to be thrown and an
	 * error message to be sent to \c errorStream()
	 */
	TEST( ColumnTReadData )
	{
		ColumnTest<float> column( "ColumnTTest", Column::REAL );
		column.addData( MIN_VAL(float) );

		CHECK_EQUAL( MIN_VAL(float), column.getData( 0 ) );

		CHECK_THROW( column.getData( 1 ), std::out_of_range );
	}

/// @cond DOXYGEN_SKIP
}
/// @endcond
