/**
 * @file TestTimer.cpp
 * @date Aug 1, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class Timer
 */

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

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

	/**
	 * @struct TimerFixture
	 * @brief Helper struct providing set-up/tear-down of Timer tests
	 *
	 * @copydetails EventFixture
	 */
	struct TimerFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		TimerTestObserver observer;
		Timer timer;
		data::TypeInfo type;

		TimerFixture():
			sim( "TimerTestSim" ),
			log( TestLogConsumer::create() ),
			observer( false ),
			timer( sim, "TimerTest", &observer ),
			type( typeid(Timer) )
			{
				sim.addConsumer( log );
			}
	};

	/**
	 * @test odemx::Timer construction and destruction
	 *
	 * Expected effects:
	 * @li simulation context is set correctly
	 * @li Memory type is TIMER
	 * @li observer is set correctly
	 * @li construction and destruction can be observed
	 */
	TEST_FIXTURE( TimerFixture, ConstructionDestruction )
	{
		data::Label label = "TimerTestConstruction2";
		{
			Timer timer2( sim, label, &observer );
			CHECK_EQUAL( Memory::TIMER, timer2.getMemoryType() );
			CHECK_EQUAL( MAX_PRIORITY, timer2.getPriority() );
			CHECK_EQUAL( &observer, timer2.data::Observable<base::EventObserver>::getObservers().front() );
			CHECK_EQUAL( &observer, timer2.data::Observable<TimerObserver>::getObservers().front() );
			CHECK( log->getTraceRecord( "create", type ) );
			CHECK( observer.observed( "onCreate", timer2.getLabel() ) );
		}
		CHECK( log->getTraceRecord( "destroy", type ) );
	}

	/**
	 * @test odemx::Timer::setIn(base::SimTime)
	 *
	 * Expected function call effects:
	 * @li an invalid base::SimTime value causes an error message and immediate return
	 * @li the Timer is scheduled at \c now + \c delay
	 * @li the call can be observed
	 * @li the timer is set and not available for use
	 */
	TEST_FIXTURE( TimerFixture, SetIn )
	{
#ifdef ODEMX_USE_CONTINUOUS
		timer.setIn( -1 );
		CHECK( log->getErrorRecord( "Timer::setIn(): cannot schedule Timer, invalid SimTime < 0", type ) );
#endif

		base::SimTime delay = 5;
		timer.setIn( delay );
		CHECK_EQUAL( sim.getTime() + delay, timer.getExecutionTime() );
		CHECK( log->getTraceRecord( "set in", type ) );
		CHECK( observer.observed( "onSetIn", timer.getLabel(), toString( delay ) ) );
		CHECK( timer.isSet() );
		CHECK( ! timer.isAvailable() );
	}

	/**
	 * @test odemx::Timer::setAt(base::SimTime)
	 *
	 * Expected function call effects:
	 * @li an invalid base::SimTime value causes an error message and immediate return
	 * @li the Timer is scheduled at \c absoluteTime
	 * @li the call can be observed
	 * @li the timer is set and not available for use
	 */
	TEST_FIXTURE( TimerFixture, SetAt )
	{
		base::SimTime delay = 5;
		sim.setCurrentTime( delay );

		timer.setAt( delay - 1 );
		CHECK( log->getErrorRecord( "Timer::setAt(): cannot schedule Timer, SimTime already passed", type ) );

		base::SimTime absoluteTime = 99;
		timer.setAt( absoluteTime );
		CHECK_EQUAL( absoluteTime, timer.getExecutionTime() );
		CHECK( log->getTraceRecord( "set at", type ) );
		CHECK( observer.observed( "onSetAt", timer.getLabel(), toString( absoluteTime ) ) );
		CHECK( timer.isSet() );
		CHECK( ! timer.isAvailable() );
	}

	/**
	 * @test odemx::Timer::reset(base::SimTime)
	 *
	 * Expected function call effects:
	 * @li resetting an unset timer causes a warning message
	 * @li an invalid base::SimTime value causes an error message and immediate return
	 * @li the Timer is scheduled at \c now + \c delay
	 * @li the call can be observed
	 * @li the timer is set and not available for use
	 */
	TEST_FIXTURE( TimerFixture, Reset )
	{
		timer.reset( 0 );
		CHECK( log->getWarningRecord( "Timer::reset(): scheduling of idle Timer using reset", type ) );

#ifdef ODEMX_USE_CONTINUOUS
		timer.reset( -1 );
		CHECK( log->getErrorRecord( "Timer::reset(): cannot schedule Timer, invalid SimTime < 0", type ) );
#endif

		base::SimTime delay = 5;
		base::SimTime oldTimeout = timer.getExecutionTime();
		timer.reset( delay );
		CHECK_EQUAL( sim.getTime() + delay, timer.getExecutionTime() );
		CHECK( log->getTraceRecord( "reset", type ) );
		CHECK( observer.observed( "onReset", timer.getLabel(), toString( oldTimeout ), toString( delay ) ) );
		CHECK( timer.isSet() );
		CHECK( ! timer.isAvailable() );
	}

	/**
	 * @test odemx::Timer::eventAction()
	 *
	 * This function is tested indirectly by scheduling the Timer and
	 * running the Simulation.
	 *
	 * Expected function call effects:
	 * @li the call can be observed
	 * @li empty Memory causes a warning because Timers are supposed to wake
	 * processes and thus prevent deadlocks
	 * @li the timer is not set and thus available for use
	 * @li registered processes are alerted
	 */
	TEST_FIXTURE( TimerFixture, EventAction )
	{
		timer.setIn( 0 );
		sim.step();
		CHECK( observer.observed( "onTimeout", timer.getLabel() ) );
		CHECK( log->getTraceRecord( "timeout", type ) );
		CHECK( log->getWarningRecord( "Timer::eventAction(): memory empty, cannot alert any process", type ) );

		SuiteBase::ProcessTest process( sim, "TimerTestProcess" );
		base::SimTime delay = 5;
		timer.setIn( delay );
		timer.registerProcess( &process );
		sim.step();
		CHECK( process.isAlerted() );
		CHECK( ! timer.isSet() );
		CHECK( timer.isAvailable() );
	}

	/**
	 * @test odemx::Timer::stop()
	 *
	 * Expected function call effects:
	 * @li the call can be observed
	 * @li the timer is not set and thus available for use
	 * @li registered processes are removed from Memory
	 */
	TEST_FIXTURE( TimerFixture, Stop )
	{
		SuiteBase::ProcessTest process( sim, "TimerTestProcess" );
		timer.registerProcess( &process );
		timer.setIn( 0 );

		timer.stop();
		CHECK( log->getTraceRecord( "stop", type ) );
		CHECK( observer.observed( "onStop", timer.getLabel() ) );
		CHECK( ! timer.isSet() );
		CHECK( timer.isAvailable() );
		CHECK_EQUAL( (std::size_t) 0, timer.getWaiting().size() );
	}

	/**
	 * @test odemx::Timer::registerProcess(Process*)
	 *
	 * Expected function call effects:
	 * @li the call can be observed
	 * @li the process is found in the list of waiting processes
	 */
	TEST_FIXTURE( TimerFixture, RegisterProcess )
	{
		SuiteBase::ProcessTest process( sim, "TimerTestProcess" );
		timer.registerProcess( &process );
		CHECK( log->getTraceRecord( "register process", type ) );
		CHECK( observer.observed( "onRegisterProcess", timer.getLabel(), process.getLabel() ) );
		CHECK( find( timer.getWaiting().begin(), timer.getWaiting().end(), &process ) != timer.getWaiting().end() );
	}

	/**
	 * @test odemx::Timer::removeProcess(Process*)
	 *
	 * Expected function call effects:
	 * @li the call can be observed
	 * @li the process is not found in the list of waiting processes
	 */
	TEST_FIXTURE( TimerFixture, RemoveProcess )
	{
		SuiteBase::ProcessTest process( sim, "TimerTestProcess" );
		timer.registerProcess( &process );

		timer.removeProcess( &process );
		CHECK( log->getTraceRecord( "remove process", type ) );
		CHECK( observer.observed( "onRemoveProcess", timer.getLabel(), process.getLabel() ) );
		CHECK( find( timer.getWaiting().begin(), timer.getWaiting().end(), &process ) == timer.getWaiting().end() );
	}

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