/**
 * @file TestWait.cpp
 * @date Aug 2, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class Wait
 */

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

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

	/**
	 * @struct WaitFixture
	 * @brief Helper struct providing set-up/tear-down of Wait tests
	 *
	 * @copydetails EventFixture
	 */
	struct WaitFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		SuiteBase::ProcessTestObserver obs;
		SuiteBase::ProcessTest p1, p2, p3, p4, p5, p6;
		Wait wait;
		data::TypeInfo type;

		WaitFixture()
		:	sim( "WaitTestSim" ),
		 	log( TestLogConsumer::create() ),
			obs( false ),
			p1( sim, "WaitTestProcess1", &obs ),
			p2( sim, "WaitTestProcess2", &obs ),
			p3( sim, "WaitTestProcess3" ),
			p4( sim, "WaitTestProcess4" ),
			p5( sim, "WaitTestProcess5" ),
			p6( sim, "WaitTestProcess6" ),
			wait( sim, "WaitTest" ),
			type( typeid(Wait) )
			{
				sim.addConsumer( log );
			}
	};

	/**
	 * @test odemx::Wait construction for user-defined Simulation
	 *
	 * Expected effects:
	 * @li label is set correctly
	 * @li the given Simulation is set as context
	 * @li the wait condition is set correctly
	 * @li all given processes are added to the observation list
	 */
	TEST_FIXTURE( WaitFixture, UserSimConstruction )
	{
		data::Label l = "WaitTestUserSimConstruction1";
		{
			Wait testWait1( sim, l );
			CHECK_EQUAL( l, testWait1.getLabel() );
			CHECK_EQUAL( true, testWait1.getCondition() );
			CHECK( log->getTraceRecord( "create", type ) );
		}
		CHECK( log->getTraceRecord( "destroy", type ) );
		log->clear();

		l = "WaitTestUserSimConstruction2";
		Wait testWait2( sim, l, &p1 );
		std::list< base::Process* >& observed = const_cast< std::list< base::Process* >& >( testWait2.getWaitingProcesses() );
		CHECK_EQUAL( l, testWait2.getLabel() );
		CHECK_EQUAL( true, testWait2.getCondition() );
		CHECK( std::find( observed.begin(), observed.end(), &p1 ) != observed.end() );
		CHECK( log->getTraceRecord( "create", type ) );
		log->clear();

		l = "WaitTestUserSimConstruction3";
		Wait testWait3( sim, l, &p1, &p2 );
		observed = testWait3.getWaitingProcesses();
		CHECK_EQUAL( l, testWait3.getLabel() );
		CHECK_EQUAL( true, testWait3.getCondition() );
		CHECK( std::find( observed.begin(), observed.end(), &p1 ) != observed.end() );
		CHECK( std::find( observed.begin(), observed.end(), &p2 ) != observed.end() );
		CHECK( log->getTraceRecord( "create", type ) );
		log->clear();

		l = "WaitTestUserSimConstruction4";
		Wait testWait4( sim, l, &p1, &p2, &p3 );
		observed = testWait4.getWaitingProcesses();
		CHECK_EQUAL( l, testWait4.getLabel() );
		CHECK_EQUAL( true, testWait4.getCondition() );
		CHECK( std::find( observed.begin(), observed.end(), &p1 ) != observed.end() );
		CHECK( std::find( observed.begin(), observed.end(), &p2 ) != observed.end() );
		CHECK( std::find( observed.begin(), observed.end(), &p3 ) != observed.end() );
		CHECK( log->getTraceRecord( "create", type ) );
		log->clear();

		l = "WaitTestUserSimConstruction5";
		base::Process* processes[6] = { &p1, &p2, &p3, &p4, &p5, &p6 };
		Wait testWait5( sim, l, 6, processes );
		observed = testWait5.getWaitingProcesses();
		CHECK_EQUAL( l, testWait5.getLabel() );
		CHECK_EQUAL( true, testWait5.getCondition() );
		CHECK( std::find( observed.begin(), observed.end(), &p1 ) != observed.end() );
		CHECK( std::find( observed.begin(), observed.end(), &p2 ) != observed.end() );
		CHECK( std::find( observed.begin(), observed.end(), &p3 ) != observed.end() );
		CHECK( std::find( observed.begin(), observed.end(), &p4 ) != observed.end() );
		CHECK( std::find( observed.begin(), observed.end(), &p5 ) != observed.end() );
		CHECK( std::find( observed.begin(), observed.end(), &p6 ) != observed.end() );
		CHECK( log->getTraceRecord( "create", type ) );
		log->clear();
	}

	/**
	 * @test odemx::Wait::addProcess(base::Process*)
	 *
	 * Expected function call effects:
	 * @li the given process is added to the list
	 */
	TEST_FIXTURE( WaitFixture, AddProcess )
	{
		const std::list< base::Process* >& observed = wait.getWaitingProcesses();
		CHECK( observed.empty() );
		wait.addProcess( &p1 );

		CHECK( ! observed.empty() );
		CHECK( std::find( observed.begin(), observed.end(), &p1 ) != observed.end() );
		CHECK( log->getTraceRecord( "add process", type ) );
	}

	/**
	 * @test odemx::Wait::removeProcess(base::Process*)
	 *
	 * Expected function call effects:
	 * @li the given process is removed from the list
	 */
	TEST_FIXTURE( WaitFixture, RemoveProcess )
	{
		wait.addProcess( &p1 );
		wait.addProcess( &p2 );
		wait.addProcess( &p3 );

		const std::list< base::Process* >& observed = wait.getWaitingProcesses();
		CHECK_EQUAL( (size_t) 3, observed.size() );

		wait.removeProcess( &p2 );
		CHECK_EQUAL( (size_t) 2, observed.size() );
		CHECK( std::find( observed.begin(), observed.end(), &p2 ) == observed.end() );
		CHECK( log->getTraceRecord( "remove process", type ) );
	}

	/**
	 * @test odemx::Wait::wait() call with terminated processes
	 *
	 * Precondition: registered process are already finished
	 *
	 * Expected function call effects:
	 * @li the caller returns immediately because the waiting condition is
	 * already fulfilled
	 */
	TEST_FIXTURE( WaitFixture, WaitAllTerminated )
	{
		wait.addProcess( &p1 );
		wait.addProcess( &p2 );
		wait.addProcess( &p3 );

		p1.activate();
		p2.activate();
		p3.activate();

		sim.runUntil( 1 );

		CHECK( p1.hasReturned() );
		CHECK( p2.hasReturned() );
		CHECK( p3.hasReturned() );

		Waiter waiter( sim, "WaitTestWaitForTerminated", wait );
		waiter.activate();
		sim.step();
		CHECK( waiter.hasReturned() );
		CHECK_EQUAL( true, waiter.waitReturnValue );
		CHECK( log->getTraceRecord( "wait", type ) );
		CHECK( log->getTraceRecord( "continue", type ) );
	}

	/**
	 * @test odemx::Wait::wait() for activation upon other Processes' termination
	 *
	 * Precondition: two processes are registered for observation by a Wait object.
	 *
	 * Expected function call effects:
	 * @li the caller waits for all processes to finish and then returns
	 * @li upon success the call returns \c true
	 */
	TEST_FIXTURE( WaitFixture, WaitAll )
	{
		wait.addProcess( &p1 );
		wait.addProcess( &p2 );
		p1.activate();
		p2.activate();

		Waiter waiter( sim, "WaitTestWaitForAll", wait );
		waiter.activate();
		sim.step();
		CHECK_EQUAL( base::Process::IDLE, waiter.getProcessState() );

		sim.runUntil( 1 );
		CHECK( p1.hasReturned() );
		CHECK_EQUAL( base::Process::TERMINATED, p1.getProcessState() );
		CHECK( p2.hasReturned() );
		CHECK_EQUAL( base::Process::TERMINATED, p2.getProcessState() );
		CHECK( waiter.hasReturned() );
		CHECK_EQUAL( true, waiter.waitReturnValue );
	}

	/**
	 * @test odemx::Wait::wait() call with terminated processes
	 *
	 * Precondition: two processes are registered for observation by a Wait object.
	 *
	 * Expected function call effects:
 	 * @li the caller waits for at least one process to finish and then returns
	 * @li upon success the call returns \c true
	 */
	TEST_FIXTURE( WaitFixture, WaitOne )
	{
		wait.addProcess( &p1 );
		wait.addProcess( &p2 );
		p1.activate();

		wait.setCondition( false );
		Waiter waiter( sim, "WaitTestWaitForOne", wait );
		waiter.activate();
		sim.step();
		CHECK_EQUAL( base::Process::IDLE, waiter.getProcessState() );

		sim.runUntil( 1 );
		CHECK( p1.hasReturned() );
		CHECK_EQUAL( base::Process::TERMINATED, p1.getProcessState() );
		CHECK( waiter.hasReturned() );
		CHECK_EQUAL( true, waiter.waitReturnValue );
	}

	/**
	 * @test odemx::Wait::wait() call with interrupt
	 *
	 * Precondition: a process is registered for observation by a Wait object.
	 *
	 * Expected function call effects:
 	 * @li the caller waits for the process to finish
	 * @li upon interrupt, the call returns \c false
	 */
	TEST_FIXTURE( WaitFixture, WaitInterrupt )
	{
		wait.addProcess( &p1 );

		Waiter waiter( sim, "WaitTestWaitInterrupt", wait );
		waiter.activate();
		sim.step();
		CHECK_EQUAL( base::Process::IDLE, waiter.getProcessState() );

		SuiteBase::InterruptEvent interrupt( sim, &waiter );
		interrupt.schedule();
		sim.step();
		CHECK( waiter.isScheduled() );
		sim.step();
		CHECK( waiter.hasReturned() );
		CHECK_EQUAL( false, waiter.waitReturnValue );
	}

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