/*
   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 "Side.h"
#include "Group.h"
#include "BCCompiler.h"

#include <sstream>
#include <iterator>
#include <stdexcept>
#include <algorithm>

using std::istringstream;
using std::istream_iterator;
using std::runtime_error;
using std::find;
using std::find_if;
using std::getline;

Side::Side(const string& iName, int iMySide):
mySide(iMySide), myPoints(0), name(iName), commander(globalSettings.commander), alreadyAIError(false) {
	if (mySide == -1)
		mySide = sides.size();

	memset(scriptVars, 0, sizeof(scriptVars));
	memset(saveGroups, 0, sizeof(saveGroups));

	startingRect.x = 0;
	startingRect.y = 0;
	startingRect.w = startingRectDim;
	startingRect.h = startingRectDim;

	SetColors();
}

Side::~Side() {
	//make sure units are destroyed before unitPictures!
	groups.clear();
}	

void Side::SetColors() {
	switch (mySide) {
	case 0:
		color = sideRed;
		radarColor = radarRed;
		laserColor = laserRed;
		break;
	case 1:
		color = sideGreen;
		radarColor = radarGreen;
		laserColor = laserGreen;
		break;
	case 2:
		color = sideBlue;
		radarColor = radarBlue;
		laserColor = laserBlue;
		break;
	case 3:
		color = sideYellow;
		radarColor = radarYellow;
		laserColor = laserYellow;
		break;
	}
}

void Side::FilesToSDStruct() {
	string concatString = globalSettings.bdp + "fleets/" + name + "/" + name + ".dat";

	FileToString(concatString, sdStruct.mainData);

	istringstream input(sdStruct.mainData);

	istream_iterator<char> iter = input;
	istream_iterator<char> fileEnd = istream_iterator<char>();

	string tempStr;

	//skip version, commander, saved groups
	iter = find(iter, fileEnd, ':');
	++iter;
	iter = find(iter, fileEnd, ':');
	++iter;
	iter = find(iter, fileEnd, ':');
	++iter;
	iter = find(iter, fileEnd, ':');
	++iter;

	while (iter != fileEnd) {
		input.unget();

		string groupType;
		string unitName;
		string aiName;

		//group type
		getline(input, groupType, ':');
		iter = input;

		//unit name
		iter = find(iter, fileEnd, ':');
		input.ignore();
		getline(input, unitName);
		iter = input;

		if (sdStruct.unitData.find(unitName) == sdStruct.unitData.end()) {
			concatString = GetFleetDir(mySide) + unitName + ".dat";
			FileToString(concatString, sdStruct.unitData[unitName]);
		}

		//AI
		iter = find(iter, fileEnd, ':');
		input.ignore();

		getline(input, aiName);
		    
		if (aiScripts.find(aiName) == aiScripts.end())
			CreateAIScriptVec(aiName);

		iter = input;

		//ignore parent
		iter = find(iter, fileEnd, ':');
		++iter;

		//ignore saved groups and starting poses
		iter = find(iter, fileEnd, ':');
		++iter;
		iter = find(iter, fileEnd, ':');
		++iter;

		//jump to next group, if any
		iter = find_if(iter, fileEnd, isalpha);
	}
}

void Side::CreateAIScriptVec(const string& aiName) {
	if (aiName == "") {
		aiScripts[aiName].script.push_back(strToUstr("main:"));
		return;
	}
		
	const string concatString = globalSettings.bdp + "fleets/" + name + "/" + aiName + ".ai";

	string aiInputStr;
	FileToString(concatString, aiInputStr);

	istringstream aiStream(aiInputStr);

	string tempStr;
	
	try {
		while (getline(aiStream, tempStr))
			aiScripts[aiName].script.push_back(BCCompiler::ConvertToBC(tempStr));
	} catch (runtime_error e) {
		char error[200];
		sprintf(error, "Problem with AI script %s at line %d: %s", aiName.c_str(), aiScripts[aiName].script.size() + 1, e.what());
		throw runtime_error(error);
	}

	CheckOverAI(aiName);
	//don't create function lookup table here because we try to keep loading data seperate from setting up objects based on that data
}

void Side::LoadData(bool ignoreMinorIssues) {
	LoadFromStruct();

	CheckOverData(ignoreMinorIssues);

	myPoints = GetPointsValue();

	for (map<string, AIScript>::iterator mapIter = aiScripts.begin(); mapIter != aiScripts.end(); ++mapIter)
		MakeFunctionLookupTable(mapIter->first);
}

void Side::MakeFunctionLookupTable(const string& scriptName) {
	const vector<ustring>& theText = aiScripts[scriptName].script;
	
	//check for main and set firstLine
	int mainIndex = 0;
	while (mainIndex != theText.size() && theText[mainIndex][0] == TT_Comment)
		++mainIndex;

	if (theText[mainIndex] == strToUstr("main:")) {
		if (theText.size() > 1)
			aiScripts[scriptName].firstLine = mainIndex + 1;
		else
			aiScripts[scriptName].firstLine = mainIndex;
	} else {
		const string error = "AI script " + scriptName + " does not appear to begin with \"main:\"";
		throw runtime_error(error.c_str());
	}
	
	for (int i = 0; i != theText.size(); ++i) {
		int bi = 0;
		while (theText[i][bi] == TT_Tab)
			++bi;

		if (theText[i][bi] == TT_Jump) {
			ustring functionName = theText[i].substr(bi+1, string::npos);

			int functionIndex = 0;

			while (functionIndex != theText.size()) {
				if (theText[functionIndex].find(TT_Colon) == string::npos) {
					++functionIndex;
					continue;
				}
				
				bi = 0;
				while (theText[functionIndex][bi] == TT_Tab)
					++bi;

				const ustring tempStr = theText[functionIndex].substr(bi, theText[functionIndex].find(TT_Colon));
				if (tempStr == functionName)
					break;

				++functionIndex;
			}

			if (functionIndex != theText.size())
				aiScripts[scriptName].functionLookup[functionName] = functionIndex;
			else {
				const string output = "Couldn't find target of function call " + ustrToStr(functionName) + " in script " + scriptName;
				throw runtime_error(output);
			}
		}
	}
}

void Side::LoadFromStruct() {
	istringstream input(sdStruct.mainData);

	istream_iterator<char> iter = istream_iterator<char>(input);
	istream_iterator<char> fileEnd = istream_iterator<char>();

	//ignore version
	iter = find(iter, fileEnd, ':');
	++iter;

	//commander
	iter = find(iter, fileEnd, ':');
	input.ignore();
	getline(input, commander);
	iter = input;

	//save groups
	iter = find(iter, fileEnd, ':');
	++iter;
	for (int i = 0; i != nAIVars; ++i) {
		saveGroups[i].x = mySide;
		saveGroups[i].y = IterToInt(iter, fileEnd);
		++iter;
	}

	//"Groups:"
	iter = find(iter, fileEnd, ':');
	++iter;

	string tempStr;

	//now the groups
	for (int i = 0; iter != fileEnd; ++i) {
		input.unget();

		string groupName;
		UnitType unitType;
		string AIFile;
		vector<int> savedGroupsVec;
		int parentCaSh;
		CoordsInt startingCoords;

		//first the group type
		getline(input, tempStr, ':');
		iter = input;

		unitType = stringToUType[tempStr];

		//unit name
		iter = find(iter, fileEnd, ':');
		input.ignore();
		getline(input, groupName);
		iter = input;

		//AI
		iter = find(iter, fileEnd, ':');
		input.ignore();
		getline(input, AIFile);
		iter = input;
		
		iter = find(iter, fileEnd, ':');
		++iter;

		//Parent
		if (*iter == '-') {
			++iter;
			parentCaSh = -(IterToInt(iter, fileEnd));
		} else
			parentCaSh = IterToInt(iter, fileEnd);

		//saved groups
		iter = find(iter, fileEnd, ':');
		++iter;
		for (int j = 0; j != nAIVars; ++j) {
			savedGroupsVec.push_back(IterToInt(iter, fileEnd));
			++iter;
		}

		//starting coords
		iter = find(iter, fileEnd, ':');
		++iter;
		startingCoords.x = IterToInt(iter, fileEnd);
		++iter;
		startingCoords.y = IterToInt(iter, fileEnd);

		if (unitType == UT_CaShUnit)
			groups.push_back(Group(unitType, mySide, i, groupName, AIFile, CST_None, savedGroupsVec, startingCoords));
		else
			groups.push_back(Group(unitType, mySide, i, groupName, AIFile, parentCaSh, savedGroupsVec, startingCoords));
	}

	if (gsCurrent != GST_ForceSelect)
		unitPictures.clear();
}

void Side::CheckOverData(bool ignoreMinorIssues) {
	if (gsCurrent != GST_ForceSelect) {
		if (groups.size() < 1) {
			char output[80];
			sprintf (output, "Side %d contains no groups.", mySide+1);
			throw runtime_error(output);
		}
	}
	
	if (!ignoreMinorIssues) {
		//check all small ship groups have a parent capital ship
		for (int i = 0; i != groups.size(); ++i) {
			if (groups[i].GetType() != UT_SmShUnit)
				continue;
			
			int parent = groups[i].GetParentCaSh();
			
			if (parent < 0 || parent >= groups.size() || groups[parent].GetType() != UT_CaShUnit) {
				char output[80];
				sprintf(output, "Small ship group %d does not have a parent capital ship", i + 1);
				throw runtime_error(output);
			}
		}
		
		//check no capital ships are overly full
		for (int i = 0; i != groups.size(); ++i) {
			if (groups[i].GetType() == UT_CaShUnit && groups[i].GetHowFull() > groups[i].GetCapacity()) {
				char output[80];
				sprintf(output, "Capital ship group %d contains too many small ship groups", i + 1);
				throw runtime_error(output);
			}
		}
		
		//check enough capital ships to support all frigates
		int frMax = 0;
		for (int i = 0; i != groups.size(); ++i)
			frMax += groups[i].GetFrCapacity();
		
		int frTotal = 0;
		for (int i = 0; i != groups.size(); ++i) {
			if (groups[i].GetType() == UT_FrUnit)
				++frTotal;
		}
		
		if (frTotal > frMax) {
			char output[100];
			sprintf(output, "Fleet contains %d frigates yet only contains enough capital ships to support %d", frTotal, frMax);
			throw runtime_error(output);
		}
		
		for (int i = 0; i != groups.size(); ++i)
			groups[i].PointsValueCheck();
	}
}

void Side::CheckOverAI(const string& scriptName) {
	const vector<ustring>& theText = aiScripts[scriptName].script;
	char output[200];
	
	for (int i = 0; i != theText.size(); ++i) {
		bool anIf = false;
		bool anElse = false;
		bool aNumX = false;
		bool anAssign = false;
		bool aScriptVar = false;
		bool anIncOrDec = false;
		
		for (int j = 0; j != theText[i].size(); ++j) {
			switch(theText[i][j]) {
			case TT_If:
			case TT_ElIf:
				anIf = true;
				break;

			case TT_Else:
				anElse = true;
				break;

			case TT_NumEnemy:
			case TT_NumFriend:
			case TT_NumAlly:
				aNumX = true;
				break;

			case TT_Assign:
				if (!aScriptVar) {
					sprintf(output, "AI script %s attempts to assign to something other than a script variable on line %d.", scriptName.c_str(), i + 1);
					throw runtime_error(output);
				}
				anAssign = true;
				break;

			case TT_ScriptVar:
			case TT_SaveGroup:
			case TT_ScriptTimer:
			case TT_GScriptVar:
			case TT_GSaveGroup:
				aScriptVar = true;
				break;

			case TT_Increment:
			case TT_Decrement:
				if (!aScriptVar) {
					sprintf(output, "AI script %s attempts to increment or decrement before naming a variable on line %d.", scriptName.c_str(), i + 1);
					throw runtime_error(output);
				}
				anIncOrDec = true;
				break;
			}
		}
		
		if (anIf && aNumX) {
			sprintf(output, "AI script %s uses a numx type specifier on the same line as an \"if\" on line %d.\nCurrently numx commands can only be assigned to variables.", scriptName.c_str(), i + 1);
			throw runtime_error(output);
		}

		if (anIf && anElse) {
			sprintf(output, "AI script %s includes the phrase \"else if\" on line %d.\nIn GalaxyHack AI scripts you must write \"elif\" instead.", scriptName.c_str(), i + 1);
			throw runtime_error(output);
		}
		
		if (anIf && anAssign) {
			sprintf(output, "AI script %s includes a single equals sign in an if statement on line %d.\nA single equals sign is used to assign values to script variables, you probably meant to use \"==\".", scriptName.c_str(), i + 1);
			throw runtime_error(output);
		}
	}
	
	/*
		int openBCounter = 0;
		int closeBCounter = 0;

		for (int j = 0; j != theText[i].size(); ++j) {
			if (theText[i][j] == TT_OpenB)
				++openBCounter;
			else if (theText[i][j] == TT_CloseB)
				++closeBCounter;
		}

		if (openBCounter > closeBCounter) {
			sprintf(output, "AI script %s has more open rounded brackets than close rounded brackets on line %d", scriptName.c_str(), i + 1);
			throw runtime_error(output);
		}
		else if (openBCounter < closeBCounter) {
			sprintf(output, "AI script %s has less open rounded brackets than close rounded brackets on line %d", scriptName.c_str(), i + 1);
			throw runtime_error(output);
		}
	*/
}

void Side::ClearDataStructs() {
	SideDataStruct blankSDStruct;
	map<string, AIScript> blankAIScripts;
	sdStruct = blankSDStruct;
	aiScripts = blankAIScripts;
}

void Side::Reset() {
	groups.clear();
	unitPictures.clear();
	
	memset(scriptVars, 0, sizeof(scriptVars));
	memset(saveGroups, 0, sizeof(saveGroups));

	LoadFromStruct();
}

void Side::ChangeMySide(int newMySide) {
	mySide = newMySide;
	SetColors();

	//neccessary because unloading pictures requires units know which side they are on
	for (int i = 0; i != groups.size(); ++i)
		groups[i].ChangeMySide(mySide);

	Reset();
}

int Side::GetPointsValue() {
	int total = 0;

	for (int i = 0; i != groups.size(); ++i)
		total += groups[i].GetPointsValue();

	return total;
}

int Side::GetTotalUnits() {
	int total = 0;

	for (int i = 0; i != groups.size(); ++i)
		total += groups[i].GetUnitsLeft();

	return total;
}

int Side::GetTotalHealth() {
	int total = 0;

	for (int i = 0; i != groups.size(); ++i)
		total += groups[i].GetHealth();

	return total;
}

int Side::GetTotalCapShips() {
	int total = 0;

	for (int i = 0; i != groups.size(); ++i) {
		if (groups[i].GetType() == UT_CaShUnit)
			total += groups[i].GetUnitsLeft();
	}

	return total;
}

void Side::AddAIError(const string& msg) {
	aiErrorReports.push_back(msg);
	if (aiErrorReports.size() > maxMessageVectorSize)
		aiErrorReports.erase(aiErrorReports.begin());
		
	alreadyAIError = true;
}
