/**
 * @file TestResT.cpp
 * @date Jul 30, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class template ResT
 */

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

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

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

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

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

				sim.addConsumer( log );
			}
	};

	/**
	 * @test odemx::ResT 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( ResTFixture, 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 = "ResTConstructionTest2";
		{
			ResT< TokenTest > res2( sim, label, initialTokens, &observer );
			CHECK_EQUAL( &observer, res2.data::Observable< ResTObserver< 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( std::find( store2.begin(), store2.end(), *i ) != store2.end() );
			}
		}
		CHECK( log->getTraceRecord( "destroy", baseType ) );
	}

	/**
	 * @test odemx::ResT::acquire()
	 *
	 * Expected function call effects:
	 * @li a pointer to an available Token is returned
	 * @li the process is registered as owner in the token info map
	 * @li in case of interrupt, the return value is 0;
	 */
	TEST_FIXTURE( ResTFixture, AcquireSingleToken )
	{
		// create ResT with one token
		std::vector< TokenTest > init;
		init.push_back( TokenTest( 1, "ResTTestSingleToken" ) );
		ResT< TokenTest > singleRes( sim, "ResTTestSingle", init, &observer );

		// check correct token value
		ResTTaker acquirer( sim, "ResTTestSingleTaker1", singleRes, 1 );
		acquirer.activate();
		sim.step();
		CHECK_EQUAL( init.front(), *(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() );
		ResTTaker acquirer2( sim, "ResTTestSingleTaker2", singleRes, 1 );
		acquirer2.activate();
		sim.step();

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

	/**
	 * @test odemx::ResT::acquire(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 a successful call can be observed
	 * @li change of the available number of tokens can be observed
	 */
	TEST_FIXTURE( ResTFixture, AcquireMultipleSuccess )
	{
		unsigned int oldTokenCount = res->getTokenNumber();
		unsigned int takeCount = 10;
		ResTTaker acquirer( sim, "ResTTestMultipleTaker", *res, takeCount );
		acquirer.activate();
		sim.step();
		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_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::ResT::acquire(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( ResTFixture, AcquireMultipleFailure )
	{
		// ensure that this amount cannot be acquired
		unsigned int takeCount = res->getTokenNumber() + 1;
		ResTTaker acquirer( sim, "ResTTestMultipleTakerFail", *res, 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::ResT::acquire(unsigned int) warning and error
	 *
	 * Expected function call effects:
	 * @li non-Process callers cause an error message
	 * @li attempting to acquire more tokens than the limit causes a warning
	 */
	TEST_FIXTURE( ResTFixture, AcquireError )
	{
		clearErrorStream();
		res->acquire( 10 );
		CHECK( log->getErrorRecord( "ResT::acquire(): called by non-Process object", type ) );

		ResTTaker acquirer( sim, "ResTTestSingleTaker", *res, res->getTokenLimit() + 1 );
		acquirer.activate();
		sim.step();
		CHECK( log->getWarningRecord( "ResT::acquire(): requesting more than max resources will always block", type ) );
	}

	/**
	 * @test odemx::ResT::release()
	 *
	 * Expected function call effects:
	 * @li the owner in TokenInfo is reset to a null pointer
	 * @li the number of available token is increased by one
	 */
	TEST_FIXTURE( ResTFixture, ReleaseSingleToken )
	{
		ResTTaker acquirer( sim, "ResTTestSingleReleaseTaker", *res, 1, true );
		acquirer.activate();
		sim.step(); // acquire

		CHECK( acquirer.token != 0 );
		unsigned int available = res->getTokenNumber();

		sim.step(); // release
		CHECK_EQUAL( static_cast< base::Process* >( 0 ), res->getTokenInfo().find( acquirer.token )->second );
		CHECK_EQUAL( available + 1, res->getTokenNumber() );
	}

	/**
	 * @test odemx::ResT::release(vector<Token*>&) success
	 *
	 * Expected function call effects:
	 * @li for all released tokens, the owner in TokenInfo is reset to 0
	 * @li the number of available token is increased by the number of
	 * released tokens
	 * @li the change of the token count can be observed
	 * @li the successful release can be observed
	 */
	TEST_FIXTURE( ResTFixture, ReleaseMultipleSuccess )
	{
		observer.history.clear();
		unsigned int takeCount = 15;
		ResTTaker acquirer( sim, "ResTTestMultipleReleaseTaker", *res, takeCount, true );
		acquirer.activate();
		sim.step(); // acquire
		CHECK_EQUAL( takeCount, acquirer.receivedTokens.size() );
		unsigned int available = res->getTokenNumber();

		sim.step(); // release
		std::vector< TokenTest* >::iterator i;
		std::vector< TokenTest* >& received = acquirer.receivedTokens;
		for( i = received.begin(); i != received.end(); ++i )
		{
			CHECK_EQUAL( static_cast< base::Process* >( 0 ), res->getTokenInfo().find( *i )->second );
		}
		CHECK_EQUAL( available + takeCount, res->getTokenNumber() );
		CHECK( log->getTraceRecord( "change token number", baseType ) );
		CHECK( log->getTraceRecord( "release succeeded", baseType ) );
		CHECK( log->getStatisticsRecord( "count", "providers", baseType ) );
		CHECK( log->getStatisticsRecord( "update", "tokens", baseType ) );
		CHECK( observer.observed( "onChangeTokenNumber", res->getLabel(),
				toString( available ), toString( available + takeCount ) ) );
		CHECK( observer.observed( "onReleaseSucceed", res->getLabel(), toString( takeCount ) ) );
	}

	/**
	 * @test odemx::ResT::release(vector<Token*>&) failure
	 *
	 * Expected function call effects:
	 * @li unknown tokens are detected and ignored
	 * @li the number of available token is only increased by the number of
	 * successfully released tokens
	 * @li the change of the token count can be observed
	 * @li the successful release can be observed
	 * @li the release failure can be observed
	 * @li error and warning messages show why the release of tokens failed:
	 * unknown token or wrong user
	 */
	TEST_FIXTURE( ResTFixture, ReleaseMultipleFailure )
	{
		observer.history.clear();
		unsigned int takeCount = 15;
		ResTTaker acquirer( sim, "ResTTestMultipleReleaseFailTaker", *res, takeCount, true );
		acquirer.activate();
		sim.step(); // acquire

		// exchange an acquired token
		TokenTest t1( res->getTokenLimit() + 1, "UnknownToken1" );
		TokenTest* t2 = acquirer.receivedTokens.front();
		acquirer.receivedTokens.erase( acquirer.receivedTokens.begin() );
		acquirer.receivedTokens.erase( acquirer.receivedTokens.begin() );
		acquirer.receivedTokens.push_back( &t1 );

		unsigned int available = res->getTokenNumber();
		sim.step(); // release
		CHECK( log->getErrorRecord( "ResTBase::release(): token not found in token info", baseType ) );
		CHECK( log->getTraceRecord( "change token number", baseType ) );
		CHECK( log->getWarningRecord( "ResTBase::release(): could not release all tokens", baseType ) );
		CHECK( log->getTraceRecord( "release failed", baseType ) );
		CHECK( log->getTraceRecord( "release succeeded", baseType ) );
		CHECK( observer.observed( "onChangeTokenNumber", res->getLabel(),
				toString( available ), toString( available + takeCount - 2 ) ) );
		CHECK( observer.observed( "onReleaseFail", res->getLabel(), toString( 1 ) ) );
		CHECK( observer.observed( "onReleaseSucceed", res->getLabel(), toString( takeCount - 2 ) ) );

		observer.history.clear();
		res->release( t2 ); // wrong user (getCurrentSched() == 0 instead of Process*)
		CHECK( log->getErrorRecord( "ResTBase::release(): token returned by wrong user", baseType ) );
		CHECK( observer.observed( "onReleaseFail", res->getLabel(), toString( 1 ) ) );
		CHECK( observer.observed( "onReleaseSucceed", res->getLabel(), toString( 0 ) ) );
	}

	/**
	 * @test odemx::ResT::transfer(Token*,Process*,Process*)
	 *
	 * Expected function call effects:
	 * @li ownership of the token is successfully transferred and stored in
	 * token info
	 */
	TEST_FIXTURE( ResTFixture, TransferSingleToken )
	{
		ResTTaker acquirer( sim, "ResTTestSingleTransferTaker", *res, 1 );
		acquirer.activate();
		sim.step(); // acquire

		TokenTest* token = acquirer.token;
		CHECK_EQUAL( &acquirer, res->getTokenInfo().find( acquirer.token )->second );

		ResTTaker receiver( sim, "ResTTestSingleTransferReceiver", *res, 1 );
		res->transfer( token, &acquirer, &receiver );
		CHECK_EQUAL( &receiver, res->getTokenInfo().find( acquirer.token )->second );
	}

	/**
	 * @test odemx::ResT::transfer(vector<Token*>,Process*,Process*)
	 *
	 * Expected function call effects:
	 * @li ownership of all tokens is successfully transferred and stored in
	 * token info
	 */
	TEST_FIXTURE( ResTFixture, TransferMultipleSucceed )
	{
		ResTTaker acquirer( sim, "ResTTestSingleTransferTaker", *res, 10 );
		acquirer.activate();
		sim.step(); // acquire

		vector< TokenTest* > tokens = acquirer.receivedTokens;

		ResTTaker receiver( sim, "ResTTestSingleTransferReceiver", *res, 10 );
		res->transfer( tokens, &acquirer, &receiver );
		vector< TokenTest* >::const_iterator i;
		for( i = tokens.begin(); i != tokens.end(); ++i )
		{
			CHECK_EQUAL( &receiver, res->getTokenInfo().find( *i )->second );
		}
		CHECK( log->getTraceRecord( "transfer", baseType ) );
		CHECK( observer.observed( "onTransfer", res->getLabel(),
				toString( tokens.size() ), acquirer.getLabel(), receiver.getLabel() ) );
	}

	/**
	 * @test odemx::ResT::transfer(vector<Token*>,Process*,Process*) failure
	 *
	 * Expected function call effects:
	 * @li in case of failure, ownership of the token is not transferred
	 * @li if the token is unknown or owned by another Process, an error
	 * message is sent to \c errorStream()
	 */
	TEST_FIXTURE( ResTFixture, TransferFail )
	{
		ResTTaker acquirer( sim, "ResTTestTransferFailTaker", *res, 1 );
		acquirer.activate();
		sim.step(); // acquire

		TokenTest* token = acquirer.token;

		clearErrorStream();
		ResTTaker wrongOwner( sim, "ResTTestTransferFailWrongOwner", *res, 1 );
		ResTTaker receiver( sim, "ResTTestTransferFailReceiver", *res, 1 );
		res->transfer( token, &wrongOwner, &receiver );
		CHECK_EQUAL( &acquirer, res->getTokenInfo().find( acquirer.token )->second );
		CHECK( log->getErrorRecord( "ResTBase::transfer(): transfer token owned by another Process", baseType ) );

		TokenTest unknown( 99, "ResTTestTransferUnknownToken" );
		res->transfer( &unknown, &acquirer, &receiver );
		CHECK( log->getErrorRecord( "ResTBase::transfer(): transfer token not found in token info", baseType ) );
	}

	/**
	 * @test odemx::ResT::control(Token)
	 *
	 * Expected function call effects:
	 * @li the token limit is increased by one
	 * @li the new token is added to the internal store
	 * @li the first waiting Process is rescheduled to check if enough
	 * tokens are available
	 */
	TEST_FIXTURE( ResTFixture, ControlSingleToken )
	{
		ResTTaker acquirer( sim, "ResTTestControlSingleTaker", *res, res->getTokenLimit() + 1 );
		acquirer.activate();
		sim.step(); // enqueue

		unsigned int limit = res->getTokenLimit();
		TokenTest token( limit + 1, "ResTTestControlSingleToken" );
		res->control( token );
		CHECK_EQUAL( limit + 1, res->getTokenLimit() );

		const std::list< TokenTest >& store = res->getTokenStore();
		std::list< TokenTest >::const_iterator
		found =	find( store.begin(), store.end(), token );
		CHECK( found != store.end() );
		CHECK( acquirer.isScheduled() );
		CHECK( log->getTraceRecord( "add tokens", baseType ) );
		CHECK( observer.observed( "onControl", res->getLabel(), toString( 1 ) ) );
	}

	/**
	 * @test odemx::ResT::control(vector< Token >)
	 *
	 * Expected function call effects:
	 * @li the token limit is increased by the number of added tokens
	 * @li all new tokens are added to the internal store
	 * @li the first waiting Process is rescheduled to check if enough
	 * tokens are available
	 */
	TEST_FIXTURE( ResTFixture, ControlMultipleTokens )
	{
		ResTTaker acquirer( sim, "ResTTestControlMultipleTaker", *res, res->getTokenLimit() + 3 );
		acquirer.activate();
		sim.step(); // enqueue

		unsigned int limit = res->getTokenLimit();
		std::vector< TokenTest > controlTokens;
		controlTokens.push_back( TokenTest( limit + 1, "ControlToken1" ) );
		controlTokens.push_back( TokenTest( limit + 2, "ControlToken2" ) );
		controlTokens.push_back( TokenTest( limit + 3, "ControlToken3" ) );

		res->control( controlTokens );
		CHECK_EQUAL( limit + controlTokens.size(), res->getTokenLimit() );

		std::vector< TokenTest >::iterator i;
		const std::list< TokenTest >& store = res->getTokenStore();
		for( i = controlTokens.begin(); i != controlTokens.end(); ++i )
		{
			CHECK( find( store.begin(), store.end(), *i ) != store.end() );
		}
		CHECK( acquirer.isScheduled() );

		CHECK( log->getTraceRecord( "add tokens", baseType ) );
		CHECK( observer.observed( "onControl", res->getLabel(), toString( controlTokens.size() ) ) );
	}

	/**
	 * @test odemx::ResT::unControl(unsigned int) success
	 *
	 * Expected function call effects:
	 * @li if not enough tokens are available, the caller is inserted into a
	 * queue to wait
	 * @li the caller receives a vector containing the removed tokens
	 * @li the change of the token number can be observed
	 * @li the token limit is decreased by the amount of removed tokens
	 */
	TEST_FIXTURE( ResTFixture, UnControl )
	{
		ResTTaker acquirer( sim, "ResTTestUnControlTaker", *res, 1, true );
		acquirer.activate();
		sim.step(); // acquire

		unsigned int limit = res->getTokenLimit();
		ResTTestUnControl unController( sim, "ResTTestUnControlProcess", *res, limit );
		unController.activate();
		sim.step(); // enqueue
		CHECK( !unController.isScheduled() );
		CHECK_EQUAL( (std::size_t) 0, unController.receivedTokens.size() );

		sim.step(); // release
		CHECK( unController.isScheduled() );

		sim.step(); // unControl
		CHECK( unController.hasReturned() );
		CHECK_EQUAL( (std::size_t) limit, unController.receivedTokens.size() );
		CHECK( log->getTraceRecord( "remove tokens", baseType ) );
		CHECK( log->getTraceRecord( "change token number", baseType ) );
		CHECK( observer.observed( "onUnControl", res->getLabel(), toString( limit ) ) );
		CHECK( observer.observed( "onChangeTokenNumber", res->getLabel(),
				toString( limit ), toString( res->getTokenNumber() ) ) );
		CHECK_EQUAL( (unsigned int) 0, res->getTokenLimit() );
	}

	/**
	 * @test odemx::ResT::unControl(unsigned int) errors
	 *
	 * Expected function call effects:
	 * @li attempting to remove more tokens than the limit causes an error
	 * message
	 * @li attempting to call from non-Process object causes an error because
	 * the caller must be enqueue-able in a Queue
	 */
	TEST_FIXTURE( ResTFixture, UnControlErrors )
	{
		res->unControl( res->getTokenLimit() + 1 );
		CHECK( log->getErrorRecord( "ResTBase::unControl(): attempt to remove more than max tokens", baseType ) );

		res->unControl( res->getTokenLimit() );
		CHECK( log->getErrorRecord( "ResTBase::unControl(): called by non-Process object", baseType ) );
	}

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