//------------------------------------------------------------------------------
//	Copyright (C) 2002, 2004, 2007, 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 Process.h
 * @author Ralf Gerstenberger
 * @date created at 2002/01/30
 * @brief Declaration of odemx::base::Process and observer
 * @sa Process.cpp
 * @since 1.0
 */

#ifndef ODEMX_BASE_PROCESS_INCLUDED
#define ODEMX_BASE_PROCESS_INCLUDED

#include <odemx/base/Sched.h>
#include <odemx/base/TypeDefs.h>
#include <odemx/coroutine/Coroutine.h>
#include <odemx/data/Observable.h>
#include <odemx/synchronization/IMemory.h>
#include <odemx/util/attributes.h>

namespace odemx {

// forward declarations
namespace synchronization {
class ProcessQueue;
}

namespace base {

class Simulation;
class ProcessObserver;

/** \class Process

	\ingroup base

	\author Ralf Gerstenberger

	\brief %Process is the base class for all user processes in a model.

	\note %Process supports Observation.
	\note %Process supports Trace.

	\sa Event, Condition, Selection and Simulation

	%Process is the base class for all user defined processes in a model. A user
	creates a derived class for every different type of process of its model. In
	its classes the user has to provide the behaviour by implementing the main()
	method. The behaviour is executed according to an execution schedule
	(ExecutionList) at a certain point in simulation time. If multiple processes
	share the same execution time, the order of execution is defined by the way
	the processes have been scheduled. A process can be scheduled with respect
	to its priority FirstInFirstOut or LastInFirstOut at a point in time, or
	in relation to an already scheduled partner process (immediately after or before
	that process).

	\par Example:
	\include basicProcess.cpp

	\since 1.0
*/
class Process
:	public Sched // is DataProducer, etc.
,	public coroutine::Coroutine
,	public data::Observable< ProcessObserver >
{
public:
	/**
		\brief Process states

		A process passes different process states during its life time
		(between new and delete). After construction a process is CREATED.
		In this state a process isn't scheduled for execution and hasn't been executed
		yet. In RUNNABLE a process is scheduled for execution. If a process is executed
		its state is CURRENT. In every simulation there is no more than one process
		executed at a time. A process in IDLE state is not scheduled for execution. It is
		inactive. If a process has finished or is terminated its state is TERMINATED.
		Such a process is not scheduled for execution and cannot be scheduled
		anymore.
	*/
	enum ProcessState
	{
		CREATED,	///< Initial state   0
		CURRENT,	///< Active state    1
		RUNNABLE,	///< Scheduled state 2
		IDLE,		///< Wait state      3
		TERMINATED	///< Final state     4
	};

public:

	/**
		\brief Construction for user-defined Simulation
		\param s
			pointer to the Simulation object
		\param l
			label of this object
		\param o
			initial observer
	*/
	Process( Simulation& sim, const data::Label& label, ProcessObserver* obs = 0 );

	/// Destruction
	~Process();

	/**
		\brief Get process state
		\return current process state
	*/
	ProcessState getProcessState() const;

	/**
		\name Process Scheduling

		These functions are used to schedule a process
		in simulation time.

		@{
	*/
	/**
		\brief Immediate activation

		This function schedules the process at the current time before
		all other processes with the same priority (LIFO). If the current
		process (A) does not have a higher priority than the process the
		function is called for (B) the following will happen:
		\li B is scheduled before A
		\li the state of A changes to RUNNABLE while the state
			of B changes to CURRENT
		\li the main() function of A is stopped (the point is saved)
		\li the main() function of B is executed from the point it
			has been left last time (or from the beginning)
	*/
	void activate();

	/**
		\brief Activation in (relative) time \p t
		\param t
			delay for activation

		This function schedules the process at the simtime \c now + \p t
		before all other processes with the same priority (LIFO). If
		\p t is 0 this function is equal to activate().

		\sa activate()
	*/
	void activateIn( SimTime t );

	/**
		\brief Activation at (absolute) time \p t
		\param t
			time for activation

		This function schedules the process at the simtime \p t
		before all other processes with the same priority (LIFO). If
		\p t is \c now this function is equal to activate().

		\sa activate()
	*/
	void activateAt( SimTime t );

	/**
		\brief Activation before process \p p
		\param p
			pointer to partner process

		This function schedules the process just before
		process \p p. If necessary the priority of the process
		is changed. If \p p is this process this function
		does nothing. If process \p p is not scheduled ODEMx
		reports an error.
	*/
	void activateBefore( Sched* s );

	/**
		\brief Activation after process \p p
		\param p
			pointer to refrence process

		This function schedules the process just after
		process \p p. If necessary the priority of this process
		is changed. If \p p is this process this function
		does nothing. If process \p p is not scheduled ODEMx
		reports an error.
	*/
	void activateAfter( Sched* s );

	/**
		\brief Immediate activation (FIFO)

		This function schedules the process at the current time
		after all other processes with the same priority.
	*/
	void hold();

	/**
		\brief Activation (FIFO) in (relative) time \p t

		This function schedules the process at the time \c now + \p t
		after all other processes with the same priority. If \p t
		is 0 this function is equal to hold().

		\sa hold()
	*/
	void holdFor( SimTime t );

	/**
		\brief Activation (FIFO) at (absolute) time \p t

		This function schedules the process at simtime \p t
		after all other processes with the same priority. If \p t
		is \c now this function is equal to hold().

		\sa hold()
	*/
	void holdUntil( SimTime t );

	/**
		\brief Deactivation

		This function removes the process from execution schedule
		and changes its state into IDLE. The next RUNNABLE process
		in the schedule is than activated.
	*/
	void sleep();

	/**
		\brief Interrupt

		This function interrupts the process, which results in an immediate
		activation [activate()]. A Process can determine whether it was
		interrupted by isInterrupted(). A Process must handle interrupts
		on its own. It should do so after activate... and hold... operation.

		\sa activateAt(), activateUntil(), holdFor() and holdUntil()
	*/
	virtual void interrupt();

	/**
		\brief Termination

		This function terminates the process. The process is than removed from
		execution schedule and its state is changed to TERMINATED.
	*/
	void cancel();
	//@}

	/**
		\name Priority

		If the priority is changed the position of the process in the
		execution schedule and in queues is updated. This can cause a
		switch to another process.

		@{
	*/
	/**
		\brief Get priority
		\return
			process priority

		This function returns the current process priority.
	*/
	Priority getPriority() const;

	/**
		\brief Set new priority
		\return
			previous process priority

		This function changes the process priority. The position
		in the execution schedule is affected by priority changes.
	*/
	Priority setPriority( Priority newPriority );
	//@}

	/**
		\name Execution Time

		@{
	*/
	/**
		\brief Get execution time
		\return
			execution time; 0 if not RUNNABLE or CURRENT

		This function returns the execution time of the process.
		A process is scheduled for execution at its execution
		time.
	*/
	SimTime getExecutionTime() const;
	//@}

	/**
		\name Current System State

		@{
	*/
	/**
		\brief Get currently active Sched object
		\return
			pointer to the current Sched object

		This function returns the currently active Sched object.
	*/
	Sched* getCurrentSched();

	/**
		\brief Get currently active process
		\return
			pointer to the current process

		This function returns the currently active process (the one
		in state CURRENT).
	*/
	Process* getCurrentProcess();

	/**
		\brief Get current simtime
		\deprecated substituted by Sched::getTime ()
		\return
			current simulation time

		This function returns the current simulation time by simply calling
    Process::getTime ().
	*/
	DEPRECATED(SimTime getCurrentTime() const);

	/**
		\name Interrupt handling

		An Interrupt is caused by the scheduling function interrupt().
		The interrupter (Sched object) is stored. The interrupt-state is
		automatically reset by the next scheduling function called. If the
		Process is interrupted but the interrupter is 0, then the Process
		was interrupted by the simulation (environment).

		@{
	*/
	/**
		\brief Get interrupt state
		\return
			interrupt state

		This function returns the interrupt state of the process.
	*/
	bool isInterrupted() const;

	/**
		\brief Get process which called interrupt()
		\return
			pointer to process which called interrupt()

		This function returns the process which called interrupt().
	*/
	Sched* getInterrupter();
	
	//@}


	/**
		\name Suspend and alert
		\author Ronald Kluth

		A concept of suspending a process until a resource becomes
		available or a timeout occurs.

		@{

	*/

	/**
		\brief Suspend process and wait for alert
		\param m0
			pointer to object of Memory-derived class, must be given
		\param m1
			pointer to object of Memory-derived class, optional
		\param m2
			pointer to object of Memory-derived class, optional
		\param m3
			pointer to object of Memory-derived class, optional
		\param m4
			pointer to object of Memory-derived class, optional
		\param m5
			pointer to object of Memory-derived class, optional

		\return
			pointer to alerting memo object

		This function is used to suspend a process while waiting for
		one of the given Memory objects to become available.
		That can either be a PortHead or PortTail, or a Timer event.
		The function returns the pointer to	the first available Memory object.
		If the process was interrupted, this function returns 0. Hence,
		a process should always check for interruption after calling \p wait().
	*/

	synchronization::IMemory* wait( synchronization::IMemory* m0, synchronization::IMemory* m1 = 0,
			synchronization::IMemory* m2 = 0, synchronization::IMemory* m3 = 0,
			synchronization::IMemory* m4 = 0, synchronization::IMemory* m5 = 0 );

	/**
		\brief Suspend process and wait for alert
		\param memvec
			vector containing pointers to objects of Memory-derived classes
		\return
			pointer to alerting memo object

		Overloaded version of the above function. Instead of several
		pointers to Memory objects, a vector containing all the Memory
		objects can be given as a parameter.
	*/

	synchronization::IMemory* wait( synchronization::IMemoryVector* memvec );

	/**
		\brief Reschedule process when a Memory object becomes available
		\param theAlerter
			pointer to the calling Memory object

		This function is called when Memory-derived objects call alert().
		It wakes up a suspended process and reschedules it at the
		current simulation time.
	*/
	virtual void alertProcess( synchronization::IMemory* theAlerter );

	/**
		\brief Check alert state of the process
		\return
			Alert state of the process

		This function checks the alert state of the process. It returns
		true if alertProcess() was called on this object. The library uses
		it in Process::wait() to determine whether a process was alerted
		by a Memory object.
	*/
	bool isAlerted() const;

	/**
		\brief Determine who woke the process with an alert
		\return
			pointer to the available Memory object

		This function is used in Process::wait() to determine
		the currently available Memory object.
	*/
	synchronization::IMemory* getAlerter() const;

	/**
		\brief Reset the  values alerted and alerter

		This function is used in Process::wait() to avoid the use
		of outdated alert state or a previous alerter. It is recommended
		to call this function before and after waiting and receiving
		an alert.
	*/

private:
	void genericHoldUntil(SimTime t);
	void resetAlert();
	void resetInterrupt();
	//@}

public:
	/**
		\brief main() function status
		\return
			true if the main() function has returned

		This function returns true if the main() function of
		this process has returned (is finished).
	*/
	bool hasReturned() const;

	/**
		\brief Get return value of main() function
		\return
			return value from main()

		This function returns the return value of the main()
		function. Use the hasReturned() function to check
		whether the main() function has already returned.
	*/
	int getReturnValue() const;

	/**
		\brief Get pointer to Trace
		\return
			pointer to Trace object
	*/
	//virtual data::Trace* getTrace() const;

	/** \name Queue management

		Processes can be managed in queues during a simulation.
		Typically, queues are used with FIFO-strategy. This behavior
		can be influenced by setting a separate queue priority for
		a process. However, this instrument should be handled with care
		as a change in queue priority might lead to a Process always
		being in first or last position in every queue it enters.

		Currently, each Process object can only be enqueued in one
		queue at a time.

		@{
	*/
	/**
		\brief Get queue
		\return
			pointer to ProcessQueue

		If the process is in a queue this function returns
		the pointer to the queue.
	*/
	synchronization::ProcessQueue* getQueue() const;

	/**
		\brief Get enqueue-time
		\return
			time at which the process entered a queue

		If the process is in a queue this function returns
		the time at which the process entered the queue. This
		function is used by synchronisation objects.
	*/
	SimTime getEnqueueTime() const;

	/**
		\brief Get dequeue-time
		\return
			time at which the process left a queue

		If the process was in a queue this function returns
		the time it left the queue. This function is used by
		synchronisation objects.
	*/
	SimTime getDequeueTime() const;

	/**
		\brief Get queue priority
		\return
			queue priority

		This function returns the current queue priority of this process
	*/
	Priority getQueuePriority() const;

	/**
		\brief Set new queue priority
		\param newQPriority
			the new priority value
		\param reactivate
			set to \c true to immediately activate this process
		\return
			previous queue priority

		This function changes the queue priority of this process.
		The position in queues is affected by changes of this value.
		The default is 0. If reactivate is set to true, the process
		will be scheduled and activated immediately. This is useful if
		a process is enqueued in a queue where it has to meet a certain
		condition, or if it is moved into first position by this change.
		The process will still be blocked if a required condition
		could not be fulfilled.
	*/
	Priority setQueuePriority( Priority newQPriority, bool reactivate );

	//@}


protected:
	/**
		\brief User defined process behaviour

		This function must be implemented by the user to define the
		behaviour of a process. It is executed by ODEMx when the process
		becomes the current process. If the process calls a scheduling
		function (direct or indirect, at itself or another process)
		the %main() function is delayed until the process is executed
		again. The sequence in which different processes are executed
		is determined by the execution schedule.

		\sa Process Scheduling
	*/
	virtual int main() = 0;


	//  Interface

	friend class ExecutionList;
	/**
		\brief Set execution time
		\return
			previous execution time

	 	This function is used by the library. Don't
		call it directly! Use the scheduling functions
		instead.
	*/
	SimTime setExecutionTime( SimTime time );

	friend class Simulation;
	/**
		\brief Execution of this process

		This function is used by the library. Don't
		call it directly! The execution is managed
		by the process scheduling.
	*/
	void execute();
private:

	/// Coroutine entry-point implementation
	virtual void run();

	ProcessState processState_; ///< process state
	Priority priority_; ///< process priority, influences scheduling
	SimTime executionTime_; ///< process execution time
	ProcessList::iterator simListIter_; ///< remember position in Simulation lists
	bool validReturn_; ///< return value is valid
	int returnValue_; ///< return value of main()

	synchronization::ProcessQueue* queue_; ///< pointer to queue if process is waiting
	SimTime enqueueTime_; ///< enqueue-time
	SimTime dequeueTime_; ///< dequeue-time
	Priority queuePriority_; ///< separate priority setting for queues
	ProcessList::iterator queueListIter_; ///< remember position in process queues

	bool isInterrupted_; ///< Process was interrupted
	Sched* interrupter_; ///< Process was interrupted by interrupter (0->by Simulation)
	bool isAlerted_; ///< Process was alerted
	synchronization::IMemory* alerter_; ///< Process alerter

private:
	/**
		\brief set process state
		\return true if successful
	*/
	bool setProcessState( ProcessState newState );

	friend class synchronization::ProcessQueue;
	/**
		\name ProcessQueue Management
		\note No more than one ProcessQueue at a time is supported. (provisional)

		@{
	*/
	void enqueue( synchronization::ProcessQueue* inQueue ); ///< process enters queue \p nq
	void dequeue( synchronization::ProcessQueue* outQueue ); ///< process leaves queue \p nq
	//@}

	/**
		\name Trace help functions
		@{
	*/
	/// log wait to trace with a list of memory objects
	void traceWait( synchronization::IMemoryVector& memvec );
	const data::Label& getPartner();

	static std::string stateToString( ProcessState state );
	//@}
};


/** \interface ProcessObserver

	\author RalfGerstenberger

	\brief Observer for Process specific events.

	\sa Process

	\since 1.0
*/
class ProcessObserver:
	public coroutine::CoroutineObserver
{
public:
	virtual ~ProcessObserver() {}
	virtual void onCreate( Process* sender ) {} ///< Construction
	virtual void onDestroy( Process* sender ) {} ///< Destruction

	// Activation (LIFO)
	virtual void onActivate( Process* sender ) {} ///< Immediate activation
	virtual void onActivateIn( Process* sender, SimTime t ) {} ///< Activation in
	virtual void onActivateAt( Process* sender, SimTime t ) {} ///< Activation at

	// Activation in Releation to a Partner Process
	virtual void onActivateBefore( Process* sender, Sched* p ) {} ///< Activation before
	virtual void onActivateAfter( Process* sender, Sched* p ) {} ///< Activation after

	// Activation (FIFO)
	virtual void onHold( Process* sender ) {} ///< hold
	virtual void onHoldFor( Process* sender, SimTime t ) {} ///< Hold for
	virtual void onHoldUntil( Process* sender, SimTime t ) {} ///< Hold until

	virtual void onInterrupt( Process* sender ) {} ///< Process interrupted
	virtual void onSleep( Process* sender ) {} ///< Process deactivated
	virtual void onCancel( Process* sender ) {} ///< Process terminated

	virtual void onExecute( Process* sender ) {} ///< Process execution was started or continued
	virtual void onReturn( Process* sender ) {} ///< Process successfully returned from main()

	virtual void onWait( Process* sender, synchronization::IMemoryVector* memvec ) {} ///< Process waiting for alert
	virtual void onAlert( Process* sender, synchronization::IMemory* alerter ) {} ///< Process deactivated

	/// Process state change
	virtual void onChangeProcessState( Process* sender, Process::ProcessState oldState, Process::ProcessState newState ) {}
	/// Process priority change
	virtual void onChangePriority( Process* sender, Priority oldPriority, Priority newPriority ) {}
	/// Process queue priority change
	virtual void onChangeQueuePriority( Process* sender, Priority oldQPriority, Priority newQPriority ) {}
	/// Process execution time change
	virtual void onChangeExecutionTime( Process* sender, SimTime oldExecutionTime, SimTime newExecutionTime ) {}
};

} } // namespace odemx::base

#endif /* ODEMX_BASE_PROCESS_INCLUDED */
