//------------------------------------------------------------------------------
//	Copyright (C) 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 PortTailImpl.h
 * @author Ronald Kluth
 * @date created at 2007/04/04
 * @brief Declaration and Implementation of odemx::synchronization::PortTailT
 * @see PortHeadImpl.h, PortImpl.h
 * @since 2.0
 */

#ifndef ODEMX_SYNC_PORTTAIL_INCLUDED
#define ODEMX_SYNC_PORTTAIL_INCLUDED

#include <odemx/base/Process.h>
#include <odemx/base/Simulation.h>
#include <odemx/base/DefaultSimulation.h>
#include <odemx/synchronization/Memory.h>
#include <odemx/synchronization/port/PortMode.h>
#include <odemx/synchronization/port/PortImpl.h>
#include <odemx/data/Observable.h>

namespace odemx {
namespace synchronization {

// forward declarations
template < typename > class PortHeadT;
template < typename > class PortTailTObserver;

/**
 * @ingroup synch
 * @author Ronald Kluth
 * @brief %PortTailT provides write access to a port
 * @see PortHeadT, PortT
 *
 * This class manages write access to a limited internal buffer.
 * It can be run in three different error modes according to
 * PortMode. Calling @c put on a full port will either block
 * the sender (and alert him upon availability), report an error,
 * or return zero.
 *
 * @since 2.0
 */
template < typename ElementT >
class PortTailT
:	public Memory	// is a data::Producer
,	public data::Observable< PortTailTObserver< ElementT > >
,	public std::tr1::enable_shared_from_this< PortTailT< ElementT > >
{
public:
	/// Type of the elements the port can store
	typedef ElementT ElementType;
	/// Storage type of the internal buffer
	typedef typename PortT< ElementType >::StorageType StorageType;
	/// Size type of the internal buffer
	typedef typename PortT< ElementType >::SizeType SizeType;
	/// Pointer type to manage port tail objects
	typedef std::tr1::shared_ptr< PortTailT< ElementType > > Ptr;
	/// Pointer type to manage port head objects
	typedef std::tr1::shared_ptr< PortHeadT< ElementType > > HeadPtr;
	/// Type of the compatible port head observer
	typedef PortTailTObserver< ElementType > ObserverType;

	/**
	 * @brief Creation for user-defined Simulation
	 * @param sim Reference to the simulation context
	 * @param label Label of the port, the tail's label will be set to "label (tail)"
	 * @param mode Port tail's mode, default is WAITING_MODE
	 * @param limit Maximum number of elements, default is 10000
	 * @param obs Initial observer
	 * @return Shared pointer to a newly constructed port tail
	 */
	static Ptr create( base::Simulation& sim, const data::Label& label,
			PortMode mode = WAITING_MODE, SizeType limit = 10000, ObserverType* obs = 0 )
	{
		Ptr rv( new PortTailT( sim, label, mode, limit, obs ) );
		rv->initPort();
//		rv->port_.reset( new PortType( sim, label, limit ) );
//		rv->port_->setTail( typename PortType::TailPtr( rv ) );
		return rv;
	}

	/// Destruction, managed by @c shared_ptr
	virtual ~PortTailT()
	{
		ODEMX_TRACE << log( "destroy" ).scope( typeid( PortTailT< ElementType > ) );
		ODEMX_OBS_T( ObserverType, Destroy( this ) );
	}

	/**
		\brief Port write access
		\param newElement The element to be inserted into the port
		\return @c 1 upon success, @c 0 in @c ZERO_MODE or interrupt(), else @c -1

		This function is used to insert an element into a port. It also
		responds according to the tail's PortMode. In @c WAITING_MODE, the
		calling Process will be suspended if the Port is already full. When
		space becomes available, waiting callers will be alerted in a
		FIFO-manner. If a blocked caller is interrupted, the Process will be
		activated immediately and put() returns 0.
	*/
	int put( const ElementType& newElement )
	{
		ODEMX_TRACE << log( "put" ).detail( "partner", getPartner() )
				.scope( typeid( PortTailT< ElementType > ) );

		ODEMX_OBS_T( ObserverType, Put( this, newElement ) );

		while( port_->isFull() )
		{
			base::Sched* caller = getSimulation().getCurrentSched();

			switch ( mode_ )
			{
			case WAITING_MODE:
				if ( caller == 0 )
				{
					error << log( "PortTailT::put(): port in waiting mode called from environment" )
							.scope( typeid( PortTailT< ElementType > ) );
					return -1;
				}

				if ( caller->getSchedType() == base::Sched::PROCESS )
				{
					Memory::remember( caller );
					dynamic_cast< base::Process* >( caller )->sleep();

					// check for interruption of waiting period
					if( dynamic_cast< base::Process* >( caller )->isInterrupted() )
					{
						Memory::forget( caller );
						return 0;
					}
				}
				else if ( caller->getSchedType() == base::Sched::EVENT )
				{
					// error: cannot send an event to sleep
					error << log( "PortTailT::put(): waiting mode not allowed for events" )
							.scope( typeid( PortTailT< ElementType > ) );
					return -1;
				}
				break;

			case ERROR_MODE:
				error << log( "PortTailT::put(): cannot add element to full port" )
						.scope( typeid( PortTailT< ElementType > ) );
				return -1;

			case ZERO_MODE:
				return 0;
			}
		}

		// insert new element
		port_->put( newElement );

		// notify waiting Processes of newly inserted element
		if( port_->hasHead() )
		{
			HeadPtr head = getHead();
			if( head->waiting() )
			{
				head->alert();
			}
		}
		return 1; // success
	}

	/**
		\brief Get corresponding port head
		\return Pointer to the corresponding head to this tail

		This function provides access to the head of a port tail.
		If it does not exist yet, it is created.
	*/
	HeadPtr getHead()
	{
		HeadPtr head = port_->getHead();

		// check if the pointer is empty
		if( ! head )
		{
			// create new Head
			head.reset( new PortHeadT< ElementType >( getSimulation(),
					port_->getLabel(), mode_, port_->getCapacity() ) );

			// register new Head with Tail's Port
			port_->setHead( typename PortType::HeadPtr( head ) );

			// register Tail's internal Port with the Head
			head->port_ = this->port_;
		}
		return head;
	}

	/**
		\brief Get maximum capacity of the internal port
		\return Capacity of the buffer

		This function is used to determine the maximum number of
		elements that can be buffered by the internal port.

		\sa setMaxCapacity()
	*/
	SizeType getMaxCapacity()
	{
		return port_->getCapacity();
	}

	/**
		\brief Change maximum capacity of the port
		\param limit The new maximum capacity of the internal port

		This function allows to change the maximum number of elements the
		internal buffer can hold.

		\sa getMaxCapacity()
	*/
	void setMaxCapacity( SizeType limit )
	{
		ODEMX_TRACE << log( "change max capacity" )
				.valueChange( port_->getCapacity(), limit )
				.scope( typeid( PortTailT< ElementType > ) );

		ODEMX_OBS_ATTR_T( ObserverType, MaxCapacity, port_->getCapacity(), limit );

		port_->setCapacity( limit );
	}

	/// Determine the port tail's mode
	PortMode getMode() const
	{
		return mode_;
	}

	/// Change the port tail's mode
	void setMode( PortMode newMode )
	{
		ODEMX_TRACE << log( "change mode" ).valueChange( toString( mode_ ), toString( newMode ) )
				.scope( typeid( PortTailT< ElementType > ) );

		ODEMX_OBS_ATTR_T( ObserverType, Mode, mode_, newMode );

		mode_ = newMode;
	}

	/**
		\brief Get current number of port elements
		\return Current number of elements

		This method returns the current number of buffered elements.
	*/
	SizeType count() const
	{
		return port_->getElementCount();
	}

	/**
		\brief Check for availability of buffer space
		\return @c true if the port is not full

		This function is used to determine whether there is space
		available in the internal port.
	*/
	virtual bool isAvailable()
	{
		return ! port_->isFull();
	}

	/**
		\brief Cut this port tail from its head
		\return New port tail

		This function will create a	new internal port, which
		is attached to this tail. A new head for that port must then be
		requested via getHead(). Furthermore, a new port tail will
		be attached to the old internal port. That new tail will be
		returned.

		This provides the ability to insert a process into
		the middle of a port connection, for example as a filter.

		\sa splice()
	*/
	Ptr cut()
	{
		// change
		//      old_Port_head -- old_Port -- old_Port_tail
		// into
		//      old_Port_head -- old_Port -- new_Port_tail
		//      (__get new head___) new_Port -- old_Port_tail
		// where
		//      old_Port_tail == this
		// return
		//      new_Port_tail.

		ODEMX_TRACE << log( "cut" ).detail( "partner", getPartner() )
				.scope( typeid( PortTailT< ElementType > ) );

		ODEMX_OBS_T( ObserverType, Cut( this ) );

		// remember old Port
		PortPtr oldPort = port_;

		// create a new Tail and thereby a new Port
		Ptr newTail( new PortTailT< ElementType >(
				getSimulation(),
				data::Label( "Cut " ) + oldPort->getLabel(),
				mode_, oldPort->getCapacity() ) );

		// get the new Port
		PortPtr newPort = newTail->port_;

		// put the new tail on the old Port
		oldPort->setTail( typename PortType::TailPtr( newTail ) );
		newTail->port_ = oldPort;

		// put the old tail (==this) on the new Port
		newPort->setTail( typename PortType::TailPtr( this->shared_from_this() ) );
		port_ = newPort;

		return newTail;
	}

	/**
		\brief Splice two separate ports together
		\param oldHead
			the tail of the port to which this head's buffer will be attached

		This function is used to join two ports together. This tail must
		be downstream of, i.e. positioned after, oldHead's port, which
		this buffer will be joined with. The contents of this Port
		is added to oldHead's Port.

		\warning Some objects become invalid with this operation!
		This tail, oldHead, and this Port should not be accessed anymore after
		calling this method.

		\note This method does not check the maximum Port capacity before
		splicing lists.

		\sa cut()
	*/
	void splice( HeadPtr oldHead )
	{
		// this PortTail is supposed to be downstream to the PortHead oldHead:
		// >> headsTail -- headsPort -- oldHead >> thisTail -- tailsPort -- tailsHead >>

		ODEMX_TRACE << log( "splice" ).detail( "partner", getPartner() )
				.scope( typeid( PortTailT< ElementType > ) );

		ODEMX_OBS_T( ObserverType, Splice( this, oldHead ) );

		oldHead->splice( this->shared_from_this() );
	}

	/**
		\brief Splice internal lists of two separate ports together
		\param head The head of the port whose buffer will be added to this
		\param append Determines whether to append or prepend head's contents

		This function is used to join two ports' buffer contents together
		in the same way as std::list::splice(). The contents of the \p
		tail PortT will be moved to \p this port, where parameter \p append
		can	be used to specify whether the data will appended or prepended
		to this port. Default is append.

		\note This function does not check the maximum Port capacity before
		splicing lists.
	*/
	void cppSplice( HeadPtr head, bool append = true )
	{
		ODEMX_TRACE << log( "cpp splice" ).detail( "partner", getPartner() )
				.scope( typeid( PortTailT< ElementType > ) );

		ODEMX_OBS_T( ObserverType, CppSplice( this, head, append ) );

		if( append )
		{
			port_->spliceBuffer( head->port_->getBuffer(), true );
		}
		else
		{
			port_->spliceBuffer( head->port_->getBuffer(), false );
		}
	}

	/**
		\brief Determine free space of the port
		\return Number of elements that the port can still take in

		This function returns the remaining available space
		of the internal buffer.
	*/
	SizeType getAvailableSpace()
	{
		return port_->getCapacity() - port_->getElementCount();
	}

	/// Get the storage buffer, allows write access for splicing
	const StorageType& getBuffer() const
	{
		return const_cast< const StorageType& >( port_->getBuffer() );
	}

private:

	friend class PortHeadT< ElementType >;

	/**
		\brief Alert the first stored process

		This function is designed to wake up suspended processes,
		i.e. reschedule them at simulation time \c now when they
		are waiting for a full port to become available again. Only
		the first remembered process is alerted by the port tail.
	*/
	virtual void alert()
	{
		// simpler implementation than in Memory because put() does
		// some checking already and only the first waiting Process is alerted

		if( ! Memory::waiting() )
		{
			// warning: no object to wake
			if ( mode_ != ZERO_MODE )
			{
				warning << log( "PortTailT::alert(): no process saved" )
						.scope( typeid( PortTailT< ElementType > ) );
			}
			return;
		}

		// put() allows WAITING_MODE only for Processes, so cast is okay here
		base::Process* rememberedObject =
			static_cast< base::Process* >( Memory::getWaiting().front() );

		ODEMX_TRACE << log( "alert" ).detail( "partner", rememberedObject->getLabel() )
				.scope( typeid( PortTailT< ElementType > ) );

		ODEMX_OBS_T( ObserverType, Alert( this, rememberedObject ) );

		// wake up the process
		rememberedObject->alertProcess( this );
		Memory::forget( rememberedObject );
	}

	/// Get the label of the currently active Sched object, or the simulation
	const data::Label& getPartner()
	{
		// return the current process or event, if one is active
		if( getSimulation().getCurrentSched() != 0 )
		{
			return getSimulation().getCurrentSched()->getLabel();
		}
		else
		{
			return getSimulation().getLabel();
		}
	}

private:

	/// Type of the internal port object
	typedef PortT< ElementType > PortType;
	/// Pointer type to manage the internal PortT object
	typedef std::tr1::shared_ptr< PortType > PortPtr;

	PortPtr port_; ///< Pointer to the internal port object
	PortMode mode_; ///< Mode for read requests

protected:
	/// Construction private, use @c create instead
	PortTailT( base::Simulation& sim, const data::Label& label,
			PortMode mode = WAITING_MODE, SizeType limit = 10000,
			ObserverType* obs = 0 )
	:	Memory( sim, label + " (tail)", IMemory::PORTTAIL )
	,	data::Observable< ObserverType >( obs )
	,	mode_( mode )
	{
		ODEMX_TRACE << log( "create" ).scope( typeid( PortTailT< ElementType > ) );
		ODEMX_OBS_T( ObserverType, Create( this ) );

		this->port_.reset( new PortType( getSimulation(), label, limit ) );
	}
	/// Connect the port tail to the internal buffer, needed after construction
	void initPort()
	{
		this->port_->setTail( this->shared_from_this() );
	}
private:
	/// No copying
	PortTailT( const PortTailT& );
	/// No assignment
	PortTailT& operator=( const PortTailT& );
};


/**
 * @interface PortTailTObserver
 * @author Ronald Kluth
 * @brief Observer for PortTailT-specific calls.
 * @see PortTailT
 * @since 2.0
 */
template < typename ElementT >
class PortTailTObserver: public MemoryObserver {
public:
	typedef ElementT ElementType;
	typedef typename PortT< ElementType >::SizeType SizeType;

	virtual ~PortTailTObserver() {}

	virtual void onCreate( PortTailT< ElementType >* sender ) {} ///< Construction
	virtual void onDestroy( PortTailT< ElementType >* sender ) {} ///< Destruction

	virtual void onPut( PortTailT< ElementType >* sender, const ElementType& newElement ) {}
	virtual void onCut( PortTailT< ElementType >* sender ) {}
	virtual void onSplice( PortTailT< ElementType >* sender, typename PortTailT< ElementType >::HeadPtr oldHead ) {}
	virtual void onCppSplice( PortTailT< ElementType >* sender, typename PortTailT< ElementType >::HeadPtr oldHead, bool append ) {}
	virtual void onAlert( PortTailT< ElementType >* sender, base::Sched* alerted ) {}

	/// PortTail mode change
	virtual void onChangeMode( PortTailT< ElementType >* sender, PortMode oldMode, PortMode newMode ) {}
	/// PortTail capacity change
	virtual void onChangeMaxCapacity( PortTailT< ElementType >* sender, SizeType oldMax, SizeType newMax ) {}
};

} } // namespace odemx::synchronization

#endif /* ODEMX_SYNC_PORTTAIL_INCLUDED */
