/**
 * @file TestTimeFormat.cpp
 * @date Aug 16, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx classes TimeBase, TimeFormat, GermanTime, Iso8601Time
 */

#include "../TestData.h"
#include "../../TestBase/TestBase.h"

/// @cond DOXYGEN_SKIP
SUITE( Data )
{
/// @endcond

	/// helper to convert enum TimeUnit to string
	std::string unitToString( const TimeUnit::Type unit )
	{
		switch( unit )
		{
		case TimeUnit::milliseconds:
			return "milliseconds";
		case TimeUnit::seconds:
			return "seconds";
		case TimeUnit::minutes:
			return "minutes";
		case TimeUnit::hours:
			return "hours";
		default:
			return "no_unit";
		}
		return "error: Timeunit could not be determined";
	}

	/// helper for converting a TimeBase to string
	std::string baseToString( const TimeBase& base )
	{
		std::ostringstream stream;

		stream
		<< "Year: " << base.year
		<< ", Month: " << base.month
		<< ", Day: " << base.day
		<< ", Offset: " << base.offset
		<< ", Unit: " << unitToString( base.unit );

		return stream.str();
	}

	/**
	 * @struct TraceFilterFixture
	 * @brief Helper struct providing set-up/tear-down of TraceFilter tests
	 *
	 * @copydetails EventFixture
	 */
	struct TimeFixture
	{
		TimeFormatTest format;
		GermanTime german;
		TimeBase base;
		Iso8601Time iso;

		TimeFixture()
		:	format(),
			german(),
			base( 2000, 01, 01, 1000000, TimeUnit::milliseconds ),
			iso( base )
			{}
	};

	/**
	 * @test odemx::TimeBase construction
	 *
	 * Expected effects:
	 * @li year, month, day, offset, and unit are initialized correctly
	 */
	TEST( TimeBase )
	{
		TimeBase base( 2008, 8, 16, 7244, TimeUnit::minutes );
		CHECK_EQUAL( "Year: 2008, Month: 8, Day: 16, Offset: 7244, Unit: minutes",
				baseToString( base ) );
	}

	/**
	 * @test odemx::TimeZone construction
	 *
	 * Expected effects:
	 * @li hour parameter out of permitted range causes an error message
	 * @li minute parameter out of permitted range causes an error message
	 * @li otherwise, hour and minute are initialized correctly
	 */
	TEST( UtcOffset )
	{
		CHECK_THROW( Iso8601Time::UtcOffset zone1( -13, 0 ), std::out_of_range );
		CHECK_THROW( Iso8601Time::UtcOffset zone2( 15, 0 ), std::out_of_range );
		CHECK_THROW( Iso8601Time::UtcOffset zone3( 0, 1 ), std::out_of_range );

		unsigned short minute[] = { 0, 15, 30, 45 };
		short hour = -1;

		clearErrorStream();
		for( unsigned short i = 0; i != 4; ++i )
		{
			Iso8601Time::UtcOffset zone( hour, minute[ i ] );
			CHECK_EQUAL( hour, zone.hour );
			CHECK_EQUAL( minute[ i ], zone.minute );
		}
		CHECK( errorStream().str().empty() );
	}

	/**
	 * @test odemx::TimeFormat construction
	 *
	 * Expected effects:
	 * @li the object can be default-constructed with 0-TimeBase
	 * @li the object can be constructed with a reference to a TimeBase
	 * @li the TimeBase is set correctly
	 */
	TEST_FIXTURE( TimeFixture, FormatConstruction )
	{
		TimeFormatTest testFormat1;
		CHECK_EQUAL( "Year: 0, Month: 0, Day: 0, Offset: 0, Unit: no_unit",
				baseToString( testFormat1.getTimeBase() ) );

		TimeFormatTest testFormat2( TimeBase( 2008, 8, 16, 7244, TimeUnit::minutes ) );
		CHECK_EQUAL( "Year: 2008, Month: 8, Day: 16, Offset: 7244, Unit: minutes",
				baseToString( testFormat2.getTimeBase() ) );
	}

	/**
	 * @test odemx::GermanTime construction
	 *
	 * Expected function call effects:
	 * @li the object can be default-constructed with 0-TimeBase
	 * @li the object can be constructed with a reference to a TimeBase
	 * @li the TimeBase is set correctly
	 */
	TEST_FIXTURE( TimeFixture, GermanConstruction )
	{
		GermanTime testFormat1;
		CHECK_EQUAL( "Year: 0, Month: 0, Day: 0, Offset: 0, Unit: no_unit",
				baseToString( testFormat1.getTimeBase() ) );

		TimeBase base( 2008, 8, 16, 7244, TimeUnit::minutes );
		GermanTime testFormat2( base );
		CHECK_EQUAL( "Year: 2008, Month: 8, Day: 16, Offset: 7244, Unit: minutes",
				baseToString( testFormat2.getTimeBase() ) );
	}

	/**
	 * @test odemx::TimeFormat::setTimeBase()
	 *
	 * Expected function call effects:
	 * @li time base can be set by a reference and by giving separate parameters
	 */
	TEST_FIXTURE( TimeFixture, FormatSetTimeBase )
	{
		TimeBase base( 2008, 8, 16, 7244, TimeUnit::minutes );
		format.setTimeBase( base );
		CHECK_EQUAL( baseToString( base ), baseToString( format.getTimeBase() ) );

		format.setTimeBase( 2008, 8, 16, 7244, TimeUnit::minutes );
		CHECK_EQUAL( baseToString( base ), baseToString( format.getTimeBase() ) );
	}

	/**
	 * @test odemx::TimeFormat::timeToString(SimTime)
	 *
	 * Expected function call effects:
	 * @li if no TimeUnit is set, \c computeTimeValues() produces an error
	 * @li time display is computed correctly for units in milliseconds,
	 * seconds, minutes, and hours
	 */
	TEST_FIXTURE( TimeFixture, FormatTimeToString )
	{
		// default initialization without suitable TimeBase
		TimeFormatTest test;
		CHECK_THROW( test.timeToString( 0 ), odemx::DataException );

		// set suitable TimeBase
		format.setTimeBase( 0,0,0,0,TimeUnit::milliseconds );
		CHECK_EQUAL( "time: 0/3/24-17:46:40.123", format.timeToString( 10000000123ll ) );
		format.setTimeBase( 0,0,0,0,TimeUnit::seconds );
		CHECK_EQUAL( "time: 0/3/24-17:46:40", format.timeToString( 10000000 ) );
		// the following two tests might produce a different result
		// if SimTime is not an integral type
		format.setTimeBase( 0,0,0,1439,TimeUnit::minutes );
		CHECK_EQUAL( "time: 0/3/25-17:45:0", format.timeToString( 10000000 / 60 ) );
		format.setTimeBase( 0,0,0,0,TimeUnit::hours );
		CHECK_EQUAL( "time: 0/3/24-17:0:0", format.timeToString( 10000000 / 60 / 60 ) );
	}

	/**
	 * @test odemx::GermanTime::timeToString(SimTime)
	 *
	 * Expected function call effects:
	 * @li time display is computed correctly for units in milliseconds,
	 * seconds, minutes, and hours
	 */
	TEST_FIXTURE( TimeFixture, GermanTimeToString )
	{
		// set suitable TimeBase
		german.setTimeBase( 0,0,0,0,TimeUnit::milliseconds );
		CHECK_EQUAL( "00-03-24 / 17:46:40.123", german.timeToString( 10000000123ll ) );
		german.setTimeBase( 0,0,0,0,TimeUnit::seconds );
		CHECK_EQUAL( "00-03-24 / 17:46:40", german.timeToString( 10000000 ) );
		// the following two tests might produce a different result
		// if SimTime is not an integral type
		german.setTimeBase( 0,0,0,1439,TimeUnit::minutes );
		CHECK_EQUAL( "00-03-25 / 17:45:00", german.timeToString( 10000000 / 60 ) );
		german.setTimeBase( 0,0,0,0,TimeUnit::hours );
		CHECK_EQUAL( "00-03-24 / 17:00:00", german.timeToString( 10000000 / 60 / 60 ) );
	}

	/**
	 * @test odemx::TimeISO8601::timeToString(SimTime)
	 *
	 * Expected function call effects:
	 * @li time display is computed correctly for units in milliseconds,
	 * seconds, minutes, and hours
	 */
	TEST_FIXTURE( TimeFixture, ISO8601TimeToString )
	{
		// set suitable TimeBase
		iso.setTimeBase( 0,0,0,0,TimeUnit::milliseconds );

		// default zone is UTC-0
		CHECK_EQUAL( "0000-03-24T17:46:40.123Z", iso.timeToString( 10000000123ll ) );

		iso.setTimeBase( 0,0,0,0,TimeUnit::seconds );
		iso.setUtcOffset( -1, 0 );
		CHECK_EQUAL( "0000-03-24T17:46:40-01:00", iso.timeToString( 10000000 ) );

		// the following two tests might produce a different result
		// if SimTime is not an integral type
		iso.setTimeBase( 0,0,0,1439,TimeUnit::minutes );
		iso.setUtcOffset( 12, 30 );
		CHECK_EQUAL( "0000-03-25T17:45:00+12:30", iso.timeToString( 10000000 / 60 ) );
		iso.setTimeBase( 0,0,0,0,TimeUnit::hours );
		iso.setUtcOffset( 4, 45 );
		CHECK_EQUAL( "0000-03-24T17:00:00+04:45", iso.timeToString( 10000000 / 60 / 60 ) );
	}

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