/*******************************************************************
*
*  DESCRIPTION: class LocalTransitionAdmin and SingleLocalTransitionAdmin
*
*  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: 21/04/1998 (v2)
*
*******************************************************************/

// ** include files **//
#include "ltranadm.h"     // header class
#include "parser.h"
#include "neighval.h"     // NeighborhoddValue
#include "strutil.h"      // lowerCase
#include "synnode.h"      // SpecNode
#include "evaldeb.h"	  // EvalDebug

// ** public data ** //
const LocalTransAdmin::Function LocalTransAdmin::InvalidFn ;

// ** private data ** //             
LocalTransAdmin *SingleLocalTransAdmin::instance( NULL ) ;

/*******************************************************************
* Function Name: ~LocalTransAdmin() 
********************************************************************/
LocalTransAdmin::~LocalTransAdmin()
{
	for (LocalTransDB::iterator cursor = this->transDB.begin(); cursor != this->transDB.end(); cursor++ )
		delete cursor->second;
}

/*******************************************************************
* Method: LocalTransAdmin
********************************************************************/
LocalTransAdmin::LocalTransAdmin() 
: symbolTable( NULL )
{}

/*******************************************************************
* Method: evaluate
********************************************************************/
Real LocalTransAdmin::evaluate( const Function &fn, const NeighborhoodValue &neighbors, PortValues *portsVal, Time &delayTime, Time &actualtime, VirtualPortList *outPorts, Model *actModel, string portSource )
{
	Real	valueReturned, dt, lastValidValue;

	this->symbolTable = &neighbors;
	this->portTable = portsVal;
	this->ActualTime = actualtime;
	this->functionname = fn;
	this->portsource = portSource;
	this->outPortList = outPorts;
	this->evalModel = actModel;

	MASSERTMSG( this->transDB.find( fn ) != this->transDB.end(), "Funcion '" + fn + "' Not Found!");

	SpecNode *spec( (*this->transDB.find( fn )).second );

	// Throws an exception when two rules evaluate to true ( and the result or the delay is different) 
	// or no rule is satisfied!!!!
	try
	{
		if (EvalDebug().Active())
			EvalDebug().Stream() << "+---------------------------------------------------------------------------+\nNew Evaluation:\n";

		spec->resetWarningCondition();
		valueReturned = spec->evaluate() ; 

		// If there isn't any TRUE rule and exist an ElseFunction
		// then evaluate it.
		string elseFunction = spec->elseFunction();
		dt = spec->delay();
		lastValidValue = spec->value();
		register bool anyUndef =  spec->AnyUndef();
		register bool anyStochastic = spec->AnyStochastic();

		while (valueReturned.value() != 1 && elseFunction != "")
		{
			MASSERTMSG( this->transDB.find( elseFunction ) != this->transDB.end(), "Funcion '" + elseFunction + "' Not Found!\n Abort, Retry or Pizza?" );
			SpecNode *spec2( (*this->transDB.find( elseFunction )).second ) ;

			valueReturned = spec2->evaluate( anyUndef, anyStochastic );

			elseFunction = spec2->elseFunction();
			dt = spec2->delay();
			lastValidValue = spec->value();
			anyUndef =  spec2->AnyUndef();
			anyStochastic = spec2->AnyStochastic();
		}

	} catch( InvalidEvaluation &e )
	{
		e.addText( "Language used is: " + fn );
		e.addNeighborhood( neighbors );
		e.print(cerr);

		MTHROW( e );
	}

	// If the evaluation is OK (any evaluate to TRUE) then set the correct delay time
	if ( valueReturned == Real::one )
	{
		delayTime = Time::Zero ;

		MASSERTMSG( !dt.IsUndefined(), "The delay time of a rule is undefined (?)" );
		
		delayTime.mseconds( dt.value() ) ;
	}
	// else, the delay time is the default delay time.

	return Real( lastValidValue ) ;
}

/*******************************************************************
* Function Name: register
********************************************************************/
LocalTransAdmin &LocalTransAdmin::registerTransition( const string &transName, istream &transText, bool printParserInfo, bool parseForPortIn, const string &elseFunction )
{
	try
	{
		SingleParser::Instance().parse( transText, printParserInfo, parseForPortIn, elseFunction ) ;
	} catch( MException &e )
	{
		e.addLocation( MEXCEPTION_LOCATION() );
		e.addText( "Error parsing " + transName );
		throw;
	}

	MASSERTMSG( !isRegistered( transName ), transName + " exist in the function DB!" ) ;
	transDB[ lowerCase( transName ) ] = SingleParser::Instance().specification() ;

	return *this ;
}

/*******************************************************************
* Method: cellValue
********************************************************************/
const Real &LocalTransAdmin::cellValue( const NeighborPosition &pos )
{
	MASSERT( this->symbolTable ); 
	return this->symbolTable->get(pos);
}

/*******************************************************************
* Method: isRegistered
********************************************************************/
bool LocalTransAdmin::isRegistered( const Function &name ) const
{
	return this->transDB.find( lowerCase( name ) ) != this->transDB.end() ;
}

/*******************************************************************
* Method: portValue
* Description: return the value associated with a InPort
********************************************************************/
const Real &LocalTransAdmin::portValue( const string name )
{
	MASSERTMSG( this->portTable->find( name ) != this->portTable->end(), "The requested IN Port (" + name + ") not exist for some cell that uses the function '" +  functionName() + "'" );
	return (*portTable)[ name ];
}
