/**
 * @file TestBase.h
 *
 * @brief Contains all helper classes required for testing ODEMx module \b %Base.
 */

#ifndef TESTBASE_H_
#define TESTBASE_H_

#include <UnitTest++.h>
#include "../Main/Globals.h"

#include <odemx/data/SimRecordFilter.h>

#include <odemx/base/DefaultSimulation.h>
#include <odemx/base/Event.h>
#include <odemx/base/ExecutionList.h>
#include <odemx/base/Process.h>
#include <odemx/base/Sched.h>
#include <odemx/base/Simulation.h>
#include <odemx/synchronization/Timer.h>

#include <algorithm>
#include <functional>
#include <iostream>
#include <list>
#include <sstream>
#include <string>
#include <vector>

/**
 * @namespace SuiteBase
 *
 * @brief Contains helpers and tests for module ODEMx %Base
 *
 * The UnitTest++ macro SUITE( Base ) declares namespace Base.
 * Consequently, all helpers for this test suite should be placed in
 * the same namespace.
 */

namespace SuiteBase {

	using namespace odemx;
	using namespace odemx::base;
	using namespace std;

	/********************* SCHED ****************************************/

	/**
	 * @class SchedTest
	 * @brief Helper class derived from Sched for testing initialization and op< ()
	 */
	class SchedTest:
		public Sched
	{
	public:
		/// Constructor
		SchedTest( Simulation& sim, const data::Label& l, SchedType t, SimTime e, Priority p )
		:	Sched( sim, l, t ),
			execTime( e ),
			prio( p )
			{}

		/// needed for testing operator <
		virtual SimTime getExecutionTime() const { return execTime; }
		/// also needed for testing operator <
		virtual Priority getPriority() const { return prio; }

		/**
		 * @name Pure virtual functions
		 *
		 * These functions are implemented in order to make SchedTest
		 * a non-abstract class, allowing us to create SchedTest objects.
		 * No testing of these functions can be done here because subclasses
		 * redefine their behavior.
		 *
		 * @{
		 */
		virtual SimTime setExecutionTime( SimTime time ) { return 0; }
		virtual Priority setPriority( Priority newPriority ) { return 0; }
		virtual void execute() {}
		//@}
	private:
		SimTime execTime; ///< execution time
		Priority prio; ///< priority
	};

	/******************** EVENT *****************************************/

	/**
	 * @class EventTest
	 * @brief Helper class to test Event
	 */
	class EventTest:
		public Event
	{
	public:
		bool executed; ///< shows whether the event was executed

		/// Constructor for user-defined simulation
		EventTest( Simulation& sim, const data::Label& l, EventObserver* eo = 0 ):
			Event( sim, l, eo ), executed( false )
			{}
		/// simple implementation of eventAction which sets \c executed to \c true
		virtual void eventAction() { executed = true; }
	};

	/**
	 * @class EventTestObserver
	 * @brief Implements EventObserver to validate execution of Event methods
	 */
	class EventTestObserver:
		public EventObserver,
		public ObserverBase
	{
	public:
		/**
		 * @brief Constructor
		 * @param output determines whether Observations are sent to stdout
		 */
		EventTestObserver( bool output ):
			ObserverBase( output )
			{}

		/**
		 * @name Implementation of virtual Observer functions
		 *
		 * Most ODEMx classes define an Observer interface with empty
		 * implementation. These virtual functions are re-implemented here in
		 * order to observe the behavior of test objects. All of them call
		 * ObserverBase::saw() to store events with parameters in the
		 * observation history which can then be used to verify expected
		 * behavior of objects.
		 *
		 * @{
		 */
		virtual void onCreate(Event* sender) { ObserverBase::saw( "onCreate", sender ); }
		virtual void onDestroy(Event* sender) { ObserverBase::saw( "onDestroy", sender ); }
		virtual void onSchedule(Event* sender) { ObserverBase::saw( "onSchedule", sender ); }
		virtual void onScheduleAt(Event* sender, SimTime t) { ObserverBase::saw( "onScheduleAt", sender, toString( t ) ); }
		virtual void onScheduleIn(Event* sender, SimTime t) { ObserverBase::saw( "onScheduleIn", sender, toString( t ) ); }
		virtual void onScheduleAppend(Event* sender) { ObserverBase::saw( "onScheduleAppend", sender ); }
		virtual void onScheduleAppendAt(Event* sender, SimTime t) { ObserverBase::saw( "onScheduleAppendAt", sender, toString( t ) ); }
		virtual void onScheduleAppendIn(Event* sender, SimTime t) { ObserverBase::saw( "onScheduleAppendIn", sender, toString( t ) ); }
		virtual void onRemoveFromSchedule(Event* sender) { ObserverBase::saw( "onRemoveFromSchedule", sender ); }
		virtual void onExecuteEvent(Event* sender) { ObserverBase::saw( "onExecuteEvent", sender ); }
		virtual void onChangePriority(Event* sender, Priority oldPriority, Priority newPriority)
		{ ObserverBase::saw( "onChangePriority", sender, toString( oldPriority ), toString( newPriority ) ); }
		virtual void onChangeExecutionTime(Event* sender, SimTime oldExecutionTime, SimTime newExecutionTime)
		{ ObserverBase::saw( "onChangeExecutionTime", sender, toString( oldExecutionTime ), toString( newExecutionTime ) ); }
		//@}
	};

	/****************** PROCESS *******************************************/

	/**
	 * @class ProcessTest
	 * @brief Simple process subclass
	 */
	class ProcessTest:
		public Process
	{
	public:
		bool executed; ///< shows whether the process was executed

		/// Constructor for user-defined simulation
		ProcessTest( Simulation& sim, const data::Label& l, ProcessObserver* o = 0 ):
			Process( sim, l, o ), executed( false )
			{}
		/// implementation of the process behavior, sets \c executed and return value
		virtual int main() { executed = true; return 666; }
	};

	/**
	 * @class ProcessWaitTest
	 * @brief Process specialization for testing wait()
	 */
	class ProcessWaitTest:
		public Process
	{
	public:
		bool executed; ///< shows whether the process was executed
		odemx::synchronization::IMemory* waitReturnValue; ///< holds the return value after wait() call
		odemx::synchronization::IMemoryVector memvec; ///< vector of Memory objects used in wait() call

		/**
		 * @brief Constructor for user-defined simulation
		 * @param sim pointer to the simulation context
		 * @param l label of this object
		 * @param mv vector of Memory objects to be used in wait() call
		 * @param o observer of this object
		 */
		ProcessWaitTest( Simulation& sim, const data::Label& l, odemx::synchronization::IMemoryVector& mv, ProcessObserver* o = 0 ):
			Process( sim, l, o ),
			executed( false ),
			waitReturnValue( 0 ),
			memvec( mv.begin(), mv.end() )
			{}
		/**
		 * @brief Implementation of the process behavior
		 *
		 * When this process is started, \c executed is set to \c true,
		 * and wait() is called with \c memvec, causing this process to sleep
		 * until one of the Memory objects alerts it.
		 */
		virtual int main()
		{
			executed = true;
			waitReturnValue = wait( &memvec );
			return 0;
		}
	};

	/**
	 * @class InterruptEvent
	 * @brief An event class which interrupts a sleeping process
	 */
	class InterruptEvent:
		public Event
	{
	private:
		Process* theInterrupted;
	public:
		/**
		 * @brief Constructor
		 * @param sim pointer to simulation context
		 * @param p process to interrupt when this event is executed
		 */
		InterruptEvent( Simulation& sim, Process* p ):
			Event( sim, "InterruptEvent" ),
			theInterrupted( p )
			{}
		/// Execution of this method interrupts \c theInterrupted
		virtual void eventAction()
		{
			theInterrupted->interrupt();
		}
	};

	/**
	 * @class ProcessTestObserver
	 * @brief Implements ProcessObserver to validate execution of Process methods
	 */
	class ProcessTestObserver:
		public ProcessObserver,
		public ObserverBase
	{
	public:
		/**
		 * @brief Constructor
		 * @param output determines whether Observations are sent to stdout
		 */
		ProcessTestObserver( bool output ):
			ObserverBase( output )
			{}
		/// Helper method to transform ProcessState into string
		const string stateToString( Process::ProcessState ps )
		{
			switch( ps )
			{
				case Process::CREATED : return "CREATED";
				case Process::CURRENT : return "CURRENT";
				case Process::RUNNABLE : return "RUNNABLE";
				case Process::IDLE : return "IDLE";
				case Process::TERMINATED : return "TERMINATED";
			}
			return "State ERROR";
		}

		/**
		 * @name Implementation of virtual Observer functions
		 *
		 * Most ODEMx classes define an Observer interface with empty
		 * implementation. These virtual functions are re-implemented here in
		 * order to observe the behavior of test objects. All of them call
		 * ObserverBase::saw() to store events with parameters in the
		 * observation history which can then be used to verify expected
		 * behavior of objects.
		 *
		 * @{
		 */
		virtual void onCreate(Process* sender) { ObserverBase::saw( "onCreate", sender ); }
		virtual void onDestroy(Process* sender) { ObserverBase::saw( "onDestroy", sender ); }
		virtual void onActivate(Process* sender) { ObserverBase::saw( "onActivate", sender ); }
		virtual void onActivateIn(Process* sender, SimTime t) { ObserverBase::saw( "onActivateIn", sender, toString( t ) ); }
		virtual void onActivateAt(Process* sender, SimTime t) { ObserverBase::saw( "onActivateAt", sender, toString( t ) ); }
		virtual void onActivateBefore(Process* sender, Sched* p) { ObserverBase::saw( "onActivateBefore", sender, p ); }
		virtual void onActivateAfter(Process* sender, Sched* p) { ObserverBase::saw( "onActivateAfter", sender, p ); }
		virtual void onHold(Process* sender) { ObserverBase::saw( "onHold", sender ); }
		virtual void onHoldFor(Process* sender, SimTime t) { ObserverBase::saw( "onHoldFor", sender, toString( t ) ); }
		virtual void onHoldUntil(Process* sender, SimTime t) { ObserverBase::saw( "onHoldUntil", sender, toString( t ) ); }
		virtual void onInterrupt(Process* sender) { ObserverBase::saw( "onInterrupt", sender ); }
		virtual void onSleep(Process* sender) { ObserverBase::saw( "onSleep", sender ); }
		virtual void onCancel(Process* sender) { ObserverBase::saw( "onCancel", sender ); }
		virtual void onExecute(Process* sender) { ObserverBase::saw( "onExecute", sender ); }
		virtual void onReturn(Process* sender) { ObserverBase::saw( "onReturn", sender ); }
		virtual void onWait(Process* sender, odemx::synchronization::IMemoryVector* memvec)
		{
			ostringstream memos;
			for( odemx::synchronization::IMemoryVector::iterator i = memvec->begin(); i != memvec->end(); ++i )
			{
				if( i != memvec->begin() ) memos << "+";
				memos << dynamic_cast< data::Producer* >(*i)->getLabel();
			}
			ObserverBase::saw( "onWait", sender, memos.str() );
		}
		virtual void onAlert(Process* sender, odemx::synchronization::IMemory* alerter) { ObserverBase::saw( "onAlert", sender, dynamic_cast< Log::NamedElement* >( alerter ) ); }
		virtual void onChangeProcessState(Process* sender, Process::ProcessState oldState, Process::ProcessState newState)
		{
			if( oldState == Process::CURRENT && newState == Process::CURRENT )
			{
				cout << "here";
			}

			ObserverBase::saw( "onChangeProcessState", sender, stateToString( oldState ), stateToString( newState ) );

		}
		virtual void onChangeQueuePriority(Process* sender, Priority oldQPriority, Priority newQPriority)
		{ ObserverBase::saw( "onChangeQueuePriority", sender, toString( oldQPriority ), toString( newQPriority ) ); }
		virtual void onChangePriority(Process* sender, Priority oldPriority, Priority newPriority)
		{ ObserverBase::saw( "onChangePriority", sender, toString( oldPriority ), toString( newPriority ) ); }
		virtual void onChangeExecutionTime(Process* sender, SimTime oldExecutionTime, SimTime newExecutionTime)
		{ ObserverBase::saw( "onChangeExecutionTime", sender, toString( oldExecutionTime ), toString( newExecutionTime ) ); }
		//@}
	};

	/******************* SIMULATION *************************************/

	/**
	 * @class SimulationTest
	 * @brief Simple subclass of Simulation
	 */
	class SimulationTest:
		public Simulation
	{
	public:
		bool calledInit; ///< shows whether initSimulation() was called

		/// Constructor
		SimulationTest( const data::Label& l, SimulationObserver* o = 0 ):
			Simulation( l, o )
			{}

		SimulationTest( const data::Label& l, SimTime start, SimulationObserver* o = 0 ):
			Simulation( l, start, o )
			{}

		/// implementation of pure virtual function, sets \c calledInit to \c true
		virtual void initSimulation() { calledInit = true; }

		/**
		 * Protected DistContext functions made public for testing
		 * @{
		 */
		void setSeed(int n = 0)
		{
			DistContext::setSeed( n );
		}
		unsigned long getSeed()
		{
			return DistContext::getSeed();
		}
		unsigned long getNextSeed()
		{
			return DistContext::getNextSeed();
		}
		//@}
	};

	/**
	 * @class SimulationTestObserver
	 * @brief Implements SimulationObserver to validate execution of methods
	 */
	class SimulationTestObserver:
		public SimulationObserver,
		public ObserverBase
	{
	public:
		/**
		 * @brief Constructor
		 * @param output determines whether Observations are sent to stdout
		 */
		SimulationTestObserver( bool output = false ):
			ObserverBase( output )
			{}

		/// Helper method to convert a simulation list id to a string
		const string listToString( Simulation::List l )
		{
			switch( l )
			{
			case Simulation::CREATED: return "CREATED";
			case Simulation::RUNNABLE: return "RUNNABLE";
			case Simulation::IDLE: return "IDLE";
			case Simulation::TERMINATED: return "TERMINATED";
			}
			return "List ERROR";
		}

		/**
		 * @name Implementation of virtual Observer functions
		 *
		 * Most ODEMx classes define an Observer interface with empty
		 * implementation. These virtual functions are re-implemented here in
		 * order to observe the behavior of test objects. All of them call
		 * ObserverBase::saw() to store events with parameters in the
		 * observation history which can then be used to verify expected
		 * behavior of objects.
		 *
		 * @{
		 */
		virtual void onCreate(Simulation* sender) { ObserverBase::saw( "onCreate", sender ); }
		virtual void onDestroy(Simulation* sender) { ObserverBase::saw( "onDestroy", sender ); }
		virtual void onInitialization(Simulation* sender) { ObserverBase::saw( "onInitialization", sender ); }
		virtual void onExitSimulation(Simulation* sender) { ObserverBase::saw( "onExitSimulation", sender ); }
		virtual void onRun(Simulation* sender) { ObserverBase::saw( "onRun", sender ); }
		virtual void onStep(Simulation* sender) { ObserverBase::saw( "onStep", sender ); }
		virtual void onRunUntil(Simulation* sender, SimTime t) { ObserverBase::saw( "onRunUntil", sender, toString( t ) ); }
		virtual void onExecuteProcess(Simulation* sender, Process* p) { ObserverBase::saw( "onExecuteProcess", sender, p ); }
		virtual void onExecuteEvent(Simulation* sender, Event* e) { ObserverBase::saw( "onExecuteEvent", sender, e ); }
		virtual void onChangeCurrentProcess(Simulation* sender,	Process* oldCurrent, Process* newCurrent)
		{ ObserverBase::saw( "onChangeCurrentProcess", sender, oldCurrent, newCurrent ); }
		virtual void onChangeProcessList(Simulation* sender, Process* p, Simulation::List l)
		{ ObserverBase::saw( "onChangeProcessList", sender, p->getLabel(), listToString( l ) ); }
		virtual void onChangeTime(Simulation* sender, SimTime oldTime, SimTime newTime)
		{ ObserverBase::saw( "onChangeTime", sender, toString( oldTime ), toString( newTime ) ); }
		//@}
	};

}

#endif /*TESTBASE_H_*/
