/**
 * @file TestBin.cpp
 * @date Jul 23, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class Bin
 */

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

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

	/**
	 * @struct BinFixture
	 * @brief Helper struct providing set-up/tear-down of Bin tests
	 *
	 * @copydetails EventFixture
	 */
	struct BinFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		BinTestObserver observer;
		Bin bin;
		data::TypeInfo type;

		BinFixture()
		:	sim( "BinTestSim" ),
			log( TestLogConsumer::create() ),
			observer( false ),
			bin( sim, "BinTest", 25, &observer ),
			type( typeid(Bin) )
			{
				sim.addConsumer( log );
			}
	};

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

		std::size_t tokens = 99;
		data::Label label = "BinTestConstructionWithUserSim";
		{
			Bin binTest( sim, label, tokens, &observer );
			CHECK( log->getTraceRecord( "create", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "queue", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "initial tokens", type ) );
			CHECK( log->getStatisticsRecord( "update", "tokens", type ) );
			CHECK_EQUAL( &observer, binTest.getObservers().front() );
			CHECK_EQUAL( tokens, binTest.getTokenNumber() );
			CHECK_EQUAL( label, binTest.getLabel() );
			CHECK( observer.observed("onCreate", binTest.getLabel() ) );
		}
		CHECK( log->getTraceRecord( "create", type ) );
	}

	/**
	 * @test odemx::Bin::take(unsigned int) success
	 *
	 * Expected function call effects:
	 * @li non-Process objects cause an error message
	 * @li the token number is decreased by the amount taken
	 * @li token number change and take success can be observed
	 * @li on success, the function returns the amount taken
	 * @li usage is registered in statistics
	 */
	TEST_FIXTURE( BinFixture, TakeSucceed )
	{
		unsigned int oldTokenNumber = bin.getTokenNumber();
		unsigned int takeCount = oldTokenNumber / 2 + 1;

		bin.take( takeCount );
		CHECK( log->getErrorRecord( "Bin::take(): called by non-Process object", type ) );

		BinTestTaker taker( sim, "BinTestTakeProcess", bin, takeCount );
		taker.activate();
		sim.step();
		CHECK_EQUAL( oldTokenNumber - takeCount, bin.getTokenNumber() );
		CHECK( observer.observed( "onChangeTokenNumber", bin.getLabel(),
				toString( oldTokenNumber ), toString( bin.getTokenNumber() ) ) );
		CHECK( observer.observed( "onTakeSucceed", bin.getLabel(), toString( takeCount ) ) );
		CHECK_EQUAL( takeCount, taker.received );
		CHECK( log->getTraceRecord( "change token number", type ) );
		CHECK( log->getTraceRecord( "take succeeded", type ) );
		CHECK( log->getStatisticsRecord( "count", "users", type ) );
		CHECK( log->getStatisticsRecord( "update", "tokens", type ) );
	}

	/**
	 * @test odemx::Bin::take(unsigned int) failure
	 *
	 * Expected function call effects:
	 * @li a failing call can be observed
	 * @li the Process is inserted into a queue
	 * @li the Process is suspended, i.e. in state IDLE
	 * @li on Process interrupt, the function returns 0 and the Process is
	 * removed from the waiting queue
	 * @li waiting processes are awakened properly upon token increase
	 */
	TEST_FIXTURE( BinFixture, TakeFail )
	{
		unsigned int oldTokenNumber = bin.getTokenNumber();
		unsigned int takeCount = oldTokenNumber + 1;

		BinTestTaker taker( sim, "BinTestTakeProcess", bin, takeCount );
		taker.activate();
		sim.step();
		CHECK_EQUAL( (size_t)1, bin.getWaitingProcesses().size() );
		CHECK_EQUAL( &taker, bin.getWaitingProcesses().front() );
		CHECK_EQUAL( base::Process::IDLE, taker.getProcessState() );
		CHECK( log->getTraceRecord( "take failed", type ) );
		CHECK( observer.observed( "onTakeFail", bin.getLabel(), toString( takeCount ) ) );

		SuiteBase::InterruptEvent interrupt( sim, &taker );
		interrupt.schedule();
		sim.step();
		CHECK_EQUAL( base::Process::RUNNABLE, taker.getProcessState() );
		sim.step();
		CHECK_EQUAL( (unsigned int) 0, taker.received );
		CHECK_EQUAL( (std::size_t) 0, bin.getWaitingProcesses().size() );

		BinTestTaker taker2( sim, "BinTestTakeProcess", bin, takeCount );
		taker2.hold();
		BinTestTaker taker3( sim, "BinTestTakeProcess", bin, takeCount );
		taker3.hold();
		sim.step();
		sim.step();
		CHECK_EQUAL( (std::size_t) 2, bin.getWaitingProcesses().size() );

		bin.give( takeCount * 2 );
		sim.run();
		CHECK_EQUAL( takeCount, taker2.received );
		CHECK( log->getStatisticsRecord( "update", "wait time", type ) );
		CHECK_EQUAL( takeCount, taker3.received );
		CHECK_EQUAL( (std::size_t) 0, bin.getWaitingProcesses().size() );
	}

	/**
	 * @test odemx::Bin::give(unsigned int)
	 *
	 * Expected function call effects:
	 * @li token number change and give call can be observed
	 * @li waiting processes are awakened properly upon token increase
	 */
	TEST_FIXTURE( BinFixture, Give )
	{
		unsigned int oldTokenNumber = bin.getTokenNumber();
		unsigned int giveCount = oldTokenNumber + 1;

		BinTestTaker taker( sim, "BinTestTakeProcess", bin, oldTokenNumber + 1 );
		taker.hold();
		sim.step();
		CHECK_EQUAL( (std::size_t) 1, bin.getWaitingProcesses().size() );

		bin.give( giveCount );
		CHECK( observer.observed( "onChangeTokenNumber", bin.getLabel(),
				toString( oldTokenNumber ), toString( bin.getTokenNumber() ) ) );
		CHECK( observer.observed( "onGive", bin.getLabel(), toString( giveCount ) ) );
		CHECK( log->getTraceRecord( "change token number", type ) );
		CHECK( log->getTraceRecord( "give", type ) );

		CHECK( log->getStatisticsRecord( "count", "providers", type ) );
		CHECK( log->getStatisticsRecord( "update", "tokens", type ) );

		sim.run();
		CHECK_EQUAL( oldTokenNumber + 1, taker.received );
	}

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

