/**
 * @file TestExecutionList.cpp
 * @brief Tests for ODEMx class ExecutionList
 */

#include "TestBase.h"

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

	/**
	 * @struct ExecutionListFixture
	 * @brief Helper struct providing set-up/tear-down of ExecutionList tests
	 *
	 * @copydetails EventFixture
	 */
	struct ExecutionListFixture {
		ExecutionListFixture():
			sim( "SimulationTest" ),
			log( TestLogConsumer::create() ),
			exList( sim, sim.getLabel() + " execution list" ),
			testEvent( new EventTest( sim, "ExecutionListTestEvent" ) ),
			testEvent2( new EventTest( sim, "ExecutionListTestEvent" ) ),
			type( typeid(ExecutionList) )
			{
				sim.addConsumer( log );
			}

		~ExecutionListFixture()
			{
				if( testEvent ) delete testEvent;
				if( testEvent2 ) delete testEvent2;
			}

		SimulationTest sim; ///< user-defined simulation
		TestLogConsumer::Ptr log; ///< log consumer to check sim records
		ExecutionList exList; ///< the ExecutionList object
		Event* testEvent; ///< an Event to test scheduling
		Event* testEvent2; ///< another Event to test ordered scheduling
		data::TypeInfo type;
	};

	/**
	 * @test odemx::ExecutionList construction and destruction
	 *
	 * Expected observations:
	 * @li test object's Sched list is empty
	 * @li next Sched is \c 0, i.e. not set
	 * @li required implementation of \c getTime() returns \c 1
	 * @li observation calls of \c onCreate() and \c onDestroy()
	 */
	TEST_FIXTURE( ExecutionListFixture, ConstructionDestruction )
	{
		CHECK( exList.isEmpty() );
		CHECK_EQUAL( static_cast< Sched* >( 0 ), exList.getNextSched() );
		CHECK_EQUAL( static_cast< SimTime >( sim.getTime() ), exList.getTime() );

		data::Label label = "ExecutionListTestConstructioneDestruction";
		{
			ExecutionList exList( sim, label );
			CHECK( log->getTraceRecord( "create", type ) );
		}
		CHECK( log->getTraceRecord( "destroy", type ) );
	}

	/**
	 * @test odemx::ExecutionList::addSched(Sched*)
	 *
	 * Expected function call effects:
	 * @li ExecutionList is not empty, i.e. an object is scheduled
	 * @li the newly scheduled object is returned by \c getNextSched()
	 * @li the Event object knows it is scheduled
	 * @li the Event execution time is equal to \c getTime()
	 * @li observation call of \c onAddSched()
	 */
	TEST_FIXTURE( ExecutionListFixture, AddSched )
	{
		CHECK_EQUAL( 0, testEvent->getExecutionTime() );
		CHECK( ! testEvent->isScheduled() );

		exList.addSched( testEvent );
		CHECK( ! exList.isEmpty() );
		CHECK_EQUAL( exList.getNextSched(), testEvent );
		CHECK( testEvent->isScheduled() );
		CHECK_EQUAL( exList.getTime(), testEvent->getExecutionTime() );
		CHECK( log->getTraceRecord( "add", type ) );
	}

	/**
	 * @test odemx::ExecutionList::insertSched(Sched*)
	 *
	 * Expected function call effects:
	 * @li ExecutionList is not empty, i.e. an object is scheduled
	 * @li the newly scheduled object is returned by \c getNextSched()
	 * @li the Event object knows it is scheduled
	 * @li the Event execution time is equal to \c getTime()
	 * @li observation call of \c onInsertSched()
	 */
	TEST_FIXTURE( ExecutionListFixture, InsertSched )
	{
		CHECK_EQUAL( 0, testEvent->getExecutionTime() );
		CHECK( ! testEvent->isScheduled() );

		exList.insertSched( testEvent );
		CHECK( ! exList.isEmpty() );
		CHECK_EQUAL( exList.getNextSched(), testEvent );
		CHECK( testEvent->isScheduled() );
		CHECK_EQUAL( exList.getTime(), testEvent->getExecutionTime() );
		CHECK( log->getTraceRecord( "insert", type ) );
	}

	/**
	 * @test odemx::ExecutionList LIFO, FIFO insertion of Sched objects
	 *
	 * Precondition: \c testEvent2 is scheduled.
	 *
	 * Expected effect after calling insertSched( testEvent ):
	 * @li LIFO insertion at front, the next scheduled object is testEvent
	 * @li removal of testEvent makes testEvent2 the next scheduled object
	 *
	 * Expected effect after calling addSched( testEvent ):
	 * @li FIFO insertion at back, the next scheduled object is still testEvent2
	 * @li removal of testEvent2 makes testEvent the next scheduled object
	 */
	TEST_FIXTURE( ExecutionListFixture, LifoFifo )
	{
		exList.addSched( testEvent2 );

		// check LIFO insertion
		exList.insertSched( testEvent );
		CHECK( exList.getNextSched() == testEvent );
		exList.removeSched( exList.getNextSched() );
		CHECK( exList.getNextSched() == testEvent2 );

		// check FIFO insertion
		exList.addSched( testEvent );
		CHECK( exList.getNextSched() == testEvent2 );
		exList.removeSched( exList.getNextSched() );
		CHECK( exList.getNextSched() == testEvent );
	}

	/**
	 * @test odemx::ExecutionList::insertSchedBefore(Sched*,Sched*)
	 *
	 * Precondition: \c testEvent is scheduled and has higher priority
	 * than \c testEventBefore.
	 *
	 * Expected function call effects:
	 * @li object \c testEventBefore is scheduled
	 * @li execution time of \c testEventBefore equals time of ExecutionList
	 * @li priority of \c testEventBefore equals that of \c testEvent
	 * @li observation call of \c onInsertSchedBefore()
	 * @li order in list is: \c testEventBefore, \c testEvent
	 * @li error output when attempting to schedule an object before
	 * an unscheduled object
	 */
	TEST_FIXTURE( ExecutionListFixture, InsertSchedBefore )
	{
		exList.addSched( testEvent );

		Event* testEventBefore = new EventTest( exList.getSimulation(), "EventTestBefore" );
		Priority basePrio = testEvent->getPriority();
		testEventBefore->setPriority( basePrio - 2 );
		CHECK( testEventBefore->getPriority() < testEvent->getPriority() );

		exList.insertSchedBefore( testEventBefore, testEvent );
		CHECK( testEventBefore->isScheduled() );
		CHECK_EQUAL( testEventBefore->getExecutionTime(), exList.getTime() );
		CHECK_EQUAL( testEventBefore->getPriority(), testEvent->getPriority() );
		CHECK( log->getTraceRecord( "insert before", type ) );

		// check order
		CHECK_EQUAL( exList.getNextSched(), testEventBefore );
		exList.removeSched( exList.getNextSched() );
		CHECK_EQUAL( exList.getNextSched(), testEvent );
		exList.removeSched( exList.getNextSched() );
		CHECK( exList.isEmpty() );

		// check errors
		exList.insertSchedBefore( testEvent, testEventBefore );
		CHECK( log->getErrorRecord( "ExecutionList::insertSchedBefore(): "
				"partner not scheduled", type ) );

		exList.insertSchedBefore( testEventBefore, testEventBefore );
		CHECK( log->getErrorRecord( "ExecutionList::insertSchedBefore(): "
				"sched and partner are the same", type ) );

		delete testEventBefore;
	}

	/**
	 * @test odemx::ExecutionList::insertSchedAfter(Sched*,Sched*)
	 *
	 * Precondition: \c testEvent is scheduled and has lower priority
	 * than \c testEventAfter.
	 *
	 * Expected function call effects:
	 * @li object \c testEventAfter is scheduled
	 * @li execution time of \c testEventAfter equals time of ExecutionList
	 * @li priority of \c testEventAfter equals that of \c testEvent
	 * @li observation call of \c onInsertSchedAfter()
	 * @li order in list is: \c testEvent, \c testEventAfter
	 * @li error output when attempting to schedule an object after
	 * an unscheduled object
	 */
	TEST_FIXTURE( ExecutionListFixture, InsertSchedAfter )
	{
		exList.addSched( testEvent );

		Event* testEventAfter = new EventTest( exList.getSimulation(), "EventTestAfter" );
		Priority basePrio = testEvent->getPriority();
		testEventAfter->setPriority( basePrio + 2 );
		CHECK( testEventAfter->getPriority() > testEvent->getPriority() );

		exList.insertSchedAfter( testEventAfter, testEvent );
		CHECK( testEventAfter->isScheduled() );
		CHECK_EQUAL( testEventAfter->getExecutionTime(), exList.getTime() );
		CHECK_EQUAL( testEventAfter->getPriority(), testEvent->getPriority() );
		CHECK( log->getTraceRecord( "insert after", type ) );

		// check order
		CHECK_EQUAL( exList.getNextSched(), testEvent );
		exList.removeSched( exList.getNextSched() );
		CHECK_EQUAL( exList.getNextSched(), testEventAfter );
		exList.removeSched( exList.getNextSched() );
		CHECK( exList.isEmpty() );

		// check errors
		exList.insertSchedAfter( testEvent, testEventAfter );
		CHECK( log->getErrorRecord( "ExecutionList::insertSchedAfter(): "
				"partner not scheduled", type ) );

		exList.insertSchedAfter( testEventAfter, testEventAfter );
		CHECK( log->getErrorRecord( "ExecutionList::insertSchedAfter(): "
				"sched and partner are the same", type ) );

		delete testEventAfter;
	}

	/**
	 * @test odemx::ExecutionList::removeSched(Sched*)
	 *
	 * Expected function call effects:
	 * @li ExecutionList is empty, i.e. no object is scheduled
	 * @li the Event object knows it is scheduled
	 * @li \c getNextSched() returns 0
	 * @li observation call of \c onRemoveSched()
	 * @li error output when attempting to remove an unscheduled object
	 */
	TEST_FIXTURE( ExecutionListFixture, RemoveSched )
	{
		exList.addSched( testEvent );
		CHECK( ! exList.isEmpty() );

		exList.removeSched( testEvent );
		CHECK( exList.isEmpty() );
		CHECK( ! testEvent->isScheduled() );
		CHECK_EQUAL( static_cast< Sched* >( 0 ), exList.getNextSched() );
		CHECK( log->getTraceRecord( "remove", type ) );

		// check error
		exList.removeSched( testEvent );
		CHECK( log->getErrorRecord( "ExecutionList::removeSched(): "
				"attempt to remove unscheduled object", type ) );
	}

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