//----------------------------------------------------------------------------
//	Copyright (C) 2002, 2004 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 WaitQ.h

	\author Ralf Gerstenberger

	\date created at 2002/03/26

	\brief Declaration of odemx::synchronization::WaitQ and observer

	\sa WaitQ.cpp

	\since 1.0
*/

#ifndef ODEMX_WAITQ_INCLUDED
#define ODEMX_WAITQ_INCLUDED

#include <odemx/data/Producer.h>
#include <odemx/synchronization/Queue.h>
#include <odemx/data/Observable.h>

namespace odemx {

// forward declarations
namespace base {
class Process;
class Simulation;
}

namespace synchronization {

class WaitQObserver;

/** \class WaitQ

	\ingroup synch

	\author Ralf Gerstenberger

	\brief %WaitQ realises a master slave synchronisation,
	where the master gets control after successful synchronisation.

	\note WaitQ supports Observation
	\note WaitQ supports Trace
	\note WaitQ supports Report

	WaitQ realises a master slave synchronisation scheme. Two processes
	are synchronised with each other. One plays the role of a master process
	and gets control (execution) after successful synchronisation. The other
	(slave) is frozen and returned to the master process. A master process
	can provide a selection function to synchronise with particular processes
	only. If multiple processes could be used for synchronisation process-
	priority and FIFO-strategy are used to choose the process.
	WaitQ is used for general process to process synchronisation.

	\since 1.0
*/
class WaitQ
:	public data::Producer
,	public data::Observable< WaitQObserver >
{
public:

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

	/// Destruction
	~WaitQ();

	/**
		\name Master-slave synchronisation
		@{
	*/
	/**
		\brief Wait for activation by a 'master' process

		A process calling wait() is deactivated and passed
		over to a process synchronising with coopt() or avail(). If
		a slave process is interrupted before a successful synchronisation
		it is reactivated and the function returns false. (An interrupted
		slave process does not change user and waiting time statistics, but has
		influence on queue statistics.)
	*/
	bool wait();

	/**
		\brief Wait for activation by a 'master' process

		A process calling wait() is deactivated and passed
		over to a process synchronising with coopt() or avail().
		This overloaded version requires the master's weight function
		to properly synchronize a waiting slave with the master for
		which the weight function returns the highest value.
		If a slave process is interrupted before a successful synchronisation
		it is reactivated and the function returns false. (An interrupted
		slave process does not change user and waiting time statistics, but has
		influence on queue statistics.)
	*/
	bool wait( base::Weight weightFct );

	/**
		\brief Get a 'slave' process

		A master process uses coopt() to synchronise with a slave process.
		The master can provide a 'selection function' to synchronise with
		a specific slave process. Until there is a suitable slave process
		available the master process is blocked. If a blocked master process
		is interrupted the synchronisation attempt is cancelled and coopt()
		returns 0. (An interrupted master process does not change user and wait
		time statistics, but has influence on queue statistics.)

		\warning A master may not call interrupt() to reactivate a received slave().
	*/
	base::Process* coopt( base::Selection sel = 0 );

	/**
		\brief Get a 'slave' process by evaluating a weight function

		A master process uses coopt() to synchronise with a slave process.
		The master can also provide a 'weight function' to synchronise with
		a specific slave process. If there is no slave process
		available the master process is blocked. If a blocked master process
		is interrupted the synchronization attempt is cancelled and coopt()
		returns 0. (An interrupted master process does not change user and wait
		time statistics, but has influence on queue statistics.)

		\warning A master may not call interrupt() to reactivate a received slave().
	*/
	base::Process* coopt( base::Weight weightFct );

	/**
		\brief Get available slaves without blocking (optional: select slave)

		A master process can use avail() to get a suitable slave process if
		available. Otherwise avail() returns 0.

		\warning A master may not call interrupt() to reactivate a received slave().
	*/
	base::Process* avail( base::Selection sel = 0 );
	//@}

	/// List of blocked slaves
	const base::ProcessList& getWaitingSlaves() const;
	/// List of blocked masters
	const base::ProcessList& getWaitingMasters() const;

private:
	/// master management
	Queue masterQueue_;
	/// slave management
	Queue slaveQueue_;

	/// find slave
	base::Process* getSlave( base::Process* master = 0, base::Selection sel = 0 );
	/// find slave by weight
	base::Process* getWeightedSlave( base::Process* master = 0, base::Weight weightFct = 0 );
	/// statistics
	void updateStatistics( base::Process* master, base::Process* slave );
	/// master synch. implementation
	base::Process* sync( bool wait, base::Selection sel );

	/// Helper for quick access to the current simulation time
	base::SimTime getTime() const;
	/// Helper for quick access to the currently running object
	base::Sched* getCurrentSched();
	/// Helper for quick access to the current process
	base::Process* getCurrentProcess();
};

/** \interface WaitQObserver

	\author Ralf Gerstenberger

	\brief Observer for WaitQ-specific events

	\sa WaitQ

	\since 1.0
*/
class WaitQObserver
{
public:
	virtual ~WaitQObserver() {}

	/// Construction
	virtual void onCreate( WaitQ* sender ) {}

	/// Process is synchronising as slave
	virtual void onWait( WaitQ* sender, base::Process* slave ) {}
	/// Process is synchronising as slave
	virtual void onWaitWeight( WaitQ* sender, base::Process* slave ) {}
	/// Process is blocked in synchronisation as master

	virtual void onCooptFail( WaitQ* sender, base::Process* master ) {}
	/// Process succeeds in synchronisation as master, getting slave
	virtual void onCooptSucceed( WaitQ* sender, base::Process* master,
			base::Process* slave ) {}
	/// master Process doesn't find any slave
	virtual void onAvailFail( WaitQ* sender, base::Process* master ) {}
	/// master Process find some slave
	virtual void onAvailSucceed( WaitQ* sender, base::Process* master,
			base::Process* slave ) {}

	/// Process is blocked in synchronisation as master for a particular slave
	virtual void onCooptSelFail( WaitQ* sender, base::Process* master ) {}
	/// Process succeeds in synchronisation as master, getting a particular slave
	virtual void onCooptSelSucceed( WaitQ* sender, base::Process* master,
			base::Process* slave ) {}
	/// master Process doesn't find a particular slave
	virtual void onAvailSelFail( WaitQ* sender, base::Process* master ) {}
	/// master Process find a particular slave
	virtual void onAvailSelSucceed( WaitQ* sender, base::Process* master,
			base::Process* slave ) {}

	/// Process is blocked in synchronisation as master for a particular slave
	virtual void onCooptWeightFail( WaitQ* sender, base::Process* master ) {}
	/// Process succeeds in synchronisation as master, getting a particular slave
	virtual void onCooptWeightSucceed( WaitQ* sender, base::Process* master,
			base::Process* slave ) {}
};

} } // namespace odemx::synchronization

#endif
