#include "gamestate.h"

#include "game.h"

#include "astleaf.h"
#include "astleafliteral.h"
#include "smlnode.h"
#include "interface.h"

#include <gats/types.h>

#include <bu/sio.h>
using namespace Bu;

GameState::GameState( Game *pGame, Interface *pIface ) :
	pGame( pGame ),
	pIface( pIface ),
	bRunning( true ),
	bReturnOnly( false )
{
}

GameState::~GameState()
{
}

Gats::Dictionary *GameState::toGats() const
{
	Gats::Dictionary *pRoot = new Gats::Dictionary;

	Gats::Dictionary *pSit = pRoot->insertDict("situations");
	for( ScopeHash::const_iterator i = hsSituation.begin(); i; i++ )
	{
		pSit->insert( i.getKey(), scopeToGats( *i ) );
	}
	pRoot->insert("global", scopeToGats( &sGlobal ) );
	pRoot->insert("player", scopeToGats( &sPlayer ) );
	pRoot->insert("curSituation", sCurSituation );
	pRoot->insert("curPrompt", sPrompt );

	return pRoot;
}

void GameState::fromGats( Gats::Dictionary *pRoot )
{
	sPrompt = pRoot->getStr("curPrompt");
	sCurSituation = pRoot->getStr("curSituation");
	gatsToScope( pRoot->getDict("global"), &sGlobal );
	gatsToScope( pRoot->getDict("player"), &sPlayer );
	for( Gats::Dictionary::iterator i = pRoot->getDict("situations")->begin();
		 i; i++ )
	{
		Scope *pSc = new Scope();
		gatsToScope( dynamic_cast<Gats::Dictionary *>(*i), pSc );
		hsSituation.insert( i.getKey(), pSc );
	}
}

Gats::Object *GameState::scopeToGats( const Scope *pSrc ) const
{
	Gats::Dictionary *pDst = new Gats::Dictionary();
	for( Scope::const_iterator i = pSrc->begin(); i; i++ )
	{
		pDst->insert( i.getKey(), variableToGats( *i ) );
	}

	return pDst;
}

Gats::Object *GameState::variableToGats( const Variable &rVar ) const
{
	Gats::List *pLst = new Gats::List();
	pLst->append( rVar.getType() );
	switch( rVar.getType() )
	{
		case Variable::tNull:
			break;

		case Variable::tBool:
			pLst->append( new Gats::Boolean( rVar.getBool() ) );
			break;

		case Variable::tInt:
			pLst->append( rVar.getInt() );
			break;

		case Variable::tFloat:
			pLst->append( rVar.getFloat() );
			break;

		case Variable::tSituation:
			pLst->append( rVar.getString() );
			break;

		case Variable::tVariable:
			pLst->append( rVar.getVariableRef().sid );
			pLst->append( rVar.getVariableRef().sName );
			break;

		case Variable::tVarPtr:
			sio << "No!!!" << sio.nl;
			break;

		case Variable::tList:
			{
				Gats::List *pSub = pLst->appendList();
				for( Variable::VariableArray::const_iterator i =
					 rVar.getList().begin(); i; i++ )
				{
					pSub->append( variableToGats( *i ) );
				}
			}
			break;

		case Variable::tDictionary:
			{
				Gats::List *pSub = pLst->appendList();
				for( Variable::VariableHash::const_iterator i =
					 rVar.getHash().begin(); i; i++ )
				{
					pSub->append( variableToGats( i.getKey() ) );
					pSub->append( variableToGats( *i ) );
				}
			}
			break;

		case Variable::tString:
			pLst->append( rVar.getString() );
			break;
	}
	return pLst;
}

void GameState::gatsToScope( Gats::Dictionary *pRoot, Scope *pSrc ) const
{
	for( Gats::Dictionary::iterator i = pRoot->begin(); i; i++ )
	{
		pSrc->insert( i.getKey(), gatsToVariable(
			dynamic_cast<Gats::List *>(*i)
			) );
	}
}

Variable GameState::gatsToVariable( Gats::List *pLst ) const
{
	Gats::List::iterator i = pLst->begin();

	switch( dynamic_cast<Gats::Integer *>(*i)->getValue() )
	{
		case Variable::tNull:
			return Variable( Variable::tNull );
			break;

		case Variable::tBool:
			return Variable(
				dynamic_cast<Gats::Boolean *>(*(i+1))->getValue()
				);
			break;

		case Variable::tInt:
			return Variable(
				dynamic_cast<Gats::Integer *>(*(i+1))->getValue()
				);
			break;

		case Variable::tFloat:
			return Variable(
				dynamic_cast<Gats::Float *>(*(i+1))->getValue()
				);
			break;

		case Variable::tSituation:
			return Variable::newSituationName(
				*dynamic_cast<Gats::String *>(*(i+1))
				);
			break;

		case Variable::tVariable:
			return Variable::newVariableName(
				*dynamic_cast<Gats::String *>(*(i+2)),
				(ScopeId)dynamic_cast<Gats::Integer *>(*(i+1))->getValue()
				);
			break;

		case Variable::tVarPtr:
			sio << "No!!!" << sio.nl;
			break;

		case Variable::tList:
			{
				Variable vRet( Variable::tList );
				for( Gats::List::iterator k =
					 dynamic_cast<Gats::List *>(*(i+1))->begin(); k; k++ )
				{
					vRet += gatsToVariable(
						dynamic_cast<Gats::List *>(*k)
						);
				}
				return vRet;
			}
			break;

		case Variable::tDictionary:
			{
				Variable vRet( Variable::tDictionary );
				for( Gats::List::iterator k =
					 dynamic_cast<Gats::List *>(*(i+1))->begin(); k; k++ )
				{
					Variable vKey = gatsToVariable(
						dynamic_cast<Gats::List *>(*k)
						);
					k++;
					if( !k )
					{
						throw Bu::ExceptionBase("Premature end of dict.");
					}
					Variable vValue = gatsToVariable(
						dynamic_cast<Gats::List *>(*k)
						);
					vRet.insert( vKey, vValue );
				}
				return vRet;
			}
			break;

		case Variable::tString:
			return Variable(
				*dynamic_cast<Gats::String *>(*(i+1))
				);
			break;
	}

	throw Bu::ExceptionBase("Type unknown: %d", dynamic_cast<Gats::Integer *>(*i)->getValue() );
}

class Situation *GameState::getCurSituation()
{
	return pGame->getSituation( sCurSituation );
}
/*
void GameState::run( class AstBranch *pAst )
{
//	pushScope( pAst );
	run();
//	if( lsLocal.getSize() == iDepth )
//		delete lsLocal.peekPop();
}

void GameState::pushScope( class AstBranch *pAst )
{
	sio << "--> pushScope: " << pAst->getType() << sio.nl;
	if( pAst->getType() != AstNode::tScope )
		throw Bu::ExceptionBase("Nope, nothing doing, you can't run a non-scope AstBranch.");

	lsLocal.push( new Scope() );
//	int iDepth = lsLocal.getSize();
	sProg.push( pAst->getNodeList().begin() );
}
*/
void GameState::init()
{
	Variable vStart = pGame->getParam("start");
	if( vStart.getType() != Variable::tSituation )
		throw Bu::ExceptionBase("game.start is not set to a situation name.");

	gotoSituation( vStart.getString() );
}

void GameState::gotoSituation( const Bu::String &sName )
{
	lStack.clear();
	for( ScopeList::iterator i = lsLocal.begin(); i; i++ )
		delete *i;
	lsLocal.clear();

	Situation *pSit = pGame->getSituation( sName );
	sCurSituation = sName;
	sGoto.clear();
	if( !hsSituation.has( sName ) )
	{
		hsSituation.insert( sName, new Scope() );
		pSit->exec( *this, Situation::modeSetup );
		if( !sGoto.isSet() )
			pSit->exec( *this, Situation::modeEnter );
	}
	else
	{
		pSit->exec( *this, Situation::modeEnter );
	}

	if( sGoto.isSet() )
	{
		gotoSituation( sGoto );
	}
}

void GameState::callFunction( const Bu::String &sName )
{
	pGame->getFunction( sName )->call( *this );
}

void GameState::execCommand( const Bu::String &sCmd )
{
	Bu::StringList lCmd = tokenize( sCmd );

	if( !pGame->getSituation( sCurSituation )->execCommand( *this, lCmd ) )
	{
		if( !pGame->execCommand( *this, lCmd ) )
		{
			pIface->display( SmlNode::parse(
				"I don't understand that command."
				) );
			return;
		}
	}

	if( sGoto.isSet() )
		gotoSituation( sGoto );
}

void GameState::execOption( int idx )
{
	pGame->getSituation( sCurSituation )->execOption( *this, idx );

	if( sGoto.isSet() )
		gotoSituation( sGoto );
}

bool GameState::hasVariable( const Bu::String &sName, ScopeId id )
{
	switch( id )
	{
		case sidLocal:
			return lsLocal.peek()->has( sName );

		case sidGlobal:
			return sGlobal.has( sName );

		case sidPlayer:
			return sPlayer.has( sName );

		case sidSituation:
			return hsSituation.get( sCurSituation )->has( sName );
	}
	
	throw Bu::ExceptionBase("Really bad scopeid passed into getVariable");
}

void GameState::delVariable( const Bu::String &sName, ScopeId id )
{
	switch( id )
	{
		case sidLocal:
			return lsLocal.peek()->erase( sName );

		case sidGlobal:
			return sGlobal.erase( sName );

		case sidPlayer:
			return sPlayer.erase( sName );

		case sidSituation:
			return hsSituation.get( sCurSituation )->erase( sName );
	}
	
	throw Bu::ExceptionBase("Really bad scopeid passed into getVariable");
}

Variable &GameState::getVariable( const Bu::String &sName, ScopeId id )
{
	try
	{
	switch( id )
	{
		case sidLocal:
			return lsLocal.peek()->get( sName );

		case sidGlobal:
			return sGlobal.get( sName );

		case sidPlayer:
			return sPlayer.get( sName );

		case sidSituation:
			return hsSituation.get( sCurSituation )->get( sName );
	}
	}
	catch( Bu::HashException &e )
	{
		throw Bu::ExceptionBase("No %d variable named %s found.",
				id,
				sName.getStr() );
	}

	throw Bu::ExceptionBase("Really bad scopeid passed into getVariable");
}

void GameState::setVariable( const Bu::String &sName, const Variable &v,
	ScopeId id )
{
	switch( id )
	{
		case sidLocal:
			lsLocal.peek()->insert( sName, v );
			return;

		case sidGlobal:
			sGlobal.insert( sName, v );
			return;

		case sidPlayer:
			sPlayer.insert( sName, v );
			return;

		case sidSituation:
			hsSituation.get( sCurSituation )->insert( sName, v );
			return;
	}

	throw Bu::ExceptionBase("Really bad scopeid passed into setVariable");
}

Variable &GameState::deref( Variable &src, bool bCreate )
{
	if( src.getType() == Variable::tVariable )
	{
		VariableRef r = src.getVariableRef();
		try
		{
			switch( r.sid )
			{
				case sidLocal:
					return lsLocal.peek()->get( r.sName );

				case sidGlobal:
					return sGlobal.get( r.sName );

				case sidPlayer:
					return sPlayer.get( r.sName );

				case sidSituation:
					return hsSituation.get( sCurSituation )->get( r.sName );
			}
		}
		catch( Bu::HashException &e )
		{
			if( bCreate )
			{
				switch( r.sid )
				{
					case sidLocal:
						lsLocal.peek()->insert( r.sName, Variable() );
						return lsLocal.peek()->get( r.sName );

					case sidGlobal:
						sGlobal.insert( r.sName, Variable() );
						return sGlobal.get( r.sName );

					case sidPlayer:
						sPlayer.insert( r.sName, Variable() );
						return sPlayer.get( r.sName );

					case sidSituation:
						hsSituation.get( sCurSituation )->insert( r.sName, Variable() );
						return hsSituation.get( sCurSituation )->get( r.sName );
				}
			}
			else
			{
				throw Bu::ExceptionBase("No %d variable named %s found.",
						r.sid,
						r.sName.getStr() );
			}
		}
	}
	else if( src.getType() == Variable::tVarPtr )
	{
		return *src.getVariablePtr();
	}
	return src;
}

Bu::StringList GameState::tokenize( const Bu::String &sSrc )
{
	Bu::StringList lRet;
	Bu::String sToken;
	bool bWs = true;
	Bu::String::const_iterator i = sSrc.begin();

	while( i )
	{
		for(; i; i++ )
		{
			if( *i != ' ' && *i != '\t' && *i != '\n' && *i != '\r' )
				break;
		}
		for(; i; i++ )
		{
			if( i == ' ' || i == '\t' || *i == '\n' || *i == '\r' )
			{
				break;
			}
			
			sToken.append( *i );
		}
		if( sToken.getSize() > 0 )
		{
			lRet.append( sToken );
			sToken.clear();
		}
	}

	return lRet;
}

void GameState::exit()
{
	bRunning = false;
	bEscape = true;
}

Variable GameState::popDeref()
{
	Variable v = lStack.peekPop();
	if( v.getType() == Variable::tVariable )
	{
		VariableRef r = v.getVariableRef();
		return getVariable( r.sName, r.sid );
	}
	else if( v.getType() == Variable::tVarPtr )
	{
		return *v.getVariablePtr();
	}
	return v;
}

void GameState::run( const class AstBranch *pAst, bool bNewScope )
{
	if( pAst->getType() != AstNode::tScope )
		throw Bu::ExceptionBase("Nope, nothing doing, you can't run a non-scope AstBranch.");

	run( pAst->getNodeList(), bNewScope );
}

void GameState::run( const AstBranch::NodeList &lNode, bool bNewScope )
{
	ProgramStack sProg;
	
	if( bNewScope )
		lsLocal.push( new Scope() );
	sProg.push( lNode.begin() );

	while( !sProg.isEmpty() )
	{
		ProgramCounter &i = sProg.peek();
//		sio << "Stack: " << lStack << sio.nl;
//		sio << "exec: " << (*i)->getType() << sio.nl;
		switch( (*i)->getType() )
		{
		// tLeaf
		case AstNode::tNot:
			{
				Variable x = popDeref();
				if( x.getType() != Variable::tBool )
					throw Bu::ExceptionBase("Non-bool used with logical not operator.");
				push( Variable( !x.getBool() ) );
			}
			break;

		case AstNode::tComp:
			{
				push( popDeref() == popDeref() );
			}
			break;

		case AstNode::tCompGt:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				push( x > y );
			}
			break;

		case AstNode::tCompLt:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				push( x < y );
			}
			break;

		case AstNode::tCompGtEq:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				push( x >= y );
			}
			break;

		case AstNode::tCompLtEq:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				push( x <= y );
			}
			break;

		case AstNode::tStore:
			{
				Variable y = popDeref();
				deref( lStack.peek(), true ) = y;
/*				Variable dst = pop();
				
				VariableRef r = dst.getVariableRef();
				setVariable( r.sName, y, r.sid ); */
			}
			break;
		
		case AstNode::tStoreRev:
			{
				Variable dst = pop();
				Variable y = popDeref();
				VariableRef r = dst.getVariableRef();
				setVariable( r.sName, y, r.sid );
			}
			break;

		case AstNode::tAnd:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				if( x.getType() != Variable::tBool ||
					y.getType() != Variable::tBool )
				{
					throw Bu::ExceptionBase("Non-bool used in logical AND operator.");
				}
				lStack.push( Variable( x.getBool() && y.getBool() ) );
			}
			break;

		case AstNode::tOr:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				if( x.getType() != Variable::tBool ||
					y.getType() != Variable::tBool )
				{
					throw Bu::ExceptionBase("Non-bool used in logical OR operator.");
				}
				lStack.push( Variable( x.getBool() || y.getBool() ) );
			}
			break;

		case AstNode::tPlus:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				lStack.push( x + y );
			}
			break;

		case AstNode::tMinus:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				lStack.push( x - y );
			}
			break;

		case AstNode::tDivide:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				lStack.push( x / y );
			}
			break;

		case AstNode::tMultiply:
			{
				Variable y = popDeref();
				Variable x = popDeref();
				lStack.push( x * y );
			}
			break;

		case AstNode::tPlusStore:
			{
				Variable y = popDeref();
				deref( lStack.peek(), true ) += y;
			}
			break;

		case AstNode::tMinusStore:
			{
				Variable y = popDeref();
				deref( lStack.peek(), true ) -= y;
			}
			break;

		case AstNode::tDivideStore:
			{
				Variable y = popDeref();
				deref( lStack.peek(), true ) /= y;
			}
			break;

		case AstNode::tMultiplyStore:
			{
				Variable y = popDeref();
				deref( lStack.peek(), true ) *= y;
			}
			break;

		case AstNode::tNegate:
			push( -popDeref() );
			break;

		case AstNode::tIn:
			{
				Variable v = popDeref();
				Variable x = popDeref();
				if( v.getType() == Variable::tDictionary ||
					v.getType() == Variable::tList )
				{
					push( Variable( v.has( x ) ) );
				}
				else
				{
					throw Bu::ExceptionBase("Invalid type for operator in");
				}
			}
			break;

		case AstNode::tGoto:
			{
				Variable x = popDeref();
				if( x.getType() != Variable::tSituation )
					throw Bu::ExceptionBase("You cannot goto anything but a situation.");
				sGoto = x.getString();
				return;
			}
			break;

		case AstNode::tSwap:
			{
				Variable y = pop();
				Variable x = pop();
				push( y );
				push( x );
			}
			break;

		case AstNode::tReturn:
			if( bNewScope )
				lsLocal.pop();
			return;

		case AstNode::tAppend:
			{
				Variable v = popDeref();
				deref( lStack.peek() ) += v;
			}
			break;

		case AstNode::tInsert:
			{
				Variable v = popDeref();
				Variable k = popDeref();
				deref( lStack.peek() ).insert( k, v );
			}
			break;

		case AstNode::tIndex:
			{
				Variable v = popDeref();
				Variable cx = pop();
				Variable &c = deref( cx );
				push( Variable( &c.get( v ) ) );
			}
			break;

		case AstNode::tPop:
			pop();
			break;

		case AstNode::tDeref:
			push( popDeref() );
			break;

		case AstNode::tExists:
			{
				Variable v = pop();
				VariableRef r = v.getVariableRef();
				push( Variable( hasVariable( r.sName, r.sid ) ) );
			}
			break;

		case AstNode::tDelete:
			{
				Variable v = pop();
				VariableRef r = v.getVariableRef();
				delVariable( r.sName, r.sid );
				push( Variable( Variable::tNull ) );
			}
			break;

		// tLeafLiteral
		case AstNode::tVarName:
		case AstNode::tLiteral:
			lStack.push( dynamic_cast<const AstLeafLiteral *>(*i)->getValue() );
			break;

		case AstNode::tFuncCall:
			callFunction(
				dynamic_cast<const AstLeafLiteral *>(*i)
					->getValue().getString()
				);
			break;

		// tBranch
		case AstNode::tScope:
			throw Bu::ExceptionBase("Scope?  that shouldn't be here...");
			break;

		case AstNode::tIf:
			{
				AstBranch::NodeList lIf =
					dynamic_cast<const AstBranch *>(*i)->getNodeList();
				Variable v = popDeref();
				if( v.getType() != Variable::tBool )
					throw Bu::ExceptionBase("conditional did not evaluate to boolean.");
				AstBranch::NodeList::const_iterator iIf = lIf.begin();
				if( v.getBool() )
				{
					i++;
					sProg.push( dynamic_cast<const AstBranch *>(*iIf)->
						getNodeList().begin() );
					continue;
				}
				else
				{
					iIf++;
					if( iIf )
					{
						i++;
						sProg.push( dynamic_cast<const AstBranch *>(*iIf)->
							getNodeList().begin() );
						continue;
					}
				}
			}
			break;

		case AstNode::tForEach:
			{
				AstBranch::NodeList lFe =
					dynamic_cast<const AstBranch *>(*i)->getNodeList();
				AstBranch::NodeList::const_iterator iEach = lFe.begin();
				AstBranch::NodeList::const_iterator iIn = iEach+1;
				AstBranch::NodeList::const_iterator iDo = iIn+1;
				
				const AstBranch::NodeList &lEachVrs =
					dynamic_cast<const AstBranch *>(*iEach)->getNodeList();
				AstBranch::NodeList::const_iterator iEachVrs = lEachVrs.begin();

				bool bUseKey = false;
				VariableRef vrKey, vrValue;
				if( lEachVrs.getSize() == 2 )
				{
					vrKey = dynamic_cast<const AstLeafLiteral *>(*iEachVrs)->
						getValue().getVariableRef();
					iEachVrs++;
					vrValue = dynamic_cast<const AstLeafLiteral *>(*iEachVrs)->
						getValue().getVariableRef();
					bUseKey = true;
				}
				else
				{
					vrValue = dynamic_cast<const AstLeafLiteral *>(*iEachVrs)->
						getValue().getVariableRef();
				}

				run( dynamic_cast<const AstBranch *>(*iIn),
						false );
				Variable vIn = popDeref();
				
				const AstBranch::NodeList &rDo =
					dynamic_cast<const AstBranch *>(*iDo)->getNodeList();

				if( vIn.getType() == Variable::tDictionary )
				{
					const Variable::VariableHash &rHash = vIn.getHash();
					for( Variable::VariableHash::const_iterator i =
						 rHash.begin(); i; i++ )
					{
						if( bUseKey )
							setVariable( vrKey.sName, i.getKey(), vrKey.sid );
						setVariable( vrValue.sName, i.getValue(), vrValue.sid );
						run( rDo, false );	
					}
				}
				else if( vIn.getType() == Variable::tList )
				{
					if( bUseKey )
					{
						throw Bu::ExceptionBase("You cannot use key:value pairs as iterators in a for each loop iterating over a list.");
					}
					const Variable::VariableArray &rList = vIn.getList();
					for( Variable::VariableArray::const_iterator i =
						 rList.begin(); i; i++ )
					{
						setVariable( vrValue.sName, *i, vrValue.sid );
						run( rDo, false );	
					}
				}
			}
			break;

		case AstNode::tWhile:
			{
				AstBranch::NodeList lWhile =
					dynamic_cast<const AstBranch *>(*i)->getNodeList();
				AstBranch::NodeList::const_iterator iTest = lWhile.begin();
				AstBranch::NodeList::const_iterator iDo = iTest+1;

				for(;;)
				{
					run( dynamic_cast<const AstBranch *>(*iTest)->
						getNodeList(), false );
					Variable v = popDeref();
					if( v.getType() != Variable::tBool )
						throw Bu::ExceptionBase("conditional did not evaluate to boolean.");
					if( !v.getBool() )
						break;

					run( dynamic_cast<const AstBranch *>(*iDo)->
						getNodeList(), false );
				}
			}
			break;
		}

		if( sGoto.isSet() )
			return;

		++sProg.peek();
		while( !sProg.isEmpty() && !sProg.peek() )
		{
			sProg.pop();
		}
	}
	if( bNewScope )
		lsLocal.pop();
}