/**
 * @file TestBinT.cpp
 * @date Jul 26, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class template BinT
 */

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

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

	// compare TokenTest objects
	bool operator== ( const TokenTest& t1, const TokenTest& t2 )
	{
		return ( t1.id == t2.id ) && ( t1.label == t2.label );
	}

	// make TokenTest objects streamable
	std::ostream& operator<< ( std::ostream& stream, const TokenTest& t )
	{
		stream << "(" << t.id << ", " << t.label << ")";
		return stream;
	}

	/**
	 * @struct BinTFixture
	 * @brief Helper struct providing set-up/tear-down of BinT tests
	 *
	 * @copydetails EventFixture
	 */
	struct BinTFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		BinTTestObserver observer;
		BinT< TokenTest > bin;
		data::TypeInfo type;

		BinTFixture()
		:	sim( "BinTTestSim" ),
			log( TestLogConsumer::create() ),
			observer( false ),
			bin( sim, "BinTTest", &observer ),
			type( typeid(BinT< TokenTest >) )
			{
				sim.addConsumer( log );

				std::vector< TokenTest > tokens;
				for( int i = 0; i != 25; ++i )
				{
					tokens.push_back( TokenTest( i, string("BinTToken") + toString( i ) ) );
				}
				// add the tokens to the bin
				bin.give( tokens );
			}
	};

	/**
	 * @test odemx::BinT 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 initial tokens are set
	 * @li construction and destruction can be observed
	 */
	TEST_FIXTURE( BinTFixture, ConstructionDestruction )
	{
		observer.history.clear();

		data::Label label = "BinTConstructionTest";
		{
			BinT< TokenTest > bin2( sim, label, &observer );
			CHECK( log->getTraceRecord( "create", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "queue", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "initial tokens", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "token type", type ) );
			CHECK( log->getStatisticsRecord( "update", "tokens", type ) );
			CHECK( observer.observed( "onCreate", bin2.getLabel() ) );
			CHECK_EQUAL( &observer, bin2.getObservers().front() );
			CHECK_EQUAL( label, bin2.getLabel() );
			CHECK_EQUAL( (std::size_t) 0, bin2.getTokenNumber() );
		}
		CHECK( log->getTraceRecord( "create", type ) );

		std::vector< TokenTest > initialTokens;
		initialTokens.push_back( TokenTest( 1, "InitToken1" ) );
		initialTokens.push_back( TokenTest( 2, "InitToken2" ) );

		label = "BinTConstructionTest3";
		BinT< TokenTest > bin3( sim, label, initialTokens, &observer );
		CHECK_EQUAL( initialTokens.size(), bin3.getTokenNumber() );
		CHECK_EQUAL( initialTokens.front(), bin3.getTokenStore().front() );
		CHECK_EQUAL( initialTokens.back(), bin3.getTokenStore().back() );
	}

	/**
	 * @test odemx::BinT::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
	 * @li the tokens returned are the first \c takeCount elements of the
	 * tokenStore
	 */
	TEST_FIXTURE( BinTFixture, TakeSucceed )
	{
		unsigned int oldTokenNumber = bin.getTokenNumber();
		unsigned int takeCount = oldTokenNumber / 2 + 1;

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

		BinTTaker taker( sim, "BinTTestTakeSucceedProcess", bin, takeCount );
		taker.activate();
		sim.step();
		CHECK_EQUAL( oldTokenNumber - takeCount, bin.getTokenNumber() );
		CHECK_EQUAL( oldTokenNumber - takeCount, bin.getTokenStore().size() );
		CHECK( observer.observed( "onChangeTokenNumber", bin.getLabel(), \
				toString( oldTokenNumber ), toString( bin.getTokenNumber() ) ) );
		CHECK( observer.observed( "onTakeSucceed", bin.getLabel(), toString( takeCount ) ) );

		for( unsigned int i = 0; i != takeCount; ++i )
		{
			CHECK_EQUAL( i, (unsigned int)taker.received->at( i ).id );
		}

		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::BinT::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 interrupt the function returns 0
	 * @li waiting processes are awakened properly upon token increase
	 */
	TEST_FIXTURE( BinTFixture, TakeFail )
	{
		unsigned int oldTokenNumber = bin.getTokenNumber();
		unsigned int takeCount = oldTokenNumber + 1;

		BinTTaker taker( sim, "BinTTestTakeFailProcess1", bin, takeCount );
		taker.activate();
		sim.step();
		CHECK( observer.observed( "onTakeFail", bin.getLabel(), toString( takeCount ) ) );
		CHECK_EQUAL( (std::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( static_cast< std::vector< TokenTest >* >( 0 ), taker.received.get() );
		CHECK_EQUAL( (std::size_t) 0, bin.getWaitingProcesses().size() );

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

		std::vector< TokenTest > tokens;
		int last = bin.getTokenStore().back().id;
		for( unsigned int i = last; i != last + takeCount * 2; ++i )
		{
			tokens.push_back( TokenTest( i, string("BinTToken") + toString( i ) ) );
		}
		bin.give( tokens );
		sim.run();
		CHECK_EQUAL( takeCount, taker2.received->size() );
		CHECK( log->getStatisticsRecord( "update", "wait time", type ) );
		CHECK_EQUAL( takeCount, taker3.received->size() );
		CHECK_EQUAL( (std::size_t) 0, bin.getWaitingProcesses().size() );
	}

	/**
	 * @test odemx::BinT::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( BinTFixture, Give )
	{
		unsigned int oldTokenNumber = bin.getTokenNumber();
		unsigned int giveCount = oldTokenNumber + 1;

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

		std::vector< TokenTest > tokens;
		int last = bin.getTokenStore().back().id;
		for( unsigned int i = last; i != last + giveCount; ++i )
		{
			tokens.push_back( TokenTest( i, string("BinTToken") + toString( i ) ) );
		}
		bin.give( tokens );
		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->size() );
	}

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