/*
   Copyright (C) 2004 by James Gregory
   Part of the GalaxyHack project
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.
 
   See the COPYING file for more details.
*/

#include "AIInterpreter.h"
#include "Group.h"
#include "Globals.h"
#include "Inlines.h"
#include "Projectile.h"
#include "Random.h"

#include <string>
#include <cstdlib>

using std::string;
using std::isdigit;

/*
Interpet functions should be called with the iter on the first char of the thing you wanted interpreted
Functions should return with iter on the first thing they didn't look at
*/

void AIInterpreter::Init(Group_Base* iThisGroup, const vector<ustring>* iTheText, AICommands* iTheCommands) {
	thisGroup = iThisGroup;
	theText = iTheText;
	theCommands = iTheCommands;
	
	//AIInterpreter is only inited once side sizes are stable, so this is OK. This just means the code can be slightly tidier as it's annoying having to put "thisGroup->" everywhere
	mySide = thisGroup->mySide;
	myGroup = thisGroup->myGroup;

	memset(scriptVars, 0, sizeof(scriptVars));
	memset(scriptTimers, 0, sizeof(scriptTimers));
	memset(theCommands, 0, sizeof(AICommands));
}

void AIInterpreter::GetCommands() {
	//ensure bools and enums and speeds start off with 0s
	memset(theCommands, 0, sizeof(AICommands));
	
	/*
	currentLine to keep track of where we are
	insCounter to prevent infinite loops
	*/
	
	currentLine = sides[mySide].aiScripts[thisGroup->aiFilename].firstLine;
	insCounter = 0;
	
	CallFunction();
}

/*
this is the function that calls all the other AI line interpretation functions, and uses the information returned
from them to decide which lines of the script to read and which to skip.
It gets called recursively by script function calls.
*/
void AIInterpreter::CallFunction() {
	vector<bool> rememberIfElseResult;

	while (currentLine != theText->size()) {
		if (insCounter >= maxIns) {
			char output[80];
			sprintf(output, "Processed maximum %d instructions", maxIns);
			thisGroup->ReportOnScriptError(output);
			return;
		}

		lIter = (*theText)[currentLine].begin();
		lEnd = (*theText)[currentLine].end();
		
		//don't want blank lines resetting the if results
		if (*lIter == TT_Comment) {
			++currentLine;
			continue;
		}
		
		int tabLevel = 0;
		
		while (*lIter == TT_Tab) {
			++tabLevel;
			++lIter;
		}
		
		//more tabs than bools?
		while (tabLevel > static_cast<int>(rememberIfElseResult.size()) - 1)
			rememberIfElseResult.push_back(false);
		
		//more bools than tabs?
		while (static_cast<int>(rememberIfElseResult.size()) - 1 > tabLevel)
			rememberIfElseResult.pop_back();

		switch (*lIter) {
		case TT_If: {
				++insCounter;

				bool ifResult = InterpretIf();

				if (ifResult == true)
					//don't skip anything
					rememberIfElseResult[tabLevel] = true;
				else {
					//was false, skip at least one command
					rememberIfElseResult[tabLevel] = false;
					SkipBlock(tabLevel);
				}
			}
			break;


		case TT_Else:
			++insCounter;

			if (rememberIfElseResult[tabLevel] == true)
				SkipBlock(tabLevel);
			else
				//don't skip anything
				rememberIfElseResult[tabLevel] = true;
			break;
		
		case TT_ElIf:
			++insCounter;
			
			if (rememberIfElseResult[tabLevel] == true)
				SkipBlock(tabLevel);			
			else {
				bool ifResult = InterpretIf();

				if (ifResult == true)
					//don't skip anything
					rememberIfElseResult[tabLevel] = true;
				else
					//if it was false, skip at least one command
					SkipBlock(tabLevel);
			}
			break;

		case TT_Return:
			++insCounter;
			return;
			break;

		case TT_Jump: {
				++insCounter;
				int saveLN = currentLine;

				++lIter;
				ustring functionName(lIter, lEnd);

				int calledLine = sides[mySide].aiScripts[thisGroup->aiFilename].functionLookup[functionName];
				//++ called line because we want to start from the line after the function label
				currentLine = ++calledLine;

				CallFunction();
				currentLine = saveLN;
			}
			break;

		case TT_StartTimer:
			++insCounter;
			InterpretStartTimer();
			break;

		case TT_ScriptVar:
		case TT_GScriptVar:
			++insCounter;
			InterpretSetVar();
			break;
		
		case TT_SaveGroup:
		case TT_GSaveGroup:
			++insCounter;
			InterpretSetSaveGroup();
			break;

		case TT_Move:
		case TT_MoveAway:
			++insCounter;
			InterpretMove();
			break;

		case TT_Fire:
			++insCounter;
			InterpretFire();
			break;

		case TT_Patrol:
			++insCounter;
			InterpretPatrol();
			break;

		default:
			//I think this can only be a function label.
			//So we return, as we don't want to fall-through into another function.
			return;
			break;
		}

		++currentLine;
	}

	//this only happens if we reach the end of the file before
	//coming across a return command
	return;
}

void AIInterpreter::SkipBlock(int tabLevel) {
	++currentLine;

	while (currentLine != theText->size()) {
		lIter = (*theText)[currentLine].begin();
		
		//don't want blank lines setting tab level
		if (*lIter == TT_Comment) {
			++currentLine;
			continue;
		}
		
		int countTabs = 0;
		while (*lIter == TT_Tab) {
			++countTabs;
			++lIter;
		}

		if (countTabs <= tabLevel) {
			//remember ++currentLine at end of main function loop
			--currentLine;
			return;
		}
			
		++currentLine;
	}
	
	//remember ++currentLine at end of main function loop
	--currentLine;
}

//this function just gets our iterator past the initial TT_If
bool AIInterpreter::InterpretIf() {
	++lIter;
	return InterpretIfPartTwo();
}

bool AIInterpreter::InterpretIfPartTwo() {
	bool firstResult;

	while (lIter != lEnd) {
		//each case must put the iter one past the end of that word/
		//operator/whatever
		switch (*lIter) {
		/* given up on this for now
		case TT_OpenB:
			++lIter;
			//recursively call function
			firstResult = InterpretIfPartTwo();
			break;
		
		
		case TT_CloseB:
			++lIter;
			//got to end, return firstResult
			return firstResult;
			break;
		*/
		
		case TT_Not:
			++lIter;
			//recursively call function and invert result
			firstResult = !InterpretIfPartTwo();
			break;

		case TT_And:
			++lIter;

			//as it's AND, if either side is false, we return false

			//actually, as I always compare just the first and
			//current result, not the previous and current result,
			//it doesn't work quite as you think it does
			//However, for some crazy logic reason I don't
			//actually think it's possivle to come up with
			//a situation where it makes any difference if you
			//are comparing the first or the previous result
			
			if (!firstResult || !InterpretIfPartTwo()) {
				ShortCircuitSkip();
				return false;
			}
 						
			//else keep going round
			break;

		case TT_Or:
			++lIter;
				
			if (firstResult || InterpretIfPartTwo()) {
				ShortCircuitSkip();
				return true;
			} 	
			
			//else keep going round
			break;

		default:
			//assume must be the first specifier of a comparison, or FIXME or what?
			firstResult = TrueOrFalse();
			break;
		}
	}

	//as with the note above &&: it doesn't seem to make sense to
	//just return the first result, but I think for some crazy
	//reason of logic that I don't understand, this works
	return firstResult;
}

//only called from "if"
bool AIInterpreter::TrueOrFalse() {
	//scripts are allowed to specify multiple pieces of information from a single
	//group
	int rememberNSide = 0;
	int rememberNGroup = 0;
	int firstValue;
	
	switch (*lIter) {
	case TT_Our:
	case TT_NearestEnemy:
	case TT_NearestFriend:
	case TT_NearestAlly:
	case TT_SaveGroup:
	case TT_GSaveGroup: {
		unsigned char nextToken;
		//past number
		if (*lIter == TT_SaveGroup || *lIter == TT_GSaveGroup)
			nextToken = *(lIter + 2);
		else
			nextToken = *(lIter + 1);
			
		if (nextToken == TT_Equal || nextToken == TT_NotEqual)
			return AreGroupsTheSame();
		else
			return IsNearestLikeThis();
		}
		break;
		
	case TT_AnyEnemy:
	case TT_AnyFriend:
	case TT_AnyAlly:
		return DoAnyHave();
		break;

	default:
		firstValue = TokenToInt(rememberNSide, rememberNGroup);
		break;
	}

	unsigned char operatorType = *lIter;
	++lIter;
	int secondValue = TokenToInt();

	return CompareValues(firstValue, operatorType, secondValue);
}

bool AIInterpreter::CompareValues(int firstValue, const unsigned char operatorType, int secondValue) {
	switch (operatorType) {
	case TT_Equal:
		if (firstValue == secondValue)
			return true;
		break;

	case TT_NotEqual:
		if (firstValue != secondValue)
			return true;
		break;

	case TT_GreaterThan:
		if (firstValue > secondValue)
			return true;
		break;

	case TT_LessThan:
		if (firstValue < secondValue)
			return true;
		break;

	case TT_GreaterThanEqual:
		if (firstValue >= secondValue)
			return true;
		break;

	case TT_LessThanEqual:
		if (firstValue <= secondValue)
			return true;
		break;
	}

	return false;
}

void AIInterpreter::InterpretStartTimer() {
	++lIter;
	int which = TokenToInt();
	scriptTimers[which] = frameCounter;
}

void AIInterpreter::InterpretSetVar() {
	unsigned char varToken = *lIter;
	++lIter;
	int which = IterToInt(lIter, lEnd);
	
	//++
	if (*lIter == TT_Increment) {
		switch (varToken) {
		case TT_ScriptVar:
			scriptVars[which]++;
			break;
		case TT_GScriptVar:
			sides[mySide].scriptVars[which]++;
			break;
		}
		return;
	}

	//--
	else if (*lIter == TT_Decrement) {
		switch (varToken) {
		case TT_ScriptVar:
			scriptVars[which]--;
			break;
		case TT_GScriptVar:
			sides[mySide].scriptVars[which]--;
			break;
		}
		return;
	}
	
	//else skip TT_Equal
	++lIter;

	switch (varToken) {
	case TT_ScriptVar: {
			int newValue = TokenToInt();
			scriptVars[which] = newValue;
		}
		break;

	case TT_GScriptVar: {
			int newValue = TokenToInt();
			sides[mySide].scriptVars[which] = newValue;
		}
		break;
	}
}

void AIInterpreter::InterpretSetSaveGroup() {
	const unsigned char varToken = *lIter;
	++lIter;
	int which = IterToInt(lIter, lEnd);
	//already one past end, but skip equals
	++lIter;
	
	//we don't need to remember the returned distance
	int temp;
	CoordsInt targetIndices;
	
	switch (varToken)
	{
	case TT_SaveGroup:
		FindNearestWith(targetIndices, temp);
		if (targetIndices.x != -1)
			saveGroups[which] = targetIndices;
		break;

	case TT_GSaveGroup:
		FindNearestWith(targetIndices, temp);
		if (targetIndices.x != -1)
			sides[mySide].saveGroups[which] = targetIndices;
		break;
	}
}

void AIInterpreter::InterpretMove() {
	//is it move or moveaway?
	if (*lIter == TT_MoveAway)
		theCommands->bInverse = true;
	//we might get multiple move commands so we need to overwrite any old inverting
	else
		theCommands->bInverse = false;

	++lIter;

	switch (*lIter) {
	case TT_N:
	case TT_NorthEdge:
		theCommands->moveCommand = MC_MoveCompass;
		theCommands->moveTarget.y = CD_N;
		break;

	case TT_NE:
		theCommands->moveCommand = MC_MoveCompass;
		theCommands->moveTarget.y = CD_NE;
		break;

	case TT_E:
	case TT_EastEdge:
		theCommands->moveCommand = MC_MoveCompass;
		theCommands->moveTarget.y = CD_E;
		break;

	case TT_SE:
		theCommands->moveCommand = MC_MoveCompass;
		theCommands->moveTarget.y = CD_SE;
		break;

	case TT_S:
	case TT_SouthEdge:
		theCommands->moveCommand = MC_MoveCompass;
		theCommands->moveTarget.y = CD_S;
		break;

	case TT_SW:
		theCommands->moveCommand = MC_MoveCompass;
		theCommands->moveTarget.y = CD_SW;
		break;

	case TT_W:
	case TT_WestEdge:
		theCommands->moveCommand = MC_MoveCompass;
		theCommands->moveTarget.y = CD_W;
		break;

	case TT_NW:
		theCommands->moveCommand = MC_MoveCompass;
		theCommands->moveTarget.y = CD_NW;
		break;

	case TT_NearestWorldEdge: {
			int throwaway;
			theCommands->moveCommand = MC_MoveCompass;
			theCommands->moveTarget.y = NearestEdge(throwaway);
		}
		break;

	//lets hope the specifier specifies a group
	default:
		//if -1 is returned it means there is no available target meeting
		//all the criteria
		CoordsInt targetIndices;
		FindNearestWith(targetIndices, theCommands->moveTargetDist);
		if (targetIndices.x != -1) {
			theCommands->moveTarget = targetIndices;

			if (theCommands->moveTarget.x != maxPlayers)
				theCommands->moveCommand = MC_MoveGroup;
			else
				theCommands->moveCommand = MC_MoveCompass;
		}
		break;
	}
}

void AIInterpreter::InterpretFire() {
	//have we already got a fire command?
	if (theCommands->bFire) {
		thisGroup->ReportOnScriptError("Multiple fire commands received", currentLine);
		return;
	}
	
	if (thisGroup->units[0].GetType() == UT_FrUnit) {
		//then we have a group name
		++lIter;
	
		CoordsInt targetIndices;
		FindNearestWith(targetIndices, theCommands->fireTargetDist);
		if (targetIndices.x != -1) {
			theCommands->fireTarget = targetIndices;

			if (theCommands->fireTarget.x != maxPlayers)
				theCommands->bFire = true;
			else
				thisGroup->ReportOnScriptError("Attempt to fire at world edge", currentLine);
		}
	}
	else
		theCommands->bFire = true;
}

void AIInterpreter::InterpretPatrol() {
	++lIter;

	//first we have a number
	theCommands->patrolDist = TokenToInt();

	//if -1 is returned it means there is no available target meeting
	//all the criteria
	CoordsInt targetIndices;
	FindNearestWith(targetIndices, theCommands->moveTargetDist);
	if (targetIndices.x != -1) {
		theCommands->moveTarget = targetIndices;

		if (theCommands->moveTarget.x != maxPlayers)
			theCommands->moveCommand = MC_Patrol;
		else
			thisGroup->ReportOnScriptError("Attempt to patrol world edge", currentLine);
	}
}

int AIInterpreter::TokenToInt() {
	int giveSide = -1;
	int giveGroup = -1;
	return TokenToInt(giveSide, giveGroup);
}

int AIInterpreter::TokenToInt(int& giveSide, int& giveGroup) {
	int cache = 0;

	switch (*lIter) {
	case TT_Integer:
		++lIter;
		cache = IterToInt(lIter, lEnd);
		//we ++lIter at the end of this function to put it one past end
		--lIter;
		break;

	case TT_ScriptVar: {
		++lIter;
		int which = IterToInt(lIter, lEnd);
		cache = scriptVars[which];
		//we ++lIter at the end of this function to put it one past end
		--lIter;
		break;
	}

	case TT_GScriptVar: {
		++lIter;
		int which = IterToInt(lIter, lEnd);
		cache = sides[mySide].scriptVars[which];
		//we ++lIter at the end of this function to put it one past end
		--lIter;
		break;
	}

	case TT_ScriptTimer: {
		++lIter;
		int which = IterToInt(lIter, lEnd);
		cache = frameCounter - scriptTimers[which];
		//we ++lIter at the end of this function to put it one past end
		--lIter;
		break;
	}

	case TT_Random:
		cache = Random() % 100 + 1;
		break;
		
	case TT_None:
		cache = WCAT_None;
		break;

	case TT_Laser:
		cache = WCAT_Large;
		break;

	case TT_Missile:
		cache = WCAT_Missile;
		break;

	case TT_Torpedo:
		cache = WCAT_Torpedo;
		break;

	case TT_CapitalShip:
		cache = UT_CaShUnit;
		break;

	case TT_Frigate:
		cache = UT_FrUnit;
		break;

	case TT_SmallShip:
		cache = UT_SmShUnit;
		break;

	case TT_Our:
		++lIter;

		giveSide = mySide;
		giveGroup = myGroup;

		cache = StatToInt(*lIter, mySide, myGroup);
		break;

	case TT_NearestEnemy: 
	case TT_NearestFriend:
	case TT_NearestAlly:
	case TT_SaveGroup:
	case TT_GSaveGroup:
		GetGroupIndices(giveSide, giveGroup);
		cache = StatToInt(*lIter, giveSide, giveGroup);
	break;

	case TT_NearestWorldEdge:
			//++ on to "distance", not past because we ++ at end
			++lIter;

			giveSide = maxPlayers;
			giveGroup = NearestEdge(cache);
		break;

	case TT_NorthEdge:
	case TT_EastEdge:
	case TT_SouthEdge:
	case TT_WestEdge:
		giveSide = maxPlayers;
		giveGroup = TokenToEdge(cache);
		break;
		
	case TT_NumEnemy:
	case TT_NumFriend:
	case TT_NumAlly:
		cache = HowManyHave();
		//we ++lIter at the end of this function to put it one past end
		--lIter;
		break;

	default:
		if (giveSide != -1)
			//assume must be a group stat of a previously specified group
			cache = StatToInt(*lIter, giveSide, giveGroup);
		else {
			lIter = lEnd;
			thisGroup->ReportOnScriptError("Didn't recognise token when asked to find TokenToInt", currentLine);
			return cache;
		}
		break;
	}
	
	//either moving on to arithmetic, or otherwise leaving one past end
	++lIter;

	if (lIter != lEnd) {
		unsigned char theOperator = *lIter;

		switch (theOperator) {
		case TT_Add:
			++lIter;
			return cache + TokenToInt(giveSide, giveGroup);
			break;
		case TT_Minus:
			++lIter;
			return cache - TokenToInt(giveSide, giveGroup);
			break;
		case TT_Multiply:
			++lIter;
			return cache * TokenToInt(giveSide, giveGroup);
			break;
		case TT_Divide: {
			++lIter;
			int temp = TokenToInt(giveSide, giveGroup);

			if (temp != 0)
				return cache / temp;
			else
				return maxAIInt;
			break;
			}			
		case TT_Modulo: {
			++lIter;
			int temp = TokenToInt(giveSide, giveGroup);

			if (temp != 0)
				return cache % temp;
			else
				return maxAIInt;
			break;
			}
		}
	}

	return cache;
}

int AIInterpreter::StatToInt(const unsigned char theToken, int nSide, int nGroup) {
	if (nSide == maxPlayers) {
		if (theToken == TT_Distance) {
			return DistToXEdge(static_cast<CompassDirection>(nGroup));
		} else {
			lIter = lEnd;
			thisGroup->ReportOnScriptError("Attempt to find stat other than distance of saved world edge", currentLine);
			return 0;
		}
	}

	switch (theToken) {
	case TT_Number:
		return sides[nSide].groups[nGroup].GetUnitsLeft();
		break;

	case TT_Health:
		return sides[nSide].groups[nGroup].GetHealth();
		break;
		
	case TT_Shield:
		return sides[nSide].groups[nGroup].GetShield();
		break;
	
	case TT_Armour:
		return sides[nSide].groups[nGroup].GetArmour();
		break;
		
	case TT_MaxHealth:
		return sides[nSide].groups[nGroup].GetMaxHealth();
		break;
		
	case TT_MaxShield:
		return sides[nSide].groups[nGroup].GetMaxShield();
		break;
		
	case TT_MaxArmour:
		return sides[nSide].groups[nGroup].GetMaxArmour();
		break;
		
	case TT_UnitMaxShield:
		return sides[nSide].groups[nGroup].GetUnitMaxShield();
		break;
		
	case TT_UnitMaxArmour:
		return sides[nSide].groups[nGroup].GetUnitMaxArmour();
		break;

	case TT_Speed:
		return sides[nSide].groups[nGroup].GetSpeed();
		break;

	case TT_Distance:
		return thisGroup->FindDistanceTo(nSide, nGroup);
		break;

	case TT_SmallRange:
		return weaponLookup[sides[nSide].groups[nGroup].GetSmallType()].range;
		break;

	case TT_SmallPower:
		return weaponLookup[sides[nSide].groups[nGroup].GetSmallType()].power;
		break;

	case TT_BigRange:
		return weaponLookup[sides[nSide].groups[nGroup].GetBigType()].range;
		break;

	case TT_BigPower:
		return weaponLookup[sides[nSide].groups[nGroup].GetBigType()].power;
		break;

	case TT_BigType:
		return weaponLookup[sides[nSide].groups[nGroup].GetBigType()].category;
		break;

	case TT_BigAmmo:
		return sides[nSide].groups[nGroup].GetBigAmmo();
		break;

	case TT_Left:
		return sides[nSide].groups[nGroup].GetUnitsLeft();
		break;
		
	case TT_MissTarget: {
			int total = 0;
			CoordsInt theGroup = {nSide, nGroup};

			for (list<Projectile>::iterator iter = projectiles.begin(); iter != projectiles.end(); ++iter) {
				if (iter->GetType() == WCAT_Missile && iter->GetTarget() == theGroup)
					++total;
			}
			return total;
		}
		break;

	case TT_TorpTarget: {
			int total = 0;
			CoordsInt theGroup = {nSide, nGroup};

			for (list<Projectile>::iterator iter = projectiles.begin(); iter != projectiles.end(); ++iter) {
				if (iter->GetType() == WCAT_Torpedo && iter->GetTarget() == theGroup)
					++total;
			}
			return total;
		}
		break;

	case TT_GroupType:
		return sides[nSide].groups[nGroup].GetType();
		break;

	case TT_InSmallRange: {
		int range = weaponLookup[sides[mySide].groups[myGroup].GetSmallType()].range;
		int dist = thisGroup->FindDistanceTo(nSide, nGroup);
		
		if (dist <= range)
			return 1;
		else
			return 0;
		}
		break;
	
	case TT_InBigRange: {
		int range = weaponLookup[sides[mySide].groups[myGroup].GetBigType()].range;
		int dist = thisGroup->FindDistanceTo(nSide, nGroup);
		
		if (dist <= range)
			return 1;
		else
			return 0;
		}
		break;

	case TT_OurInSmallRange: {
		int range = weaponLookup[sides[nSide].groups[nGroup].GetSmallType()].range;
		int dist = thisGroup->FindDistanceTo(nSide, nGroup);
		
		if (dist <= range)
			return 1;
		else
			return 0;
		}
		break;
	
	case TT_OurInBigRange: {
		int range = weaponLookup[sides[nSide].groups[nGroup].GetBigType()].range;
		int dist = thisGroup->FindDistanceTo(nSide, nGroup);
		
		if (dist <= range)
			return 1;
		else
			return 0;
		}
		break;
	}
	
	lIter = lEnd;
	thisGroup->ReportOnScriptError("Failed to convert group stat to integer", currentLine);
	return 0;
}

///

bool AIInterpreter::AreGroupsTheSame() {
	int side1 = 0;
	int side2 = 0;
	int group1 = 0;
	int group2 = 0;

	GetGroupIndices(side1, group1);

	unsigned char operatorType = *lIter;
	++lIter;

	GetGroupIndices(side2, group2);

	int lhs, rhs;

	if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
		lhs = (side1 >> 16) | group1;
		rhs = (side2 >> 16) | group2;
	} else {
		lhs = (side1 << 16) | group1;
		rhs = (side2 << 16) | group2;
	}

	return CompareValues(lhs, operatorType, rhs);
}

void AIInterpreter::GetGroupIndices(int& side, int& group) {
	switch(*lIter) {
	case TT_Our:
		side = mySide;
		group = myGroup;
		break;

	case TT_NearestEnemy:
		NearestEnemy(side, group);
		break;

	case TT_NearestFriend:
		side = mySide;
		NearestFriend(group);
		break;

	case TT_NearestAlly:
		NearestAlly(side, group);
		break;

	case TT_SaveGroup:
	case TT_GSaveGroup:
		WhichSavedGroup(side, group);
		break;
	}

	++lIter;
}

//if x is -1 it means no group found
void AIInterpreter::FindNearestWith(CoordsInt& giveIndices, int& giveDist) {
	//in part two all the groups start off as valid and are then eliminated,
	//here all sides start off as invalid and are then added
	vector<bool> possibleSides(sides.size(), false);

	switch (*lIter) {
	case TT_Our:
		//leave one past end
		++lIter;
		giveIndices.x = mySide;
		giveIndices.y = myGroup;
		break;
		
	case TT_NearestEnemy:
		++lIter;
		for (int i = 0; i != sides.size(); ++i) {
			if (sides[i].myFlag != sides[mySide].myFlag)
				possibleSides[i] = true;
		}
		FindNearestWithPartTwo(giveIndices, possibleSides, giveDist);
		break;

	case TT_NearestFriend:
		++lIter;
		possibleSides[mySide] = true;
		FindNearestWithPartTwo(giveIndices, possibleSides, giveDist);
		break;

	case TT_NearestAlly:
		++lIter;
		for (int i = 0; i != sides.size(); ++i) {
			if (sides[i].myFlag == sides[mySide].myFlag && i != mySide)
				possibleSides[i] = true;
		}
		FindNearestWithPartTwo(giveIndices, possibleSides, giveDist);
		break;

	case TT_SaveGroup: {
			++lIter;
			int which = IterToInt(lIter, lEnd);
			
			if (saveGroups[which].x == maxPlayers)
				giveDist = DistToXEdge(static_cast<CompassDirection>(saveGroups[which].y));
			else
				giveDist = thisGroup->FindDistanceTo(saveGroups[which].x, saveGroups[which].y);

			giveIndices.x = saveGroups[which].x;
			giveIndices.y = saveGroups[which].y;
		}
		break;

	case TT_GSaveGroup: {
			++lIter;
			int which = IterToInt(lIter, lEnd);
			
			if (sides[mySide].saveGroups[which].x == maxPlayers)
				giveDist = DistToXEdge(static_cast<CompassDirection>(sides[mySide].saveGroups[which].y));
			else
				giveDist = thisGroup->FindDistanceTo(sides[mySide].saveGroups[which].x, sides[mySide].saveGroups[which].y);

			giveIndices.x = sides[mySide].saveGroups[which].x;
			giveIndices.y = sides[mySide].saveGroups[which].y;
		}
		break;

	case TT_NearestWorldEdge:
			//leave one past end
			++lIter;

			giveIndices.x = maxPlayers;
			giveIndices.y = NearestEdge(giveDist);
		break;

	case TT_NorthEdge:
	case TT_EastEdge:
	case TT_SouthEdge:
	case TT_WestEdge:
		giveIndices.x = maxPlayers;
		giveIndices.y = TokenToEdge(giveDist);
		break;
	
	default:
		lIter = lEnd;
		thisGroup->ReportOnScriptError("Didn't recognise group type when reading AI script", currentLine);
		giveIndices.x = -1;
		break;
	}
}

//if x is -1 it means no group found
void AIInterpreter::FindNearestWithPartTwo(CoordsInt& giveIndices, vector<bool>& possibleSides, int& giveDist) {
	giveIndices.x = -1;
	giveDist = maxAIInt;

	vector<vector <bool> > possibleGroups(possibleSides.size());

	SearchThroughGroups(possibleSides, possibleGroups);

	for (int i = 0; i != possibleSides.size(); ++i) {
		for (int j = 0; j != possibleGroups[i].size(); ++j) {
			if (possibleGroups[i][j] == true) {
				int tempDist = thisGroup->FindDistanceTo(i, j);

				if (tempDist < giveDist) {
					giveDist = tempDist;
					giveIndices.x = i;
					giveIndices.y = j;
				}
			}
		}
	}
}

void AIInterpreter::SearchThroughGroups(vector<bool>& possibleSides, vector<vector <bool> >& possibleGroups) {
	for (int i = 0; i != possibleSides.size(); ++i) {
		if (possibleSides[i] == false)
			continue;

		//all start off as being possible, the opposite to the sides vector
		possibleGroups[i].resize(sides[i].groups.size(), true);
	}
	
	ustring::const_iterator saveIter = lIter;
	
	for (int i = 0; i != possibleSides.size(); ++i) {
		if (possibleSides[i] == false)
			continue;
		
		for (int j = 0; j != possibleGroups[i].size(); ++j) {
			lIter = saveIter;
			possibleGroups[i][j] = IsGroupLikeThis(i, j, true, true);
		}
	}
}

///

bool AIInterpreter::DoAnyHave() {
	//in shared search all the groups start off as valid and are then eliminated,
	//here all sides start off as invalid and are then added
	vector<bool> possibleSides(sides.size(), false);

	WhichSidesValid(possibleSides);
	
	++lIter;
	
	ustring::const_iterator saveIter = lIter;
	
	for (int i = 0; i != possibleSides.size(); ++i) {
		if (possibleSides[i] == false)
			continue;
		
		for (int j = 0; j != sides[i].groups.size(); ++j) {
			lIter = saveIter;
			if (IsGroupLikeThis(i, j, true, true))
				return true;
		}
	}
	
	//got to end, must be false
	return false;
}

int AIInterpreter::HowManyHave() {
	//in shared search all the groups start off as valid and are then eliminated,
	//here all sides start off as invalid and are then added
	vector<bool> possibleSides(sides.size(), false);

	WhichSidesValid(possibleSides);
	
	++lIter;
	
	ustring::const_iterator saveIter = lIter;
	
	int total = 0;
	
	for (int i = 0; i != possibleSides.size(); ++i) {
		if (possibleSides[i] == false)
			continue;
		
		for (int j = 0; j != sides[i].groups.size(); ++j) {
			lIter = saveIter;
			if (IsGroupLikeThis(i, j, true, true))
				++total;
		}
	}
	
	return total;
}

void AIInterpreter::WhichSidesValid(vector<bool>& possibleSides) {
	switch (*lIter) {
	case TT_AnyEnemy:
	case TT_NumEnemy:
		for (int i = 0; i != sides.size(); ++i) {
			if (sides[i].myFlag != sides[mySide].myFlag)
				possibleSides[i] = true;
		}
		break;

	case TT_AnyFriend:
	case TT_NumFriend:
		possibleSides[mySide] = true;
		break;

	case TT_AnyAlly:
	case TT_NumAlly:
		for (int i = 0; i != sides.size(); ++i) {
			if (sides[i].myFlag == sides[mySide].myFlag && i != mySide)
				possibleSides[i] = true;
		}
		break;
	}
}

bool AIInterpreter::IsNearestLikeThis() {
	int nSide, nGroup;
	bool discountDead = true;
	bool discountOur = true;
	
	switch (*lIter) {
	case TT_Our:
		nSide = mySide;
		nGroup = myGroup;
		discountOur = false;
		break;

	case TT_NearestEnemy:
		NearestEnemy(nSide, nGroup);
		break;

	case TT_NearestFriend:
		nSide = mySide;
		NearestFriend(nGroup);
		break;

	case TT_NearestAlly:
		NearestAlly(nSide, nGroup);
		break;
		
	case TT_SaveGroup:
	case TT_GSaveGroup:
		WhichSavedGroup(nSide, nGroup);
		discountDead = false;
		discountOur = false;

		if (nSide == maxPlayers) {
			++lIter;
			return IsEdgeLikeThis(nSide, nGroup);
		}
		break;

	default: {
			lIter = lEnd;
			thisGroup->ReportOnScriptError("IsGroupLikeThis failed", currentLine);
			return false;
		}
		break;
	}
	
	++lIter;
	return IsGroupLikeThis(nSide, nGroup, discountDead, discountOur);
}

bool AIInterpreter::IsGroupLikeThis(int nSide, int nGroup, bool discountDead, bool discountOur) {
	//discount dead people and/or ourselves?
	if ((!sides[nSide].groups[nGroup].GetAlive() && discountDead) 
	|| (nSide == mySide && nGroup == myGroup && discountOur)) {
		ShortCircuitSkip();
		return false;
	}

	while (lIter != lEnd) {
		//possibly we have reached the end of this if, if so must be true
		if (*lIter == TT_And || *lIter == TT_Or)
			return true;
		
		bool booleanResult = false;
		if (*lIter == TT_InSmallRange || *lIter ==TT_InBigRange || *lIter == TT_OurInSmallRange || *lIter == TT_OurInBigRange)
			booleanResult = true;
			
		int statValue = TokenToInt(nSide, nGroup);
		
		if (booleanResult) {
			if (!statValue) {
				ShortCircuitSkip();
				return false;
			}
		} else {
			const unsigned char operatorType = *lIter;
			++lIter;
			int secondValue = TokenToInt();

			if (!CompareValues(statValue, operatorType, secondValue)) {
				ShortCircuitSkip();
				return false;
			}
		}
	}
	
	//if we reach the end it is true
	return true;
}

bool AIInterpreter::IsEdgeLikeThis(int nSide, int nGroup) {
	int statValue = TokenToInt(nSide, nGroup);
	
	const unsigned char operatorType = *lIter;
	++lIter;
	int secondValue = TokenToInt();
	
	if (!CompareValues(statValue, operatorType, secondValue))
		return false;
	else
		return true;
}

void AIInterpreter::ShortCircuitSkip() {
	while (lIter != lEnd && *lIter != TT_And && *lIter != TT_Or)
		++lIter;
}

void AIInterpreter::NearestEnemy(int& giveSide, int& giveGroup) {
	//if no enemies left (unlikely) we'll use 0 just to prevent a crash
	giveSide = 0;
	giveGroup = 0;
	
	int currentDist = maxAIInt;
	
	for (int i = 0; i != sides.size(); ++i) {
		if (sides[i].myFlag != sides[mySide].myFlag) {
			for (int j = 0; j != sides[i].groups.size(); ++j) {
				if (!sides[i].groups[j].GetAlive())
					continue;

				int tempDist = thisGroup->FindDistanceTo(i, j);

				if (tempDist < currentDist) {
					currentDist = tempDist;
					giveSide = i;
					giveGroup = j;
				}
			}
		}
	}
}

void AIInterpreter::NearestFriend(int& giveGroup) {
	//if no friends left (possible) we'll use 0 just to prevent a crash
	giveGroup = 0;
	
	int currentDist = maxAIInt;
	
	//who is nearest?
	for (int j = 0; j != sides[mySide].groups.size(); ++j) {
		if (!sides[mySide].groups[j].GetAlive() || j == myGroup)
			continue;

		int tempDist = thisGroup->FindDistanceTo(mySide, j);

		if (tempDist < currentDist) {
			currentDist = tempDist;
			giveGroup = j;
		}
	}
}

void AIInterpreter::NearestAlly(int& giveSide, int& giveGroup) {
	//if no allies left (possible) we'll use 0 just to prevent a crash
	giveSide = 0;
	giveGroup = 0;
	
	int currentDist = maxAIInt;
	
	//who is nearest?
	for (int i = 0; i != sides.size(); ++i) {
		if (sides[i].myFlag == sides[mySide].myFlag && i != mySide) {
			for (int j = 0; j != sides[i].groups.size(); ++j) {
				if (!sides[i].groups[j].GetAlive())
					continue;

				int tempDist = thisGroup->FindDistanceTo(i, j);

				if (tempDist < currentDist) {
					currentDist = tempDist;
					giveSide = i;
					giveGroup = j;
				}
			}
		}
	}
}

//this doesn't leave token one past end to stay in line with nearest-type functions
void AIInterpreter::WhichSavedGroup(int& giveSide, int& giveGroup) {
	const unsigned char groupType = *lIter;
	++lIter;
	int which = IterToInt(lIter, lEnd);
	//this doesn't leave token one past end to stay in line with nearest-type functions
	--lIter;
	
	switch(groupType) {
	case TT_SaveGroup:
		giveSide = saveGroups[which].x;
		giveGroup = saveGroups[which].y;
		break;

	case TT_GSaveGroup:
		giveSide = sides[mySide].saveGroups[which].x;
		giveGroup = sides[mySide].saveGroups[which].y;
		break;
	}
}

///

CompassDirection AIInterpreter::NearestEdge(int& distance) {
		CompassDirection ret = CD_N;
		distance = DistToTopEdge();
		int edist = DistToRightEdge();
		int sdist = DistToBottomEdge();
		int wdist = DistToLeftEdge();
		
		if (edist < distance) {
			distance = edist;
			ret = CD_E;
		}
		if (sdist < distance) {
			distance = sdist;
			ret = CD_S;
		}
		if (wdist < distance) {
			distance = wdist;
			ret = CD_W;
		}

		return ret;
}

int AIInterpreter::DistToXEdge(CompassDirection which) {
	switch(which) {
	case CD_N:
		return DistToTopEdge();
	case CD_E:
		return DistToRightEdge();
	case CD_S:
		return DistToBottomEdge();
	case CD_W:
		return DistToLeftEdge();
	}
}

CompassDirection AIInterpreter::TokenToEdge(int& giveDist) {
	CompassDirection ret;

	switch(*lIter) {
	case TT_NorthEdge:
		ret = CD_N;
		break;

	case TT_EastEdge:
		ret = CD_E;
		break;

	case TT_SouthEdge:
		ret = CD_S;
		break;

	case TT_WestEdge:
		ret = CD_W;
		break;
	}

	giveDist = DistToXEdge(ret);

	//one past end
	++lIter;
	return ret;
}

int AIInterpreter::DistToLeftEdge() const {
	return static_cast<int>(thisGroup->myx);
}

int AIInterpreter::DistToRightEdge() const {
	return static_cast<int>(worldWidth - (thisGroup->myx + thisGroup->width));
}

int AIInterpreter::DistToTopEdge() const {
	return static_cast<int>(thisGroup->myy);
}

int AIInterpreter::DistToBottomEdge() const {
	return static_cast<int>(worldHeight - (thisGroup->myy + thisGroup->height));
}
