//------------------------------------------------------------------------------
//	Copyright (C) 2002, 2004, 2008, 2009 Humboldt-Universitaet zu Berlin
//
//	This library is free software; you can redistribute it and/or
//	modify it under the terms of the GNU Lesser General Public
//	License as published by the Free Software Foundation; either
//	version 2.1 of the License, or (at your option) any later version.
//
//	This library is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//	Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public
//	License along with this library; if not, write to the Free Software
//	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/**
 * @file Simulation.h
 * @author Ralf Gerstenberger
 * @date created at 2002/02/22
 * @brief Declaration of odemx::base::Simulation and observer
 * @sa Simulation.cpp
 * @since 1.0
 */

#ifndef ODEMX_BASE_SIMULATION_INCLUDED
#define ODEMX_BASE_SIMULATION_INCLUDED

#include <odemx/base/Scheduler.h>
#include <odemx/base/SimTime.h>
#include <odemx/coroutine/CoroutineContext.h>
#include <odemx/data/Label.h>
#include <odemx/data/Producer.h>
#include <odemx/data/LoggingManager.h>
#include <odemx/random/DistContext.h>
#include <odemx/data/Observable.h>
#include <odemx/util/attributes.h>

#include <list>
#ifdef _MSC_VER
#include <memory>
#else
#include <tr1/memory>
#endif


// forward declaration, this class is used in unit tests
class TestSimulation;

namespace odemx {
namespace base {

// forward declarations
class Event;
class Process;
class DefaultLogConfig;
class SimulationObserver;

/** \class Simulation

	\ingroup base

	\author Ralf Gerstenberger

	\brief %Simulation is the base class for all user simulations.

	\note %Simulation supports Observation.
	\note %Simulation supports Trace.

	In a user simulation
	the method initSimulation() must be implemented to set-up the
	simulation.	run(), step() or runUntil() can be used to compute the
	simulation.	run() returns after the simulation has finished. step()
	returns after each execution of a scheduled object in the simulation.
	runUntil() returns
	after simulation time exceeds the given time or the simulation is finished.
	%Simulation also manages lists of all Process objects in the states
	CREATED, RUNNABLE, IDLE and TERMINATED.

	\par Example:
	\include matryoshka.cpp

	\since 1.0
*/
class Simulation
:	public data::LoggingManager // must be initialized first, because
,	public data::Producer // Producer needs Context for initialization
,	public coroutine::CoroutineContext
,	public random::DistContext
,	public data::Observable< SimulationObserver >
{
public:
	/// Process list identifiers
	enum List
	{
		CREATED, ///< Created processes
		RUNNABLE, ///< Runnable processes
		IDLE, ///< Idle processes
		TERMINATED ///< Terminated processes
	};

public:
	/**
		\brief Construction
		\param l
			label of this object
		\param o
			initial observer
	*/
	Simulation( const data::Label& label = "Simulation", SimulationObserver* obs = 0 );

	/**
		\brief Construction
		\param l
			label of this object
		\param o
			initial observer
	*/
	Simulation( const data::Label& label, SimTime startTime, SimulationObserver* obs = 0 );

	/// Destruction
	virtual ~Simulation();

	/// Describe the purpose of the simulation
	void setDescription( const std::string& description );

	/// Get the simulation description
	const std::string& getDescription() const;

	/**
		\name Simulation computing

		The computation of a simulation is started with one of
		the following functions. A simulation is finished when there is
		no Sched object left to be executed or the function exitSimulation()
		was called.

		@{
	*/
	/// Run simulation until it is finished
	void run();
	/// Execute one scheduled object and return
	void step();
	/// Run until the current time reaches the \c stopTime
	void runUntil( SimTime stopTime );

	/**
		\brief Simulation status
		\return
			true if simulation is finished

		This function returns true if the simulation is finished.
	*/
	bool isFinished() const;

	/**
		\brief Stop simulation

		This function stops the simulation.
	*/
	virtual void exitSimulation();

	//@}

	/**
		\name Process management

		Simulation remembers all process objects. The following
		functions can be used to get the objects in different
		process states.

		\sa Process::ProcessState

		@{
	*/
	/**
		\brief Get currently executed process
		\return
			pointer to executed process object

		This function returns a pointer to the momentarily
		executed process (the process in state CURRENT).
	*/
	Process* getCurrentProcess();

	/**
		\brief Get currently executed Sched object
		\return
			pointer to executed Sched object

		This function returns a pointer to the momentarily
		executed object. That can either be the process in
		state CURRENT, or the currently running event.
	*/
	Sched* getCurrentSched();

	void resetCurrentSched();
	void resetCurrentProcess();

	/**
		\brief Get list of processes in state CREATED
		\return
			list of processes in state CREATED

		This function returns a list of all processes
		in state CREATED.
	*/
	DEPRECATED(ProcessList& getCreatedProcesses());
	/**
		\brief Get list of processes in state CREATED
		\return
			list of processes in state CREATED

		This function returns a list of all processes
		in state CREATED.
	*/
	const ProcessList& getCreatedProcesses() const;
	/**
		\brief Get list of processes in state RUNNABLE
		\return
			list of processes in state RUNNABLE

		This function returns a list of all processes
		in state RUNNABLE.
	*/
	DEPRECATED(ProcessList& getRunnableProcesses());
	/**
		\brief Get list of processes in state RUNNABLE
		\return
			list of processes in state RUNNABLE

		This function returns a list of all processes
		in state RUNNABLE.
	*/
	const ProcessList& getRunnableProcesses() const;

	/**
		\brief Get list of processes in state IDLE
		\deprecated Please use the overloads instead.
		\return
			list of processes in state IDLE

		This function returns a list of all processes
		in state IDLE.
	*/
	DEPRECATED(ProcessList& getIdleProcesses());
	/**
		\brief Get list of processes in state IDLE
		\return
			list of processes in state IDLE

		This function returns a list of all processes
		in state IDLE.
	*/
	const ProcessList& getIdleProcesses() const;

	/**
		\brief Get list of processes in state TERMINATED
		\return
			list of processes in state TERMINATED

		This function returns a list of all processes
		in state TERMINATED.
	*/
	DEPRECATED(ProcessList& getTerminatedProcesses());
	/**
		\brief Get list of processes in state TERMINATED
		\return
			list of processes in state TERMINATED

		This function returns a list of all processes
		in state TERMINATED.
	*/
	const ProcessList& getTerminatedProcesses() const;

	/**
	 * @brief Controls whether all terminated processes are delete automatically
	 *
	 * After each object execution, the scheduler tests if this property is
	 * activated. In that case, it proceeds by deleting all processes stored
	 * in the list @c terminatedProcesses_ and clears the list.
	 *
	 * @warning This feature MUST NOT be used if you allocate processes objects
	 * on the runtime stack because @c delete calls are not allowed.
	 * @warning This feature is unsafe! It should only be used if the memory
	 * taken up by finished processes is really needed. You will not be able
	 * to access pointers or references to processes in your simulation program
	 * after process termination.
	 */
	void autoDeleteProcesses( bool value = true );
	//@}

	/**
		\brief Get current simulation time
		\return
			time now

		This function returns the current simulation time.
		The current time is determined by the current or
		last executed process. If a process is executed its
		execution time becomes the new simulation time.
	*/
	SimTime getTime() const;

	/// Used by the Scheduler to adjust the simulation time
	void setCurrentTime( SimTime newTime );

	/**
		\brief Get initial simulation time
		\return
			time start

		This function returns the initial simulation time, which
		can only be set in the constructor.
	*/
	SimTime getStartTime() const;

	/**
		\brief Get a reference to the scheduler
		\return
			reference to the scheduler
	*/
	Scheduler& getScheduler();

	/// Get the unique identifier of a simulation object
	SimulationId getId() const;

protected:
	/**
		\brief Initialization of a simulation

		Implement this method to initialize a simulation.
		One can create and activate the first processes of
		a simulation in this function. Inside this
		function the scheduling operations does not cause
		an immediate execution of a process. The first process
		is executed after this function is finished.
	*/
	virtual void initSimulation() = 0;

private:
	// needed for unit testing
	friend class TestSimulation;

	std::string description_; ///< simulation description
	bool isInitialized_; ///< simulation is initialized
	bool isStopped_; ///< simulation is stopped
	SimTime startTime_; ///< start time offset for simulation, default is 0
	SimTime currentTime_; ///< current time

	Scheduler scheduler_; ///< manages execution of events and processes
	Process* currentProcess_; ///< current process on stack
	Sched* currentSched_; ///< current scheduled object: event or process
	ProcessList createdProcesses_; ///< CREATED processes
	ProcessList runnableProcesses_; ///< RUNNABLE processes
	ProcessList idleProcesses_; ///< IDLE processes
	ProcessList terminatedProcesses_; ///< TERMINATED processes
	bool autoDelete_; ///< determines whether finished process objects are deleted
	SimulationId uniqueId_; ///< stores a unique identifier for each simulation

private:

	friend class Process;
	friend class Event;
	friend class Scheduler;

	/**
		\name Process/Event Management
		@{
	*/
	void setCurrentSched( Sched* s );
	void setCurrentProcess( Process* p );
	void setProcessAsCreated( Process* p );
	void setProcessAsRunnable( Process* p );
	void setProcessAsIdle( Process* p );
	void setProcessAsTerminated( Process* p );
	//@}

	/**
		\name Help procedures
		@{
	*/
	void init();
	void removeProcessFromList( Process* p );
	void checkAutoDelete();
	//@}
};

/** \interface SimulationObserver

	\author Ralf Gerstenberger

	\brief Observer for Simulation events

	\since 1.0
*/
class SimulationObserver
:	public coroutine::CoroutineContextObserver
{
public:
	/// Construction
	virtual void onCreate( Simulation* sender ) {}

	/// Initialisation
	virtual void onInitialization( Simulation* sender ) {}
	/// Termination
	virtual void onExitSimulation( Simulation* sender ) {}
	/// Run simulation
	virtual void onRun( Simulation* sender ) {}
	/// Step through simulation
	virtual void onStep( Simulation* sender ) {}
	/// Run simulation until time t
	virtual void onRunUntil( Simulation* sender, SimTime t ) {}

	/**
		\brief Change of current sched
		\warning
			\p oldCurrent or \p newCurrent may be 0
	*/
	virtual void onChangeCurrentSched( Simulation* sender,
					Sched* oldCurrent, Sched* newCurrent ) {}

	/// Process changed list
	virtual void onChangeProcessList( Simulation* sender,
					Process* p, Simulation::List l ) {}

	/// Time change
	virtual void onChangeTime( Simulation* sender,
					SimTime oldTime, SimTime newTime ) {}
};

} } // namespace odemx::base

#endif /* ODEMX_BASE_SIMULATION_INCLUDED */
