/**
 * @file TestProcessQueue.cpp
 * @brief Tests for ODEMx class ProcesQueue
 */

#include "TestSynchronization.h"
#include "../TestBase/TestBase.h"

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

	using namespace SuiteBase;

	/**
	 * @struct ProcessQueueFixture
	 * @brief Helper struct providing set-up/tear-down of ProcessQueue tests
	 *
	 * @copydetails EventFixture
	 */
	struct ProcessQueueFixture
	{
		ProcessQueueFixture():
			queue(),
			sim( "ProcessQueueHelperSim" ),
			p1( sim, "ProcessQueueHelper1" ),
			p2( sim, "ProcessQueueHelper2" ),
			p3( sim, "ProcessQueueHelper3" )
			{}

		odemx::synchronization::ProcessQueue queue; ///< a test queue
		SimulationTest sim; ///< user-defined Simulation context
		ProcessTest p1; ///< a test Process
		ProcessTest p2; ///< a test Process
		ProcessTest p3; ///< a test Process
	};

	/**
	 * @test odemx::ProcessQueue construction and destruction
	 *
	 * Tests correct ProcessQueue member initialization:
	 * @li length of the ProcessQueue
	 */
	TEST_FIXTURE( ProcessQueueFixture, ConstructionDestruction )
	{
		CHECK( queue.getLength() == 0 );
	}

	/**
	 * @test odemx::ProcessQueue::getTop()
	 *
	 * Expected function call effects:
	 * @li returns \c 0 when empty
	 * @li when not empty, it returns a pointer to the first Process in the queue
	 */
	TEST_FIXTURE( ProcessQueueFixture, GetTop )
	{
		// 0 pointer if empty
		CHECK( queue.isEmpty() );
		CHECK( queue.getTop() == 0 );
		CHECK( queue.getLength() == 0 );

		// process pointer if queue contains element
		queue.inSort( &p1 );
		CHECK( ! queue.isEmpty() );
		CHECK( queue.getLength() == 1 );
		CHECK( queue.getTop() == &p1 );
	}

	/**
	 * @test odemx::ProcessQueue::getPosition(Process*)
	 *
	 * Three Process objects are inserted into the queue, in FIFO mode.
	 *
	 * Expected function call effects:
	 * @li return 0 if a process is not in the queue
	 * @li return the correct queue position for each queued Process
	 */
	TEST_FIXTURE( ProcessQueueFixture, GetPosition )
	{
		CHECK( 0 == queue.getPosition( &p1 ) );

		queue.inSort( &p1 );
		queue.inSort( &p2 );
		queue.inSort( &p3 );
		CHECK( 1 == queue.getPosition( &p1 ) );
		CHECK( 2 == queue.getPosition( &p2 ) );
		CHECK( 3 == queue.getPosition( &p3 ) );
	}

	/**
	 * @test odemx::ProcessQueue::popQueue()
	 *
	 * Two processes are inserted into the queue in FIFO order.
	 *
	 * Expected function call effects:
	 * @li the ProcessQueue has length 1
	 * @li the first Process is not enqueued anymore
	 * @li the second Process is now in first position
	 */
	TEST_FIXTURE( ProcessQueueFixture, PopQueue )
	{
		queue.inSort( &p1 );
		queue.inSort( &p2 );
		CHECK( 1 == queue.getPosition( &p1 ) );
		CHECK( 2 == queue.getPosition( &p2 ) );
		CHECK( queue.getLength() == 2 );

		queue.popQueue();
		CHECK( queue.getTop() == &p2 );
		CHECK( 0 == queue.getPosition( &p1 ) );
		CHECK( 1 == queue.getPosition( &p2 ) );
		CHECK( queue.getLength() == 1 );
	}

	/**
	 * @test odemx::ProcessQueue::remove(Process*)
	 *
	 * By scheduling and executing an Event, the SImTime is advanced before
	 * inserting three processes <tt>p1, p2, p3</tt> into the queue.
	 *
	 * Expected function call effects when removing the Process \c p2:
	 * @li the Process dequeue time equals the current SimTime
	 * @li the Process's queue pointer is \c 0
	 * @li \c getPosition(p2) returns \c 0, i.e. \c p2 is not enqueued
	 * @li \c p1 is still in first position
	 * @li \c p3 is in second position now
	 */
	TEST_FIXTURE( ProcessQueueFixture, Remove )
	{
		SimTime advanceTime = 250;
		EventTest e( sim, "TimeAdvanceEvent" );
		e.scheduleAt( advanceTime );
		sim.step();
		CHECK( sim.getTime() == advanceTime );

		queue.inSort( &p1 );
		queue.inSort( &p2 );
		queue.inSort( &p3 );

		queue.remove( &p2 );
		CHECK( p2.getDequeueTime() == advanceTime );
		CHECK( p2.getQueue() == 0 );
		// 0 means process is not in queue
		CHECK( 0 == queue.getPosition( &p2 ) );
		CHECK( 1 == queue.getPosition( &p1 ) );
		CHECK( 2 == queue.getPosition( &p3 ) );

		CHECK( queue.getTop() == &p1 );
		queue.remove( &p1 );
		CHECK( queue.getTop() == &p3 );
	}

	/**
	 * @test odemx::ProcessQueue::inSort(Process*)
	 *
	 * As before, an Event is used to advance SimTime. One Process is inserted.
	 *
	 * Expected function call effects:
	 * @li the Process's enqueue time is set to the current SimTime
	 * @li the Process's queue pointer points to \c queue
	 * @li the Process is in queue position 1
	 * @li \c getTop() returns a pointer to the Process
	 */
	TEST_FIXTURE( ProcessQueueFixture, InSort )
	{
		SimTime advanceTime = 245;
		EventTest e( sim, "TimeAdvanceEvent" );
		e.scheduleAt( advanceTime );
		sim.step();
		CHECK( sim.getTime() == advanceTime );

		// check normal insertion
		queue.inSort( &p1 );
		CHECK( p1.getEnqueueTime() == advanceTime );
		CHECK( p1.getQueue() == &queue );
		CHECK( 1 == queue.getPosition( &p1 ) );
		CHECK( queue.getTop() == &p1 );
	}

	/**
	 * @test odemx::ProcessQueue::inSort() FIFO insertion
	 *
	 * Three processes with equal queue priority are inserted:
	 * \c p1, \c p2 and \c p3.
	 *
	 * Expected function call effects:
	 * @li \c p1 is in first position
	 * @li \c p2 is in second position
	 * @li \c p2 is in third position
	 *
	 * Expected effects of re-insertion of \c p1 without prior removal:
	 * @li \c p2 is in first position
	 * @li \c p3 is in second position
	 * @li \c p1 is in third position
	 */
	TEST_FIXTURE( ProcessQueueFixture, InSortFIFO )
	{
		// make sure the processes have equal priority
		CHECK_EQUAL( 0, p1.getQueuePriority() );
		CHECK_EQUAL( 0, p2.getQueuePriority() );
		CHECK_EQUAL( 0, p3.getQueuePriority() );

		queue.inSort( &p1 );
		queue.inSort( &p2 );
		queue.inSort( &p3 );
		CHECK( queue.getTop() == &p1 );
		CHECK( 1 == queue.getPosition( &p1 ) );
		CHECK( 2 == queue.getPosition( &p2 ) );
		CHECK( 3 == queue.getPosition( &p3 ) );

		// check re-insertion of p1, should be fifo
		queue.inSort( &p1 );
		CHECK( queue.getTop() == &p2 );
		CHECK( 1 == queue.getPosition( &p2 ) );
		CHECK( 2 == queue.getPosition( &p3 ) );
		CHECK( 3 == queue.getPosition( &p1 ) );
	}

	/**
	 * @test odemx::ProcessQueue::inSort() LIFO insertion
	 *
	 * Three processes with equal queue priority are inserted:
	 * \c p1, \c p2 and \c p3.
	 *
	 * Expected function call effects:
	 * @li \c p3 is in first position
	 * @li \c p2 is in second position
	 * @li \c p1 is in third position
	 *
	 * Expected effects of re-insertion of \c p1 without prior removal:
	 * @li \c p1 is in first position
	 * @li \c p3 is in second position
	 * @li \c p2 is in third position
	 */
	TEST_FIXTURE( ProcessQueueFixture, InSortLIFO )
	{
		// make sure the processes have equal priority
		CHECK_EQUAL( 0, p1.getQueuePriority() );
		CHECK_EQUAL( 0, p2.getQueuePriority() );

		// check normal insertion
		queue.inSort( &p1, false );
		queue.inSort( &p2, false );
		queue.inSort( &p3, false );
		CHECK( queue.getTop() == &p3 );
		CHECK( 1 == queue.getPosition( &p3 ) );
		CHECK( 2 == queue.getPosition( &p2 ) );
		CHECK( 3 == queue.getPosition( &p1 ) );

		// check re-insertion of p1, should be lifo
		queue.inSort( &p1, false );
		CHECK( queue.getTop() == &p1 );
		CHECK( 1 == queue.getPosition( &p1 ) );
		CHECK( 2 == queue.getPosition( &p3 ) );
		CHECK( 3 == queue.getPosition( &p2 ) );
	}

	/**
	 * @test odemx::awakeAll()
	 *
	 * Three non-scheduled processes are inserted into the queue.
	 *
	 * Expected function call effects:
	 * @li all three processes are now scheduled
	 * @li queue length is still 3. i.e. awaking does not remove processes
	 * from the queue
	 */
	TEST_FIXTURE( ProcessQueueFixture, AwakeAll )
	{
		CHECK( sim.getScheduler().getExecutionList().isEmpty() );

		queue.inSort( &p1 );
		queue.inSort( &p2 );
		queue.inSort( &p3 );
		CHECK_EQUAL( static_cast<unsigned int>( 3 ), queue.getLength() );

		awakeAll( &queue );
		CHECK( p1.isScheduled() );
		CHECK( p2.isScheduled() );
		CHECK( p3.isScheduled() );
		CHECK_EQUAL( static_cast<unsigned int>( 3 ), queue.getLength() );
	}

	/**
	 * @test odemx::ProcessQueue::awakeFirst()
	 *
	 * Three non-scheduled processes are inserted into the queue.
	 *
	 * Expected function call effects:
	 * @li only the first process is now scheduled
	 * @li queue length is still 3. i.e. awaking does not remove processes
	 * from the queue
	 */
	TEST_FIXTURE( ProcessQueueFixture, AwakeFirst )
	{
		CHECK( sim.getScheduler().getExecutionList().isEmpty() );

		queue.inSort( &p1 );
		queue.inSort( &p2 );
		queue.inSort( &p3 );
		CHECK_EQUAL( static_cast<unsigned int>( 3 ), queue.getLength() );

		awakeFirst( &queue );
		CHECK( p1.isScheduled() );
		CHECK( ! p2.isScheduled() );
		CHECK( ! p3.isScheduled() );
		CHECK_EQUAL( static_cast<unsigned int>( 3 ), queue.getLength() );
	}

	/**
	 * @test odemx::ProcessQueue::awakeNext(Process*)
	 *
	 * Three non-scheduled processes are inserted into the queue.
	 *
	 * Expected function call effects when called with parameter \c p1:
	 * @li only the process queued after \c p1 is now scheduled
	 * @li queue length is still 3. i.e. awaking does not remove processes
	 * from the queue
	 *
	 * Expected function call effects when called with parameter \c p2:
	 * @li the process queued after \c p2 is also scheduled
	 * @li queue length is still 3. i.e. awaking does not remove processes
	 * from the queue
	 */
	TEST_FIXTURE( ProcessQueueFixture, AwakeNext )
	{
		CHECK( sim.getScheduler().getExecutionList().isEmpty() );

		queue.inSort( &p1 );
		queue.inSort( &p2 );
		queue.inSort( &p3 );
		CHECK_EQUAL( static_cast<unsigned int>( 3 ), queue.getLength() );

		awakeNext( &queue, &p1 );
		CHECK( ! p1.isScheduled() );
		CHECK( p2.isScheduled() );
		CHECK( ! p3.isScheduled() );
		CHECK_EQUAL( static_cast<unsigned int>( 3 ), queue.getLength() );

		awakeNext( &queue, &p2 );
		CHECK( ! p1.isScheduled() );
		CHECK( p2.isScheduled() );
		CHECK( p3.isScheduled() );
		CHECK_EQUAL( static_cast<unsigned int>( 3 ), queue.getLength() );
	}

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