/**
 * @file TestResTChoice.cpp
 * @date Aug 1, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class template ResTChoice
 */

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

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

	// select tokens by their id value
	bool tokenSelect( base::Process* resTaker, TokenTest* t )
	{
		return (t->id) <= ( static_cast< ResTChoiceTaker* >(resTaker)-> tokenSelectId );
	}

	/**
	 * @struct ResTChoiceFixture
	 * @brief Helper struct providing set-up/tear-down of ResTChoice tests
	 *
	 * @copydetails EventFixture
	 */
	struct ResTChoiceFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		ResTChoiceTestObserver observer;
		std::auto_ptr< ResTChoice< TokenTest > > res;
		data::TypeInfo type;
		data::TypeInfo baseType;

		ResTChoiceFixture()
		:	sim( "ResTChoiceTestSim" ),
		 	log( TestLogConsumer::create() ),
			observer( false ),
			res( 0 ),
			type( typeid(ResTChoice<TokenTest>) ),
			baseType( typeid(ResTBase<TokenTest>) )
			{
				std::vector< TokenTest > tokens;
				for( int i = 0; i != 25; ++i )
				{
					tokens.push_back( TokenTest( i + 1, string("ResTChoiceToken") + toString( i + 1 ) ) );
				}

				// set the auto_ptr and add the initial tokens to the res
				res.reset( new ResTChoice< TokenTest >( sim, "ResTChoiceTest", tokens, &observer ) );

				sim.addConsumer( log );
			}
	};

	/**
	 * @test odemx::ResTChoice 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 and transferred to the token store
	 * @li construction and destruction can be observed
	 */
	TEST_FIXTURE( ResTChoiceFixture, ConstructionDestruction )
	{
		observer.history.clear();
		std::vector< TokenTest > initialTokens;
		initialTokens.push_back( TokenTest( 1, "InitToken1" ) );
		initialTokens.push_back( TokenTest( 2, "InitToken2" ) );
		initialTokens.push_back( TokenTest( 3, "InitToken3" ) );

		data::Label label = "ResTChoiceConstructionTest2";
		{
			ResTChoice< TokenTest > res2( sim, label, initialTokens, &observer );
			CHECK_EQUAL( &observer, res2.data::Observable< ResTChoiceObserver< TokenTest > >::getObservers().front() );
			CHECK_EQUAL( &observer, res2.data::Observable< ResTBaseObserver< TokenTest > >::getObservers().front() );
			CHECK_EQUAL( label, res2.getLabel() );
			CHECK_EQUAL( initialTokens.size(), res2.getTokenNumber() );
			CHECK_EQUAL( initialTokens.size(), res2.getTokenLimit() );
			CHECK_EQUAL( initialTokens.front(), res2.getTokenStore().front() );
			CHECK_EQUAL( initialTokens.back(), res2.getTokenStore().back() );
			CHECK( log->getTraceRecord( "create", baseType ) );
			CHECK( log->getStatisticsRecord( "parameter", "queue", baseType ) );
			CHECK( log->getStatisticsRecord( "parameter", "initial tokens", baseType ) );
			CHECK( log->getStatisticsRecord( "parameter", "initial token limit", baseType ) );
			CHECK( log->getStatisticsRecord( "update", "tokens", baseType ) );
			CHECK( observer.observed( "onCreate", res2.getLabel() ) );

			const std::list< TokenTest >& store2 = res2.getTokenStore();
			for( std::vector< TokenTest >::const_iterator i = initialTokens.begin(); i != initialTokens.end(); ++i )
			{
				CHECK( find( store2.begin(), store2.end(), *i ) != store2.end() );
			}
		}
		CHECK( log->getTraceRecord( "destroy", baseType ) );
	}

	/**
	 * @test odemx::ResTChoice::acquire(ResTChoice::Selection)
	 *
	 * Expected function call effects:
	 * @li a token that satisfies the selection function is returned
	 * @li the acquiring process is set as owner of the token
	 * @li if the acquiring process is interrupted, the function returns 0
	 */
	TEST_FIXTURE( ResTChoiceFixture, AcquireSingleToken )
	{
		// create ResT with one token
		std::vector< TokenTest > init;
		init.push_back( TokenTest( 1, "ResTChoiceTestSingleToken" ) );
		ResTChoice< TokenTest > singleRes( sim, "ResTChoiceTestSingle", init, &observer );

		ResTChoiceTaker acquirer( sim, "ResTChoiceTestSingleTaker1", singleRes, 1, &tokenSelect, 1 );
		// ensure the token satisfies the selection function

		acquirer.activate();
		sim.step(); // enqueue
		CHECK_EQUAL( init.front(), *(acquirer.token) );
		CHECK( tokenSelect( &acquirer, acquirer.token ) );
		CHECK_EQUAL( &acquirer, singleRes.getTokenInfo().find( acquirer.token )->second );
		CHECK_EQUAL( 1, acquirer.getReturnValue() );

		// check 0-return for interrupted Process
		CHECK_EQUAL( (unsigned int) 0, singleRes.getTokenNumber() );
		ResTChoiceTaker acquirer2( sim, "ResTChoiceTestSingleTaker2", singleRes, 1, &tokenSelect, 1 );
		acquirer2.activate();
		sim.step();

		SuiteBase::InterruptEvent interrupt( sim, &acquirer2 );
		interrupt.schedule();
		sim.step(); // interrupt
		sim.step(); // acquirer2
		CHECK( acquirer2.hasReturned() );
		CHECK_EQUAL( static_cast< TokenTest* >( 0 ), acquirer2.token );
	}

	/**
	 * @test odemx::ResTChoice::acquire(ResTChoice::Selection,unsigned int) success
	 *
	 * Expected function call effects:
	 * @li a vector with pointers to available tokens is returned
	 * @li the number of returned tokens equals the number of requested tokens
	 * @li the acquiring process is stored as owner in the token info map
	 * @li all returned tokens fulfill the selection condition
	 * @li a successful call can be observed
	 * @li change of the available number of tokens can be observed
	 */
	TEST_FIXTURE( ResTChoiceFixture, AcquireMultipleSuccess )
	{
		unsigned int oldTokenCount = res->getTokenNumber();
		unsigned int takeCount = 10;
		ResTChoiceTaker acquirer( sim, "ResTChoiceTestMultipleTaker", *res, takeCount, &tokenSelect, takeCount );
		acquirer.activate();
		sim.step(); // take
		CHECK( acquirer.hasReturned() );
		CHECK_EQUAL( (int)takeCount, acquirer.getReturnValue() );

		// check that the acquirer is registered with each token entry in info map
		const std::map< TokenTest*, base::Process* >& info = res->getTokenInfo();
		const std::vector< TokenTest* >& tokens = acquirer.receivedTokens;
		std::vector< TokenTest* >::const_iterator i;
		for( i = tokens.begin(); i != tokens.end(); ++i )
		{
			CHECK( tokenSelect( &acquirer, *i ) );
			CHECK_EQUAL( &acquirer, info.find( *i )->second );
		}
		CHECK( log->getTraceRecord( "change token number", type ) );
		CHECK( log->getTraceRecord( "acquire succeeded", type ) );
		CHECK( log->getStatisticsRecord( "count", "users", type ) );
		CHECK( log->getStatisticsRecord( "update", "tokens", type ) );
		CHECK( log->getStatisticsRecord( "update", "wait time", type ) );
		CHECK( observer.observed( "onAcquireSucceed", res->getLabel(), toString( takeCount ) ) );
		CHECK( observer.observed( "onChangeTokenNumber", res->getLabel(), toString( oldTokenCount ), toString( res->getTokenNumber() ) ) );
	}

	/**
	 * @test odemx::ResTChoice::acquire(ResTChoice::Selection,unsigned int) failure
	 *
	 * Expected function call effects:
	 * @li the call fails when requesting more than the available tokens
	 * @li the Process is inserted into a queue to wait for available tokens
	 * @li a failing call can be observed
	 * @li in case of interrupt, an empty vector is returned
	 */
	TEST_FIXTURE( ResTChoiceFixture, AcquireMultipleFailure )
	{
		// ensure that this amount cannot be acquired
		unsigned int takeCount = res->getTokenNumber() + 1;
		ResTChoiceTaker acquirer( sim, "ResTChoiceTestMultipleTakerFail", *res, takeCount, &tokenSelect, takeCount );
		acquirer.activate();
		sim.step();

		const std::list< base::Process* >& waiting = res->getWaitingProcesses();
		CHECK( std::find( waiting.begin(), waiting.end(), &acquirer ) != waiting.end() );
		CHECK( log->getTraceRecord( "acquire failed", type ) );
		CHECK( observer.observed( "onAcquireFail", res->getLabel(), toString( takeCount ) ) );

		SuiteBase::InterruptEvent interrupt( sim, &acquirer );
		interrupt.schedule();
		sim.step(); // execute Event
		sim.step(); // execute Process
		CHECK( acquirer.hasReturned() );
		CHECK_EQUAL( (std::size_t) 0, acquirer.receivedTokens.size() );
	}

	/**
	 * @test odemx::ResTChoice::acquire(ResTChoice::Selection,unsigned int) wait for matching
	 *
	 * Expected function call effects:
	 * @li the call fails when requesting non-available tokens
	 * @li the Process is inserted into a queue to wait for available tokens
	 * @li a failing call can be observed
	 * @li in case of release, the waiting process is reactivated to check its
	 * condition
	 * @li if enough matching tokens have become available, they are acquired
	 * successfully and the process is removed from the queue
	 */
	TEST_FIXTURE( ResTChoiceFixture, AcquireMatchingTokens )
	{
		// acquire all tokens that match id less or equal takeCount
		// will release them after the next process was executed
		unsigned int takeCount = res->getTokenLimit() / 2;
		ResTChoiceTaker dummy( sim, "ResTChoiceTestMatchingDummy", *res, takeCount, &tokenSelect, takeCount, true );
		dummy.activate();
		sim.step(); // take

		// attempt to take one token with id less or equal takeCount - 2
		// exists but is already taken...
		ResTChoiceTaker acquirer( sim, "ResTChoiceTestMatchingTaker", *res, 1, &tokenSelect, 1 );
		acquirer.activate();
		sim.step(); // enqueue

		const std::list< base::Process* >& waiting = res->getWaitingProcesses();
		CHECK( std::find( waiting.begin(), waiting.end(), &acquirer ) != waiting.end() );
		CHECK( log->getTraceRecord( "acquire failed", type ) );
		CHECK( observer.observed( "onAcquireFail", res->getLabel(), toString( 1 ) ) );
		sim.step(); // dummy token release, schedules waiting processes after dummy
		sim.step(); // take
		CHECK( std::find( waiting.begin(), waiting.end(), &acquirer ) == waiting.end() );
		CHECK( log->getTraceRecord( "acquire succeeded", type ) );
		CHECK( observer.observed( "onAcquireSucceed", res->getLabel(), toString( 1 ) ) );
		CHECK( tokenSelect( &acquirer, acquirer.token ) );
		CHECK_EQUAL( &acquirer, res->getTokenInfo().find( acquirer.token )->second );
	}

	/**
	 * @test odemx::ResTChoice::acquire() warning and error
	 *
	 * Expected function call effects:
	 * @li calls from non-Process objects cause an error message
	 * @li attempting to acquire more than max tokens causes a warning message
	 * @li attempting to acquire more matching tokens than the resource contains
	 * causes a warning message
	 */
	TEST_FIXTURE( ResTChoiceFixture, AcquireError )
	{
		res->acquire( &tokenSelect, 10 );
		CHECK( log->getErrorRecord( "ResTChoice::acquire(): called by non-Process object", type ) );

		ResTChoiceTaker acquirer( sim, "ResTChoiceTestFailTaker", *res, res->getTokenLimit() + 1, &tokenSelect, res->getTokenLimit()  );
		acquirer.activate();
		sim.step();
		CHECK( log->getWarningRecord( "ResTChoice::acquire(): requesting more than max tokens will always block", type ) );
		CHECK( log->getWarningRecord( "ResTChoice::getMatchingTokens(): too few matching tokens in resource, will always block", type ) );

		std::vector< TokenTest > init;
		init.push_back( TokenTest( 1, "ResTChoiceTestAcquireErrorToken" ) );
		ResTChoice< TokenTest > failRes( sim, "ResTChoiceTestAcquireError", init, &observer );

		ResTChoiceTaker acquirerFail( sim, "ResTTestTooFewMatchingTaker", failRes, 1, &tokenSelect, 0 );
		acquirerFail.activate();
		sim.step();
		CHECK( log->getWarningRecord( "ResTChoice::getMatchingTokens(): too few matching tokens in resource, will always block", type ) );	}

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