/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/debug.h"
#include "common/savefile.h"

#include "sludge/allfiles.h"
#include "sludge/fileset.h"
#include "sludge/moreio.h"
#include "sludge/newfatal.h"
#include "sludge/objtypes.h"
#include "sludge/people.h"
#include "sludge/sludge.h"
#include "sludge/variable.h"

namespace Sludge {

const char *typeName[] = { "undefined", "number", "user function", "string",
		"built-in function", "file", "stack", "object type", "animation",
		"costume" };

void unlinkVar(Variable &thisVar) {
	switch (thisVar.varType) {
		case SVT_STRING:
			delete []thisVar.varData.theString;
			thisVar.varData.theString = NULL;
			break;

		case SVT_STACK:
			thisVar.varData.theStack->timesUsed--;
			if (thisVar.varData.theStack->timesUsed <= 0) {
				while (thisVar.varData.theStack->first)
					trimStack(thisVar.varData.theStack->first);
				delete thisVar.varData.theStack;
				thisVar.varData.theStack = NULL;
			}
			break;

		case SVT_FASTARRAY:
			thisVar.varData.fastArray->timesUsed--;
			if (thisVar.varData.theStack->timesUsed <= 0) {
				delete thisVar.varData.fastArray->fastVariables;
				delete[] thisVar.varData.fastArray;
				thisVar.varData.fastArray = NULL;
			}
			break;

		case SVT_ANIM:
			deleteAnim(thisVar.varData.animHandler);
			break;

		default:
			break;
	}
}

void setVariable(Variable &thisVar, VariableType vT, int value) {
	unlinkVar(thisVar);
	thisVar.varType = vT;
	thisVar.varData.intValue = value;
}

void newAnimationVariable(Variable &thisVar, PersonaAnimation  *i) {
	unlinkVar(thisVar);
	thisVar.varType = SVT_ANIM;
	thisVar.varData.animHandler = i;
}

PersonaAnimation  *getAnimationFromVar(Variable &thisVar) {
	if (thisVar.varType == SVT_ANIM)
		return copyAnim(thisVar.varData.animHandler);

	if (thisVar.varType == SVT_INT && thisVar.varData.intValue == 0)
		return makeNullAnim();

	fatal("Expecting an animation variable; found Variable of type", typeName[thisVar.varType]);
	return NULL;
}

void newCostumeVariable(Variable &thisVar, Persona *i) {
	unlinkVar(thisVar);
	thisVar.varType = SVT_COSTUME;
	thisVar.varData.costumeHandler = i;
}

Persona *getCostumeFromVar(Variable &thisVar) {
	Persona *p = NULL;

	switch (thisVar.varType) {
		case SVT_ANIM:
			p = new Persona;
			if (!checkNew(p))
				return NULL;
			p->numDirections = 1;
			p->animation = new PersonaAnimation  *[3];
			if (!checkNew(p->animation))
				return NULL;

			for (int iii = 0; iii < 3; iii++)
				p->animation[iii] = copyAnim(thisVar.varData.animHandler);

			break;

		case SVT_COSTUME:
			return thisVar.varData.costumeHandler;
			break;

		default:
			fatal("Expecting an animation variable; found Variable of type", typeName[thisVar.varType]);
	}

	return p;
}

int stackSize(const StackHandler *me) {
	int r = 0;
	VariableStack *a = me->first;
	while (a) {
		r++;
		a = a->next;
	}
	return r;
}

bool getSavedGamesStack(StackHandler *sH, const Common::String &ext) {

	// Make pattern
	uint len = ext.size();
	Common::String pattern = "*";
	pattern += ext;

	// Get all saved files
	Common::StringArray sa = g_system->getSavefileManager()->listSavefiles(pattern);

	// Save file names to stacks
	Variable newName;
	newName.varType = SVT_NULL;
	Common::StringArray::iterator it;
	for (it = sa.begin(); it != sa.end(); ++it) {
		(*it).erase((*it).size() - len, len);
		makeTextVar(newName, (*it));
		if (!addVarToStack(newName, sH->first))
			return false;
		if (sH->last == NULL)
			sH->last = sH->first;
	}

	return true;
}

bool copyStack(const Variable &from, Variable &to) {
	to.varType = SVT_STACK;
	to.varData.theStack = new StackHandler;
	if (!checkNew(to.varData.theStack))
		return false;
	to.varData.theStack->first = NULL;
	to.varData.theStack->last = NULL;
	to.varData.theStack->timesUsed = 1;
	VariableStack *a = from.varData.theStack->first;

	while (a) {
		addVarToStack(a->thisVar, to.varData.theStack->first);
		if (to.varData.theStack->last == NULL) {
			to.varData.theStack->last = to.varData.theStack->first;
		}
		a = a->next;
	}

	return true;
}

void addVariablesInSecond(Variable &var1, Variable &var2) {
	if (var1.varType == SVT_INT && var2.varType == SVT_INT) {
		var2.varData.intValue += var1.varData.intValue;
	} else {
		Common::String string1 = getTextFromAnyVar(var1);
		Common::String string2 = getTextFromAnyVar(var2);

		unlinkVar(var2);
		var2.varData.theString = createCString(string1 + string2);
		var2.varType = SVT_STRING;
	}
}

int compareVars(const Variable &var1, const Variable &var2) {
	int re = 0;
	if (var1.varType == var2.varType) {
		switch (var1.varType) {
			case SVT_NULL:
				re = 1;
				break;

			case SVT_COSTUME:
				re = (var1.varData.costumeHandler == var2.varData.costumeHandler);
				break;

			case SVT_ANIM:
				re = (var1.varData.animHandler == var2.varData.animHandler);
				break;

			case SVT_STRING:
				re = (strcmp(var1.varData.theString, var2.varData.theString) == 0);
				break;

			case SVT_STACK:
				re = (var1.varData.theStack == var2.varData.theStack);
				break;

			default:
				re = (var1.varData.intValue == var2.varData.intValue);
		}
	}
	return re;
}

void compareVariablesInSecond(const Variable &var1, Variable &var2) {
	setVariable(var2, SVT_INT, compareVars(var1, var2));
}

char *createCString(const Common::String &s) {
	uint n = s.size() + 1;
	char *res = new char[n];
	if (!checkNew(res)) {
		fatal("createCString : Unable to copy String");
		return NULL;
	}
	memcpy(res, s.c_str(), n);
	return res;
}

void makeTextVar(Variable &thisVar, const Common::String &txt) {
	unlinkVar(thisVar);
	thisVar.varType = SVT_STRING;
	thisVar.varData.theString = createCString(txt);
}

bool loadStringToVar(Variable &thisVar, int value) {
	makeTextVar(thisVar, g_sludge->_resMan->getNumberedString(value));
	return (bool)(thisVar.varData.theString != NULL);
}

Common::String getTextFromAnyVar(const Variable &from) {
	switch (from.varType) {
		case SVT_STRING:
			return from.varData.theString;

		case SVT_FASTARRAY: {
			Common::String builder = "FAST:";
			Common::String builder2 = "";
			Common::String grabText = "";

			for (int i = 0; i < from.varData.fastArray->size; i++) {
				builder2 = builder + " ";
				grabText = getTextFromAnyVar(from.varData.fastArray->fastVariables[i]);
				builder.clear();
				builder = builder2 + grabText;
			}
			return builder;
		}

		case SVT_STACK: {
			Common::String builder = "ARRAY:";
			Common::String builder2 = "";
			Common::String grabText = "";

			VariableStack *stacky = from.varData.theStack->first;

			while (stacky) {
				builder2 = builder + " ";
				grabText = getTextFromAnyVar(stacky->thisVar);
				builder.clear();
				builder = builder2 + grabText;
				stacky = stacky->next;
			}
			return builder;
		}

		case SVT_INT: {
			Common::String buff = Common::String::format("%i", from.varData.intValue);
			return buff;
		}

		case SVT_FILE: {
			return resourceNameFromNum(from.varData.intValue);
		}

		case SVT_OBJTYPE: {
			ObjectType *thisType = g_sludge->_objMan->findObjectType(from.varData.intValue);
			if (thisType)
				return thisType->screenName;
			break;
		}

		default:
			break;
	}

	return typeName[from.varType];
}

bool getBoolean(const Variable &from) {
	switch (from.varType) {
		case SVT_NULL:
			return false;

		case SVT_INT:
			return (bool)(from.varData.intValue != 0);

		case SVT_STACK:
			return (bool)(from.varData.theStack->first != NULL);

		case SVT_STRING:
			return (bool)(from.varData.theString[0] != 0);

		case SVT_FASTARRAY:
			return (bool)(from.varData.fastArray->size != 0);

		default:
			break;
	}
	return true;
}

bool copyMain(const Variable &from, Variable &to) {
	to.varType = from.varType;
	switch (to.varType) {
		case SVT_INT:
		case SVT_FUNC:
		case SVT_BUILT:
		case SVT_FILE:
		case SVT_OBJTYPE:
			to.varData.intValue = from.varData.intValue;
			return true;

		case SVT_FASTARRAY:
			to.varData.fastArray = from.varData.fastArray;
			to.varData.fastArray->timesUsed++;
			return true;

		case SVT_STRING:
			to.varData.theString = createCString(from.varData.theString);
			return to.varData.theString ? true : false;

		case SVT_STACK:
			to.varData.theStack = from.varData.theStack;
			to.varData.theStack->timesUsed++;
			return true;

		case SVT_COSTUME:
			to.varData.costumeHandler = from.varData.costumeHandler;
			return true;

		case SVT_ANIM:
			to.varData.animHandler = copyAnim(from.varData.animHandler);
			return true;

		case SVT_NULL:
			return true;

		default:
			break;
	}
	fatal("Unknown value type");
	return false;
}

bool copyVariable(const Variable &from, Variable &to) {
	unlinkVar(to);
	return copyMain(from, to);
}

Variable *fastArrayGetByIndex(FastArrayHandler *vS, uint theIndex) {
	if ((int)theIndex >= vS->size)
		return NULL;
	return &vS->fastVariables[theIndex];
}

bool makeFastArraySize(Variable &to, int size) {
	if (size < 0)
		return fatal("Can't create a fast array with a negative number of elements!");
	unlinkVar(to);
	to.varType = SVT_FASTARRAY;
	to.varData.fastArray = new FastArrayHandler;
	if (!checkNew(to.varData.fastArray))
		return false;
	to.varData.fastArray->fastVariables = new Variable[size];
	if (!checkNew(to.varData.fastArray->fastVariables))
		return false;
	for (int i = 0; i < size; i++) {
		initVarNew(to.varData.fastArray->fastVariables[i]);
	}
	to.varData.fastArray->size = size;
	to.varData.fastArray->timesUsed = 1;
	return true;
}

bool makeFastArrayFromStack(Variable &to, const StackHandler *stacky) {
	int size = stackSize(stacky);
	if (!makeFastArraySize(to, size))
		return false;

	// Now let's fill up the new array

	VariableStack *allV = stacky->first;
	size = 0;
	while (allV) {
		copyMain(allV->thisVar, to.varData.fastArray->fastVariables[size]);
		size++;
		allV = allV->next;
	}
	return true;
}

bool addVarToStack(const Variable &va, VariableStack *&thisStack) {
	VariableStack *newStack = new VariableStack;
	if (!checkNew(newStack))
		return false;

	if (!copyMain(va, newStack->thisVar))
		return false;
	newStack->next = thisStack;
	thisStack = newStack;
	//debugC(2, kSludgeDebugStackMachine, "Variable %s was added to stack", getTextFromAnyVar(va));
	return true;
}

bool addVarToStackQuick(Variable &va, VariableStack *&thisStack) {
	VariableStack *newStack = new VariableStack;
	if (!checkNew(newStack))
		return false;

//	if (! copyMain (va, newStack -> thisVar)) return false;

	memcpy(&(newStack->thisVar), &va, sizeof(Variable));
	va.varType = SVT_NULL;

	newStack->next = thisStack;
	thisStack = newStack;
	//debugC(2, kSludgeDebugStackMachine, "Variable %s was added to stack quick", getTextFromAnyVar(va));
	return true;
}

bool stackSetByIndex(VariableStack *vS, uint theIndex, const Variable &va) {
	while (theIndex--) {
		vS = vS->next;
		if (!vS)
			return fatal("Index past end of stack.");
	}
	return copyVariable(va, vS->thisVar);
}

Variable *stackGetByIndex(VariableStack *vS, uint theIndex) {
	while (theIndex--) {
		vS = vS->next;
		if (!vS) {
			return NULL;
		}
	}
	return &(vS->thisVar);
}

int deleteVarFromStack(const Variable &va, VariableStack *&thisStack, bool allOfEm) {
	VariableStack **huntVar = &thisStack;
	VariableStack *killMe;
	int reply = 0;

	while (*huntVar) {
		if (compareVars((*huntVar)->thisVar, va)) {
			killMe = *huntVar;
			*huntVar = killMe->next;
			unlinkVar(killMe->thisVar);
			delete killMe;
			if (!allOfEm)
				return 1;
			reply++;
		} else {
			huntVar = &((*huntVar)->next);
		}
	}

	return reply;
}

// Would be a LOT better just to keep this up to date in the above function... ah well
VariableStack *stackFindLast(VariableStack *hunt) {
	if (hunt == NULL)
		return NULL;

	while (hunt->next)
		hunt = hunt->next;

	return hunt;
}

bool getValueType(int &toHere, VariableType vT, const Variable &v) {
	//if (! v) return false;
	if (v.varType != vT) {
		Common::String e1 = "Can only perform specified operation on a value which is of type ";
		e1 += typeName[vT];
		Common::String e2 = "... value supplied was of type ";
		e2 += typeName[v.varType];
		fatal(e1, e2);

		return false;
	}
	toHere = v.varData.intValue;
	return true;
}

void trimStack(VariableStack *&stack) {
	VariableStack *killMe = stack;
	stack = stack->next;

	//debugC(2, kSludgeDebugStackMachine, "Variable %s was removed from stack", getTextFromAnyVar(killMe->thisVar));

	// When calling this, we've ALWAYS checked that stack != NULL
	unlinkVar(killMe->thisVar);
	delete killMe;
}

} // End of namespace Sludge