/* 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 Variable::unlinkVar() {
	switch (varType) {
		case SVT_STRING:
			delete []varData.theString;
			varData.theString = NULL;
			break;

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

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

		case SVT_ANIM:
			if (varData.animHandler) {
				delete varData.animHandler;
				varData.animHandler = nullptr;
			}
			break;

		default:
			break;
	}
}

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

void Variable::makeAnimationVariable(PersonaAnimation  *i) {
	unlinkVar();
	varType = SVT_ANIM;
	varData.animHandler = i;
}

PersonaAnimation *Variable::getAnimationFromVar() {
	if (varType == SVT_ANIM)
		return new PersonaAnimation(varData.animHandler);

	if (varType == SVT_INT && varData.intValue == 0)
		return new PersonaAnimation();

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

void Variable::makeCostumeVariable(Persona *i) {
	unlinkVar();
	varType = SVT_COSTUME;
	varData.costumeHandler = i;
}

Persona *Variable::getCostumeFromVar() {
	Persona *p = NULL;

	switch (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] = new PersonaAnimation(varData.animHandler);

			break;

		case SVT_COSTUME:
			return varData.costumeHandler;
			break;

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

	return p;
}

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

bool StackHandler::getSavedGamesStack(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);
		newName.makeTextVar((*it));
		if (!addVarToStack(newName, first))
			return false;
		if (last == NULL)
			last = first;
	}

	return true;
}

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

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

	return true;
}

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

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

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

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

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

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

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

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

void Variable::compareVariablesInSecond(const Variable &other) {
	setVariable(SVT_INT, compareVars(other));
}

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

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

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

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

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

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

			VariableStack *stacky = varData.theStack->first;

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

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

		case SVT_FILE: {
			return g_sludge->_resMan->resourceNameFromNum(varData.intValue);
		}

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

		default:
			break;
	}

	return typeName[varType];
}

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

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

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

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

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

		default:
			break;
	}
	return true;
}

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

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

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

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

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

		case SVT_ANIM:
			varData.animHandler = new PersonaAnimation(from.varData.animHandler);
			return true;

		case SVT_NULL:
			return true;

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

bool Variable::copyFrom(const Variable &from) {
	unlinkVar();
	return copyMain(from);
}

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

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

bool Variable::makeFastArrayFromStack(const StackHandler *stacky) {
	int size = stacky->getStackSize();
	if (!makeFastArraySize(size))
		return false;

	// Now let's fill up the new array

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

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

	if (!newStack->thisVar.copyMain(va))
		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 VariableStack::stackSetByIndex(uint theIndex, const Variable &va) {
	VariableStack *vS = this;
	while (theIndex--) {
		vS = vS->next;
		if (!vS)
			return fatal("Index past end of stack.");
	}
	return vS->thisVar.copyFrom(va);
}

Variable *VariableStack::stackGetByIndex(uint theIndex) {
	VariableStack *vS = this;
	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 (va.compareVars((*huntVar)->thisVar)) {
			killMe = *huntVar;
			*huntVar = killMe->next;
			killMe->thisVar.unlinkVar();
			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 *VariableStack::stackFindLast() {
	VariableStack *hunt = this;
	while (hunt->next)
		hunt = hunt->next;

	return hunt;
}

bool Variable::getValueType(int &toHere, VariableType vT) const {
	if (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[varType];
		fatal(e1, e2);

		return false;
	}
	toHere = 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
	killMe->thisVar.unlinkVar();
	delete killMe;
}

//----------------------------------------------------------------------
// Globals (so we know what's saved already and what's a reference
//----------------------------------------------------------------------

struct stackLibrary {
	StackHandler *stack;
	stackLibrary *next;
};

int stackLibTotal = 0;
stackLibrary *stackLib = NULL;

//----------------------------------------------------------------------
// For saving and loading stacks...
//----------------------------------------------------------------------
void saveStack(VariableStack *vs, Common::WriteStream *stream) {
	int elements = 0;
	int a;

	VariableStack *search = vs;
	while (search) {
		elements++;
		search = search->next;
	}

	stream->writeUint16BE(elements);
	search = vs;
	for (a = 0; a < elements; a++) {
		search->thisVar.save(stream);
		search = search->next;
	}
}

VariableStack *loadStack(Common::SeekableReadStream *stream, VariableStack **last) {
	int elements = stream->readUint16BE();
	int a;
	VariableStack *first = NULL;
	VariableStack **changeMe = &first;

	for (a = 0; a < elements; a++) {
		VariableStack *nS = new VariableStack;
		if (!checkNew(nS))
			return NULL;
		nS->thisVar.load(stream);
		if (last && a == elements - 1) {
			*last = nS;
		}
		nS->next = NULL;
		(*changeMe) = nS;
		changeMe = &(nS->next);
	}

	return first;
}

bool saveStackRef(StackHandler *vs, Common::WriteStream *stream) {
	stackLibrary *s = stackLib;
	int a = 0;
	while (s) {
		if (s->stack == vs) {
			stream->writeByte(1);
			stream->writeUint16BE(stackLibTotal - a);
			return true;
		}
		s = s->next;
		a++;
	}
	stream->writeByte(0);
	saveStack(vs->first, stream);
	s = new stackLibrary;
	stackLibTotal++;
	if (!checkNew(s))
		return false;
	s->next = stackLib;
	s->stack = vs;
	stackLib = s;
	return true;
}

void clearStackLib() {
	stackLibrary *k;
	while (stackLib) {
		k = stackLib;
		stackLib = stackLib->next;
		delete k;
	}
	stackLibTotal = 0;
}

StackHandler *getStackFromLibrary(int n) {
	n = stackLibTotal - n;
	while (n) {
		stackLib = stackLib->next;
		n--;
	}
	return stackLib->stack;
}

StackHandler *loadStackRef(Common::SeekableReadStream *stream) {
	StackHandler *nsh;

	if (stream->readByte()) {    // It's one we've loaded already...
		nsh = getStackFromLibrary(stream->readUint16BE());
		nsh->timesUsed++;
	} else {
		// Load the new stack

		nsh = new StackHandler;
		if (!checkNew(nsh))
			return NULL;
		nsh->last = NULL;
		nsh->first = loadStack(stream, &nsh->last);
		nsh->timesUsed = 1;

		// Add it to the library of loaded stacks

		stackLibrary *s = new stackLibrary;
		if (!checkNew(s))
			return NULL;
		s->stack = nsh;
		s->next = stackLib;
		stackLib = s;
		stackLibTotal++;
	}
	return nsh;
}

//----------------------------------------------------------------------
// For saving and loading variables...
//----------------------------------------------------------------------
bool Variable::save(Common::WriteStream *stream) {
	stream->writeByte(varType);
	switch (varType) {
		case SVT_INT:
		case SVT_FUNC:
		case SVT_BUILT:
		case SVT_FILE:
		case SVT_OBJTYPE:
			stream->writeUint32LE(varData.intValue);
			return true;

		case SVT_STRING:
			writeString(varData.theString, stream);
			return true;

		case SVT_STACK:
			return saveStackRef(varData.theStack, stream);

		case SVT_COSTUME:
			varData.costumeHandler->save(stream);
			return false;

		case SVT_ANIM:
			varData.animHandler->save(stream);
			return false;

		case SVT_NULL:
			return false;

		default:
			fatal("Can't save variables of this type:", (varType < SVT_NUM_TYPES) ? typeName[varType] : "bad ID");
	}
	return true;
}

bool Variable::load(Common::SeekableReadStream *stream) {
	varType = (VariableType)stream->readByte();
	switch (varType) {
		case SVT_INT:
		case SVT_FUNC:
		case SVT_BUILT:
		case SVT_FILE:
		case SVT_OBJTYPE:
			varData.intValue = stream->readUint32LE();
			return true;

		case SVT_STRING:
			varData.theString = createCString(readString(stream));
			return true;

		case SVT_STACK:
			varData.theStack = loadStackRef(stream);
			return true;

		case SVT_COSTUME:
			varData.costumeHandler = new Persona;
			if (!checkNew(varData.costumeHandler))
				return false;
			varData.costumeHandler->load(stream);
			return true;

		case SVT_ANIM:
			varData.animHandler = new PersonaAnimation;
			if (!checkNew(varData.animHandler))
				return false;
			varData.animHandler->load(stream);
			return true;

		default:
			break;
	}
	return true;
}

} // End of namespace Sludge