/**
 * @file TestCondQ.cpp
 * @date Jul 26, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class CondQ
 */

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

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

	/**
	 * @struct CondQFixture
	 * @brief Helper struct providing set-up/tear-down of CondQ tests
	 *
	 * @copydetails EventFixture
	 */
	struct CondQFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		CondQTestObserver observer;
		CondQ condQ;
		data::TypeInfo type;

		CondQFixture()
		:	sim( "CondQTestSim" ),
			log( TestLogConsumer::create() ),
			observer( false ),
			condQ( sim, "CondQTest", &observer ),
			type( typeid(CondQ) )
			{
				sim.addConsumer( log );
			}
	};

	/**
	 * @test odemx::CondQ construction and destruction
	 *
	 * Expected effects:
	 * @li observer is set correctly
	 * @li label is set
	 * @li simulation environment is set for DefaultSimulation or user-definied
	 * simulation
	 * @li construction and destruction can be observed
	 */
	TEST_FIXTURE( CondQFixture, ConstructionDestruction )
	{
		observer.history.clear();

		data::Label label = "CondQConstructionTest2";
		{
			CondQ testCondQ2( sim, label, &observer );
			CHECK( log->getTraceRecord( "create", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "queue", type ) );
			CHECK( observer.observed( "onCreate", testCondQ2.getLabel() ) );
			CHECK_EQUAL( &observer, testCondQ2.getObservers().front() );
			CHECK_EQUAL( label, testCondQ2.getLabel() );
		}
		CHECK( log->getTraceRecord( "destroy", type ) );
	}

	bool dummyCond( base::Process* p ){ return true; }

	/**
	 * @test odemx::CondQ::wait(Condition*)
	 *
	 * Expected function call effects:
	 * @li the call can be observed
	 * @li both waiting processes are added to the list
	 * @li a process with fulfilled condition is reactivated and the
	 * function returns \c true
	 * @li successful continuation of a process can be observed
	 * @li the statistics are updated
	 * @li for interrupted processes, the function returns \c false
	 * @li processes are removed from the list
	 */
	TEST_FIXTURE( CondQFixture, Wait )
	{
		bool success = condQ.wait( (base::Condition)&CondQTestProcess::condition );
		CHECK( log->getErrorRecord( "CondQ::wait(): called by non-Process object", type ) );
		CHECK( ! success );

		CondQTestProcess p1( sim, "CondQTestWaitProcess1", condQ );
		CondQTestProcess p2( sim, "CondQTestWaitProcess2", condQ );
		p1.hold();
		p2.hold();
		sim.step();
		sim.step();
		CHECK( p1.running );
		CHECK( observer.observed( "onWait", condQ.getLabel(), p1.getLabel() ) );
		CHECK( log->getTraceRecord( "wait", type ) );
		CHECK( ! p1.reactivated );
		CHECK( p2.running );
		CHECK( observer.observed( "onWait", condQ.getLabel(), p2.getLabel() ) );
		CHECK( log->getTraceRecord( "wait", type ) );
		CHECK( ! p2.reactivated );
		CHECK_EQUAL( (std::size_t) 2, condQ.getWaitingProcesses().size() );
		CHECK( find( condQ.getWaitingProcesses().begin(), condQ.getWaitingProcesses().end(), &p1 )
				!= condQ.getWaitingProcesses().end() );
		CHECK( find( condQ.getWaitingProcesses().begin(), condQ.getWaitingProcesses().end(), &p2 )
				!= condQ.getWaitingProcesses().end() );

		// make the condition check return true
		p2.conditionValue = true;
		condQ.signal();
		sim.step();
		sim.step();
		CHECK( ! p1.reactivated );
		CHECK( p2.reactivated );
		CHECK( p2.waitReturnValue == true );
		CHECK( observer.observed( "onContinue", condQ.getLabel(), p2.getLabel() ) );
		CHECK( log->getTraceRecord( "continue", type ) );
		CHECK( log->getStatisticsRecord( "update", "wait time", type ) );
		CHECK( log->getStatisticsRecord( "count", "users", type ) );
		CHECK( find( condQ.getWaitingProcesses().begin(), condQ.getWaitingProcesses().end(), &p2 )
				== condQ.getWaitingProcesses().end() );
		p1.interrupt();
		CHECK( p1.isInterrupted() );
		sim.step();
		CHECK( p1.reactivated );
		CHECK( p1.waitReturnValue == false );
		CHECK( find( condQ.getWaitingProcesses().begin(), condQ.getWaitingProcesses().end(), &p1 )
				== condQ.getWaitingProcesses().end() );
	}

	/**
	 * @test odemx::CondQ::signal()
	 *
	 * Expected function call effects:
	 * @li the call can be observed
	 * @li the statistics are updated
	 * @li all enqueued processes are rescheduled to check their
	 * condition function again
	 */
	TEST_FIXTURE( CondQFixture, Signal )
	{
		CondQTestProcess p1( sim, "CondQTestSignalProcess1", condQ );
		CondQTestProcess p2( sim, "CondQTestSignalProcess2", condQ );
		p1.hold();
		p2.hold();
		sim.step();
		sim.step();

		CondQSignal signalInvoker( sim, "CondQTestSignalInvoker", condQ );
		signalInvoker.activate();
		sim.step();

		condQ.signal();

		// signal marks the current process, which should be 0,
		// since this is a call from outside the simulation
		CHECK( log->getTraceRecord( "signal", type ) );
		CHECK( observer.observed( "onSignal", condQ.getLabel(), signalInvoker.getLabel() ) );
		CHECK( log->getStatisticsRecord( "count", "signals", type ) );
		CHECK( p1.isScheduled() );
		CHECK( p2.isScheduled() );
	}

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