/*******************************************************************
*
*  DESCRIPTION: class TransportDelayCell
*
*  AUTHOR:    Amir Barylko & Jorge Beyoglonian 
*  Version 2: Daniel Rodriguez.
*
*  EMAIL: mailto://amir@dc.uba.ar
*         mailto://jbeyoglo@dc.uba.ar
*         mailto://drodrigu@dc.uba.ar
*
*  DATE: 27/06/1998
*  DATE: 03/07/1999 (v2)
*
*******************************************************************/

// ** include files **//
#include "tdcell.h"     // header class
#include "message.h"    // class InternalMessage
#include "coupcell.h"	// CoupledCell
#include "realfunc.h"	// calculateWithQuantum

/*******************************************************************
* Method: TransportDelayCell
********************************************************************/
TransportDelayCell::TransportDelayCell( const string &name, const LocalTransAdmin::Function &fn )
: AtomicCell( name, fn )
{}


/*******************************************************************
* Method: externalFunction
********************************************************************/
Model &TransportDelayCell::externalFunction( const ExternalMessage &msg )
{
	Time delay( static_cast<const CoupledCell&>( parent() ).defaultDelay() ) ;
	Time actualTime( msg.time() );

	Real tv ;

	// The time of the queue must be updated 
	Time elapsed( msg.time() - lastChange() );
	updateRemainingTime( elapsed );

	/////////////////////////////////////////////////////////////////////
	// if the message came from an external port we must set the value

	// If come from a neighbor
	if( msg.port() == neighborPort() ){

		VirtualPortList	*vpl = new VirtualPortList;
		getOutPorts(vpl);

		tv = SingleLocalTransAdmin::Instance().evaluate( localFunction(), neighborhood(), NULL, delay, actualTime, vpl, this ) ;

		delete vpl;
	}
	else // else the message come from an IN port
	{
		string	functionName = inputPortFunction()[ msg.port().name() ];

		// first we set the new port value in the list of PortValues
		setPortValue( msg.port().name(), msg.value() );

		if (functionName == DEFAULT_FUNCTION_InPort)
			tv = msg.value() ;
		else {	// sino es un PortInTransition valido
			VirtualPortList	*vpl = new VirtualPortList;
			getOutPorts(vpl);

			tv = SingleLocalTransAdmin::Instance().evaluate( functionName, neighborhood(), &(inputPortValues()), delay, actualTime, vpl, this, msg.port().name() ) ;

			delete vpl;
		}
	}
	/////////////////////////////////////////////////////////////////////

	/////////////////////////////////////////////////////////////////////
	if (UseQuantum().Active())
		tv = valueWithQuantum(tv, Real(UseQuantum().Value()));
	/////////////////////////////////////////////////////////////////////

	Real ltb;
	Queue::iterator cursor;

	for( cursor = queueVal.begin() ; cursor != queueVal.end() && cursor->first < delay ; cursor++ )
		;

	//cursor--;

	if (cursor == queueVal.begin())
		ltb = value();
	else
		ltb = cursor->second;

	if( ltb != tv )
	{
		cursor++;

		// En CD++ se borraba hasta el ultimo elemento.
		//queueVal.erase( cursor, queueVal.end() ) ;

		//////////////////////////////////////////////////////////
		// Ahora inserto en la cola pero ordenado por tiempo
		//////////////////////////////////////////////////////////
		// Antes hacia: (insertaba sin tener en cuenta el orden)
		//queueVal.push_back( QueueElement( delay, tv ) ) ;
		// Ahora hace: (inserta teniendo en cuenta el orden)
		Queue::iterator cursorAux;

		for ( cursorAux = queueVal.begin(); cursorAux != queueVal.end() && cursorAux->first <= delay; cursorAux++ )
			;

		queueVal.insert( cursorAux, QueueElement(delay, tv) );
		///////////////////////////////////////////////////////////////

		holdIn( active, firstQueuedTime() );
	}
	return *this ;
}

/*******************************************************************
* Method: internalFunction
********************************************************************/
Model &TransportDelayCell::internalFunction( const InternalMessage &msg )
{
	Time elapsed( msg.time() - lastChange() ) ;
	updateRemainingTime( elapsed );

	MASSERT( !queueVal.empty() && firstQueuedTime() == Time::Zero ) ;

	// Modificamos el valor de la celda
	value( firstQueuedValue() );

	queueVal.erase( queueVal.begin() );

	if( queueVal.empty() )
		passivate() ;
	else
		holdIn( active, firstQueuedTime() );

	return *this ;
}

/*******************************************************************
* Method: outputFunction
********************************************************************/
Model &TransportDelayCell::outputFunction( const InternalMessage &msg )
{
	// The cell value will be update when the coupled receives the output message
	sendOutput( msg.time(), outputPort(), firstQueuedValue().value(), NULL ) ;

	return *this ;
}

/*******************************************************************
* Function Name: firstQueuedTime
********************************************************************/
const Time & TransportDelayCell::firstQueuedTime() const
{
	return queueVal.begin()->first;
}

/*******************************************************************
* Function Name: firstQueueValue
********************************************************************/
const Real &TransportDelayCell::firstQueuedValue() const
{
	return queueVal.begin()->second ;
}

/*******************************************************************
* Function Name: updateRemainingTime
********************************************************************/
TransportDelayCell & TransportDelayCell::updateRemainingTime( const Time &elapsed )
{
	for( Queue::iterator cursor = queueVal.begin() ; cursor != queueVal.end() ; cursor++ )
		cursor->first -= elapsed ;

	return *this;
}
