/*******************************************************************
*
*  DESCRIPTION: class SpecNode, VarNode and CountNode
*
*  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: 13/06/1999 (v2)
*
*******************************************************************/

// ** include files **//
#include "synnode.h"
#include "neighval.h"         // NeighborhoodValue
#include "atomcell.h"
#include "cellpos.h"
#include "limits.h"

// ** public data ** //
const RealType RealType::TheReal;
const IntType IntType::TheInt ;
const BoolType BoolType::TheBool ;

// ** public ** //
/*******************************************************************
* Method: addRule
********************************************************************/
SpecNode &SpecNode::addRule( SyntaxNode *rule, int StochasticCondition )
{
	Rule	r;
	r.Node = rule;
	r.StochasticNode = StochasticCondition != 0 ? true: false;

	rules.push_back( r ) ;
	return *this ;
}

/*******************************************************************
* Method: evaluate
********************************************************************/
Real RuleNode::evaluate()
{
	if (EvalDebug().Active())
	{
		Real aux( boolExp->evaluate() );
		EvalDebug().Stream() << "Evaluate: Rule  = " << (aux.IsUndefined() ? "Undefined" : (aux.value()==0 ? "False":"True")) << "\n";
		EvalDebug().Stream() << "\n";
		return aux;
	}
	return boolExp->evaluate();
}


/*******************************************************************
* Method: evaluate
********************************************************************/
Real SpecNode::evaluate()
{
	register int	total = 1, validRules = 0;
	register bool	avise = false;
	Real	cursorEval;

	if ( DebugCellRules().Active() )
		total = rules.size();

	RuleList::iterator cursor ;
	for( cursor = rules.begin() ; cursor != rules.end() && validRules < total ; cursor++ )
	{
		cursorEval = cursor->Node->evaluate();

		if (cursor->StochasticNode)
			anyStochastic = true;

		if( cursorEval.value() == 1 )     // 1 == TRUE
		{
			validRules++ ;

			if( validRules == 1 )
			{
				lastDelay = static_cast<RuleNode*>(cursor->Node)->delay() ;
				lastValue = static_cast<RuleNode*>(cursor->Node)->value() ; 
			} else
			{
				if( (bool)(lastValue != static_cast<RuleNode*>(cursor->Node)->value())  &&  !anyStochastic )
				{
					InvalidEvaluation e( "Two rules evaluate to TRUE and the result is different!" ) ;
					MTHROW( e ) ;
				}

				if( (bool)(lastDelay != static_cast<RuleNode*>(cursor->Node)->delay()) && !anyStochastic )
				{
					InvalidEvaluation e( "Two rules evaluate to TRUE and the delay is different!" ) ;
					MTHROW( e ) ;
				}

				if (anyStochastic && !avise)
				{
					avise = true;
					std::cout << "\nWARNING. In the stochastic model, two o more rules evaluate to TRUE.\n";
				}
			}
		}
		else if ( cursorEval.IsUndefined() )	// Value = ?
			anyUndef = true;

//		else if ( cursorEval.value() == 0 ) 	// 0 = FALSE
//				;
	}

	if ( validRules <= 0 && anyUndef )
	{
		// If the evaluation of any rule is undefined then:
		if (elseFunction() == "")
			std::cout << "Warning! - None of the rules evaluate to True, but any evaluate to undefined.\n";
		
		lastValue = Real::tundef;
		return Real::tundef;
	}
	else if ( validRules <= 0 && !anyUndef )
	{
		// IF all the rules evaluate to FALSE and none of them
		// have a 'random' like function

		if (elseFunction() == "")
		{
			if (anyStochastic)
			{
				std::cout << "Warning! - None of the rules evaluate to True in the Stochastic Model.\n";
				lastValue = Real::tundef;
				return Real::zero;
			}
			else
			{
				InvalidEvaluation e( "None of the rules evaluate to TRUE!" ) ;
				MTHROW( e ) ;
			}
		}
		else
		{
			lastValue = Real::tundef;
			return Real::zero;
		}
	}
	// If any rule evaluate to TRUE then
	return Real::one ;
}

/*******************************************************************
* Method: checkType
********************************************************************/
bool SpecNode::checkType() const
{
	RuleList::const_iterator cursor ;
	for( cursor = rules.begin() ; 
		cursor != rules.end() && cursor->Node->checkType() && cursor->Node->type().isValid( BoolType::TheBool ) ;
		cursor++ ) ;

	return cursor == rules.end() ;
}

/*******************************************************************
* Method: print
********************************************************************/
ostream &SpecNode::print( ostream &os )
{
	RuleList::iterator cursor ;
	for( cursor = rules.begin() ; cursor != rules.end() ; (*cursor++).Node->print( os ) ) ;

	return os ;
}

/*******************************************************************
* Method: evaluate
********************************************************************/
Real VarNode::evaluate()
{
	if (EvalDebug().Active())
	{
		Real valueToReturn = SingleLocalTransAdmin::Instance().cellValue( NeighborPosition( tupla ) ).value();
		EvalDebug().Stream() << "Evaluate: Cell Reference" << tupla << " = " << valueToReturn << "\n";
		return valueToReturn;
	}
	return SingleLocalTransAdmin::Instance().cellValue( NeighborPosition( tupla ) ).value() ;
}

/*******************************************************************
* Method: evaluate
********************************************************************/
Real PortRefNode::evaluate()
{
	std::string name ( static_cast<StringNode *>(portName)->getString() );

	if (name == "thisport" )
		name = SingleLocalTransAdmin::Instance().portSource();

	if (EvalDebug().Active())
	{
		Real valueToReturn = SingleLocalTransAdmin::Instance().portValue(name);
		EvalDebug().Stream() << "Evaluate: PortIn Reference(" << name << ") = " << valueToReturn << "\n";
		return valueToReturn;
	}
	return SingleLocalTransAdmin::Instance().portValue(name);
}

/*******************************************************************
* Method: evaluate
********************************************************************/
Real SendPortNode::evaluate()
{
	std::string	name( static_cast<StringNode *>(portName)->getString() );
	Real	valor( portValue->evaluate().value() );
	Time	actualTime( SingleLocalTransAdmin::Instance().actualTime() );

	if (EvalDebug().Active())
		EvalDebug().Stream() << "Evaluate: SendToPort Reference(" << name << ", " << valor << ") at time " << SingleLocalTransAdmin::Instance().actualTime() << "\n";

	const Model *am = SingleLocalTransAdmin::Instance().actualModel();
	const VirtualPortList *vpl = static_cast<const VirtualPortList *>(& (SingleLocalTransAdmin::Instance().outputPorts()) );

	((Model *) am)->sendOutput(	(const Time &) actualTime,
					(const Port &) *getPort((VirtualPortList *) vpl, name),
					valor.value(),
					NULL
				  );
	return 0;
}

/*******************************************************************
* Method: evaluate
********************************************************************/
Real AbsCellPosNode::evaluate()
{
	Real	aux = posIndex->evaluate();

	MASSERTMSG( aux.value() < INT_MAX, "The CellPos function has a parameter greater than the maximum integer allowed" );

	if (EvalDebug().Active())
		EvalDebug().Stream() << "Evaluate: CellPosition(" << (int) aux.value() << ") = " << SingleLocalTransAdmin::Instance().neighborhood().centralPosition().get( (int) aux.value() ) << "\n";

	return SingleLocalTransAdmin::Instance().neighborhood().centralPosition().get( (int) aux.value() );
}

/*******************************************************************
* Function Name: evaluate
********************************************************************/
Real CountNode::evaluate()
{
	if (evalType == toEval)
		value = sn->evaluate();

	register int total( 0 ) ;

	NeighborhoodValue *neighbors = (NeighborhoodValue *) &( SingleLocalTransAdmin::Instance().neighborhood() ) ;

	mList	*neighbor = neighbors->neighborList();

	for (neighbor->initCursor(); !neighbor->endCursor(); neighbor->next() )
		if( SingleLocalTransAdmin::Instance().cellValue( neighbor->elementCell() ) == value )
			total++ ;

	if (EvalDebug().Active())
		EvalDebug().Stream() << "Evaluate: CountNode(" << value << ") = " << total << "\n";

	return total ;
}

/*******************************************************************
* Function Name: addNeighborhood
********************************************************************/
InvalidEvaluation &InvalidEvaluation::addNeighborhood( const NeighborhoodValue &neighbors )
{
	mList	*nv;
	register int dimension = 0;

	nv = ((NeighborhoodValue *) &neighbors)->neighborList();
	nv->initCursor();

	if ( !nv->endCursor())
		dimension = nv->elementCell().dimension();

	MASSERTMSG( dimension >= 2, "The dimension must be >= 2");

	if (dimension == 2)
	{
		addText( "The state of the Neighbours is: " );

		// primero calculo el valor de 'dim'
		long	dimXMin = 9999999, dimXMax = 0;
		long	dimYMin = 9999999, dimYMax = 0;
		for (; !nv->endCursor(); nv->next() )
		{
			// Veo para 2 dimensiones (height, width)
			if ( nv->elementCell().get(DIM_WIDTH) > dimXMax )
				dimXMax = nv->elementCell().get(DIM_WIDTH);

			if ( nv->elementCell().get(DIM_WIDTH) < dimXMin )
				dimXMin = nv->elementCell().get(DIM_WIDTH);

			if ( nv->elementCell().get(DIM_HEIGHT) > dimYMax )
				dimYMax = nv->elementCell().get(DIM_HEIGHT);

			if ( nv->elementCell().get(DIM_HEIGHT) < dimYMin )
				dimYMin = nv->elementCell().get(DIM_HEIGHT);
		}	

		std::string line("+") ;
		for( int i = dimXMin ; i<= dimXMax ; i++ )
			for (int w = 0; w <= Impresion::Default.Width() + 1; w++)
				line += "-" ;
		line += "+" ;
		addText( line ) ;

		for( int i = dimYMin ; i<= dimYMax ; i++ )
		{
			line = "|" ;
			for( int j = dimXMin; j<= dimXMax; j++ ){
				nTupla	nt;
				nt.add(i);
				nt.add(j);
				line += std::string( " " );

				if (neighbors.isValid(nt))
					line += neighbors.get( NeighborPosition(nt) ).asString();
				else
					for (int w = 0; w < Impresion::Default.Width(); w++)
						line += " " ;

				line += " " ;
			}
			line += "|" ;
			addText( line ) ;
		}
	
		line = "+" ;
		for( int i = dimXMin; i<= dimXMax; i++ )
			for (int w = 0; w <= Impresion::Default.Width() + 1; w++)
				line += "-" ;
		line += "+" ;
		addText( line ) ;
	}
	else
		// Estoy en 3 o mas dimensiones
		for (nv->initCursor(); !nv->endCursor(); nv->next() )
			addText( nv->elementCell().print() + " = " + nv->elementValue()->asString() );

	return *this ;
}

/*******************************************************************
* Function Name: print
********************************************************************/
ostream &AbsCellPosNode::print( ostream &os )
{
	return os << "CellPosition " << SingleLocalTransAdmin::Instance().neighborhood().centralPosition();
}
