/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "glk/alan3/instance.h"
#include "glk/alan3/memory.h"
#include "glk/alan3/syserr.h"
#include "glk/alan3/attribute.h"
#include "glk/alan3/current.h"
#include "glk/alan3/container.h"
#include "glk/alan3/debug.h"
#include "glk/alan3/checkentry.h"
#include "glk/alan3/glkio.h"
#include "glk/alan3/inter.h"
#include "glk/alan3/options.h"
#include "glk/alan3/output.h"
#include "glk/alan3/class.h"
#include "glk/alan3/msg.h"
#include "glk/alan3/actor.h"
#include "glk/alan3/literal.h"
#include "glk/alan3/dictionary.h"
#include "glk/alan3/location.h"
#include "glk/alan3/compatibility.h"

namespace Glk {
namespace Alan3 {

/* PUBLIC DATA */

InstanceEntry *instances;   /* Instance table pointer */

AdminEntry *admin;      /* Administrative data about instances */
AttributeEntry *attributes; /* Dynamic attribute values */

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

void AdminEntry::synchronize(Common::Serializer &s) {
	s.syncAsUint32LE(location);

	Aword attr = 0;
	s.syncAsUint32LE(attr);

	s.syncAsUint32LE(alreadyDescribed);
	s.syncAsUint32LE(visitsCount);
	s.syncAsUint32LE(script);
	s.syncAsUint32LE(step);
	s.syncAsUint32LE(waitCount);
}

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

/* Instance query methods */

/*======================================================================*/
bool isA(int instance, int ancestor) {
	int parent;

	if (isLiteral(instance))
		parent = literals[instance - header->instanceMax]._class;
	else
		parent = instances[instance].parent;
	while (parent != 0 && parent != ancestor)
		parent = classes[parent].parent;

	return (parent != 0);
}


bool isAObject(int instance) {
	return isA(instance, OBJECT);
}

bool isAContainer(int instance) {
	return instance != 0 && !isLiteral(instance) && instances[instance].container != 0;
}

bool isAActor(int instance) {
	return isA(instance, ACTOR);
}

bool isALocation(int instance) {
	return isA(instance, LOCATION);
}


bool isLiteral(int instance) {
	return instance > (int)header->instanceMax;
}

bool isANumeric(int instance) {
	return isLiteral(instance) && literals[literalFromInstance(instance)].type == NUMERIC_LITERAL;
}

bool isAString(int instance) {
	return isLiteral(instance) && literals[literalFromInstance(instance)].type == STRING_LITERAL;
}


/*======================================================================*/
bool isOpaque(int container) {
	return getInstanceAttribute(container, OPAQUEATTRIBUTE);
}


/*======================================================================*/
void setInstanceAttribute(int instance, int attribute, Aptr value) {
	char str[80];

	if (instance > 0 && instance <= (int)header->instanceMax) {
		setAttribute(admin[instance].attributes, attribute, value);
		if (isALocation(instance) && attribute != VISITSATTRIBUTE)
			/* If it wasn't the VISITSATTRIBUTE the location may have
			   changed so describe next time */
			admin[instance].visitsCount = 0;
	} else {
		Common::sprintf_s(str, "Can't SET/MAKE instance (%d).", instance);
		syserr(str);
	}
}


/*======================================================================*/
void setInstanceStringAttribute(int instance, int attribute, char *string) {
	deallocate(fromAptr(getInstanceAttribute(instance, attribute)));
	setInstanceAttribute(instance, attribute, toAptr(string));
}


/*======================================================================*/
void setInstanceSetAttribute(int instance, int attribute, Aptr set) {
	freeSet((Set *)fromAptr(getInstanceAttribute(instance, attribute)));
	setInstanceAttribute(instance, attribute, set);
}


/*----------------------------------------------------------------------*/
static Aptr literalAttribute(int literal, int attribute) {
	if (isPreBeta3(header->version)) {
		if (attribute == 1)
			return literals[literalFromInstance(literal)].value;
		else
			return 0;
	} else {
		if (attribute == 0)
			return literals[literalFromInstance(literal)].value;
		else
			return getAttribute(admin[header->instanceMax].attributes, attribute);
	}
	return (EOD);
}


/*======================================================================*/
Aptr getInstanceAttribute(int instance, int attribute) {
	char str[80];

	if (isLiteral(instance))
		return literalAttribute(instance, attribute);
	else {
		if (instance > 0 && instance <= (int)header->instanceMax) {
			if (attribute == -1)
				return locationOf(instance);
			else
				return getAttribute(admin[instance].attributes, attribute);
		} else {
			Common::sprintf_s(str, "Can't ATTRIBUTE item (%d).", instance);
			syserr(str);
		}
	}
	return (EOD);
}


/*======================================================================*/
char *getInstanceStringAttribute(int instance, int attribute) {
	return scumm_strdup((char *)fromAptr(getInstanceAttribute(instance, attribute)));
}


/*======================================================================*/
Set *getInstanceSetAttribute(int instance, int attribute) {
	return copySet((Set *)fromAptr(getInstanceAttribute(instance, attribute)));
}


/*----------------------------------------------------------------------*/
static void verifyInstance(int instance, const char *action) {
	char message[200];

	if (instance == 0) {
		Common::sprintf_s(message, "Can't %s instance (%d).", action, instance);
		syserr(message);
	} else if (instance > (int)header->instanceMax) {
		Common::sprintf_s(message, "Can't %s instance (%d > instanceMax).", action, instance);
		syserr(message);
	}
}


/*======================================================================*/
bool isHere(int id, ATrans transitivity) {
	verifyInstance(id, "HERE");

	return isAt(id, current.location, transitivity);
}


/*======================================================================*/
bool isNearby(int instance, ATrans transitivity) {
	verifyInstance(instance, "NEARBY");

	if (isALocation(instance))
		return exitto(current.location, instance);
	else
		return exitto(current.location, where(instance, transitivity));
}


/*======================================================================*/
bool isNear(int instance, int other, ATrans trans) {
	Aint l1, l2;

	verifyInstance(instance, "NEAR");

	if (isALocation(instance))
		l1 = instance;
	else
		l1 = where(instance, trans);
	if (isALocation(other))
		l2 = other;
	else
		l2 = where(other, trans);
	return exitto(l2, l1);
}


/*======================================================================*/
/* Look in a container to see if the instance is in it. */
bool isIn(int instance, int container, ATrans trans) {
	int loc;

	if (!isAContainer(container))
		syserr("IN in a non-container.");

	if (trans == DIRECT)
		return admin[instance].location == container;
	else {
		loc = admin[instance].location;
		if (trans == INDIRECT && loc != 0 && !isA(loc, LOCATION))
			loc = admin[loc].location;
		while (loc != 0 && !isA(loc, LOCATION))
			if (loc == container)
				return TRUE;
			else
				loc = admin[loc].location;
		return FALSE;
	}
}



/*======================================================================*/
/* Look see if an instance is AT another. */
bool isAt(int instance, int other, ATrans trans) {
	if (instance == 0 || other == 0) return FALSE;

	if (isALocation(instance)) {
		/* Nested locations */
		/* TODO - What if the other is not a location? */
		int curr = admin[instance].location;
		switch (trans) {
		case DIRECT:
			return admin[instance].location == other;

		case INDIRECT:
			if (curr == other)
				return FALSE;
			curr = admin[curr].location;
			// fall through

		case TRANSITIVE:
			while (curr != 0) {
				if (curr == other)
					return TRUE;
				else
					curr = admin[curr].location;
			}
			return FALSE;

		default:
			break;
		}

		syserr("Unexpected value in switch in isAt() for location");
		return FALSE;
	} else if (isALocation(other)) {
		/* Instance is not a location but other is */
		switch (trans) {
		case DIRECT:
			return admin[instance].location == other;
		case INDIRECT:
			if (admin[instance].location == other)
				return FALSE;   /* Directly, so not Indirectly */
			// fall through
		case TRANSITIVE: {
			int location = locationOf(instance);
			int curr = other;
			while (curr != 0) {
				if (curr == location)
					return TRUE;
				else
					curr = admin[curr].location;
			}
			return FALSE;
		}
		default:
			break;
		}
		syserr("Unexpected value in switch in isAt() for non-location");
		return FALSE;
	} else {
		/* Other is also not a location */
		switch (trans) {
		case DIRECT:
			return positionOf(instance) == admin[other].location;
		case INDIRECT: {
			int location = locationOf(instance);
			int curr = other;
			if (location == curr)
				return FALSE;
			else
				curr = admin[curr].location;
			while (curr != 0) {
				if (curr == location)
					return TRUE;
				else
					curr = admin[curr].location;
			}
			return FALSE;
		}
		case TRANSITIVE: {
			int location = locationOf(other);
			int curr = locationOf(instance);
			bool ok = FALSE;
			while (curr != 0 && !ok) {
				if (curr == location)
					ok = TRUE;
				else
					curr = admin[curr].location;
			}
			return ok;
		}
		default:
			break;
		}
		syserr("Unexpected value in switch in isAt() for non-location");
		return FALSE;
	}
}


/*======================================================================*/
/* Return the *location* of an instance, transitively, i.e. the first
   location instance found when traversing the containment/position
   links. If that didn't turn up a location see if it was in a
   container that is somewhere, or a THING that is nowhere. It might
   also be an ENTITY which is always everywhere so take that to mean
   where the hero is. */
int locationOf(int instance) {
	int position;
	int container = 0;

	verifyInstance(instance, "get LOCATION of");

	position = admin[instance].location;
	while (position != 0 && !isALocation(position)) {
		container = position;   /* Remember innermost container */
		position = admin[position].location;
	}
	if (position > NOWHERE) /* It was a location so return that */
		return position;
	else {
		/* If we did not find a location then it might be in a container */
		if (container != 0)
			instance = container;
		/* If the instance or the container it was in is a THING then its nowhere. */
		if (isA(instance, THING))
			return NOWHERE;     /* #nowhere */
		else if (isALocation(instance))
			return NO_LOCATION; /* No location */
		else
			return locationOf(HERO);
	}
}


/*======================================================================*/
/* Return the current position of an instance, directly or not */
/* TODO: this will be a possible duplicate of where() */
int positionOf(int instance) {
	return admin[instance].location;
}


/*======================================================================*/
/* Return the current position of an instance, directly or not */
int where(int instance, ATrans trans) {
	verifyInstance(instance, "WHERE");

	if (isALocation(instance))
		return 0;
	else if (trans == DIRECT)
		return admin[instance].location;
	else
		return locationOf(instance);
}


/*----------------------------------------------------------------------*/
static bool executeInheritedMentioned(CONTEXT, int cls) {
	if (cls == 0) return FALSE;

	if (classes[cls].mentioned) {
		R0CALL1(interpret, classes[cls].mentioned)
		return TRUE;
	} else {
		bool flag;
		R0FUNC1(executeInheritedMentioned, flag, classes[cls].parent)
		return flag;
	}
}


/*----------------------------------------------------------------------*/
static bool mention(CONTEXT, int instance) {
	if (instances[instance].mentioned) {
		R0CALL1(interpret, instances[instance].mentioned)
		return TRUE;
	} else {
		bool flag;
		R0FUNC1(executeInheritedMentioned, flag, instances[instance].parent)
		return flag;
	}
}


/*======================================================================*/
void sayInstance(CONTEXT, int instance) {
#ifdef SAY_INSTANCE_WITH_PLAYER_WORDS_IF_PARAMETER
	int p, i;

	/* Find the id in the parameters... */
	if (params != NULL)
		for (p = 0; params[p].code != EOD; p++)
			if (params[p].code == instance) {
				/* Found it so.. */
				if (params[p].firstWord == EOD) /* Any words he used? */
					break;      /* No... */
				else {              /* Yes, so use them... */
					char *capitalized;
					/* Assuming the noun is the last word we can simply output the adjectives... */
					for (i = params[p].firstWord; i <= params[p].lastWord - 1; i++)
						output((char *)pointerTo(dict[wrds[i]].wrd));
					/* ... and then the noun, capitalized if necessary */
					if (header->capitalizeNouns) {
						capitalized = scumm_strdup((char *)pointerTo(dict[wrds[params[p].lastWord]].wrd));
						capitalized[0] = IsoToUpperCase(capitalized[0]);
						output(capitalized);
						deallocate(capitalized);
					} else
						output((char *)pointerTo(dict[wrds[params[p].lastWord]].wrd));
				}
				return;
			}
#endif

	bool flag;
	FUNC1(mention, flag, instance)
	if (!flag)
		CALL1(interpret, instances[instance].name)
}


/*======================================================================*/
void sayInteger(int value) {
	char buf[25];

	if (isHere(HERO, /*FALSE*/ TRANSITIVE)) {
		Common::sprintf_s(buf, "%d", value);
		output(buf);
	}
}


/*======================================================================*/
void sayString(char *string) {
	if (isHere(HERO, /*FALSE*/ TRANSITIVE))
		output(string);
	deallocate(string);
}


/*----------------------------------------------------------------------*/
static void sayLiteral(int literal) {
	char *str;

	if (isANumeric(literal))
		sayInteger(literals[literal - header->instanceMax].value);
	else {
		str = (char *)scumm_strdup((char *)fromAptr(literals[literal - header->instanceMax].value));
		sayString(str);
	}
}


/*----------------------------------------------------------------------*/
static char *wordWithCode(int classBit, int code) {
	int w;
	char str[50];

	for (w = 0; w < dictionarySize; w++)
		if (dictionary[w].code == (Aword)code && ((classBit & dictionary[w].classBits) != 0))
			return (char *)pointerTo(dictionary[w].string);
	Common::sprintf_s(str, "Could not find word of class %d with code %d.", classBit, code);
	syserr(str);
	return nullptr;
}


/*----------------------------------------------------------------------*/
static bool sayInheritedDefiniteForm(CONTEXT, int cls) {
	if (cls == 0) {
		syserr("No default definite article");
		return FALSE;
	} else {
		if (classes[cls].definite.address) {
			R0CALL1(interpret, classes[cls].definite.address)
			return classes[cls].definite.isForm;
		} else {
			bool flag;
			R0FUNC1(sayInheritedDefiniteForm, flag, classes[cls].parent)
			return flag;
		}
	}
}


/*----------------------------------------------------------------------*/
static void sayDefinite(CONTEXT, int instance) {
	if (instances[instance].definite.address) {
		CALL1(interpret, instances[instance].definite.address)

		if (!instances[instance].definite.isForm)
			CALL1(sayInstance, instance)
	} else {
		bool flag;
		FUNC1(sayInheritedDefiniteForm, flag, instances[instance].parent)
		if (!flag)
			CALL1(sayInstance, instance)
	}
}


/*----------------------------------------------------------------------*/
static bool sayInheritedIndefiniteForm(CONTEXT, int cls) {
	if (cls == 0) {
		syserr("No default indefinite article");
		return FALSE;
	} else {
		if (classes[cls].indefinite.address) {
			R0CALL1(interpret, classes[cls].indefinite.address)
			return classes[cls].indefinite.isForm;
		} else {
			bool flag;
			R0FUNC1(sayInheritedIndefiniteForm, flag, classes[cls].parent)
			return flag;
		}
	}
}


/*----------------------------------------------------------------------*/
static void sayIndefinite(CONTEXT, int instance) {
	if (instances[instance].indefinite.address) {
		CALL1(interpret, instances[instance].indefinite.address)

		if (!instances[instance].indefinite.isForm)
			CALL1(sayInstance, instance)
	} else {
		bool flag;
		FUNC1(sayInheritedIndefiniteForm, flag, instances[instance].parent)
		if (!flag)
			CALL1(sayInstance, instance)
	}
}


/*----------------------------------------------------------------------*/
static bool sayInheritedNegativeForm(CONTEXT, int cls) {
	if (cls == 0) {
		syserr("No default negative form");
		return FALSE;
	} else {
		if (classes[cls].negative.address) {
			R0CALL1(interpret, classes[cls].negative.address)
			return classes[cls].negative.isForm;
		} else {
			bool flag;
			R0FUNC1(sayInheritedNegativeForm, flag, classes[cls].parent)
			return flag;
		}
	}
}


/*----------------------------------------------------------------------*/
static void sayNegative(CONTEXT, int instance) {
	if (instances[instance].negative.address) {
		CALL1(interpret, instances[instance].negative.address)
		if (!instances[instance].negative.isForm)
			CALL1(sayInstance, instance)
	} else {
		bool flag;
		FUNC1(sayInheritedNegativeForm, flag, instances[instance].parent)
		if (!flag)
			CALL1(sayInstance, instance)
	}
}


/*----------------------------------------------------------------------*/
static void sayInheritedPronoun(CONTEXT, int instance) {
	if (instance == 0)
		syserr("No default pronoun");
	else {
		if (classes[instance].pronoun != 0)
			output(wordWithCode(PRONOUN_BIT, classes[instance].pronoun));
		else
			CALL1(sayInheritedPronoun, classes[instance].parent)
	}
}


/*----------------------------------------------------------------------*/
static void sayPronoun(CONTEXT, int instance) {
	if (instances[instance].pronoun != 0)
		output(wordWithCode(PRONOUN_BIT, instances[instance].pronoun));
	else
		CALL1(sayInheritedPronoun, instances[instance].parent)
}


/*----------------------------------------------------------------------*/
static void sayArticleOrForm(CONTEXT, int id, SayForm form) {
	if (!isLiteral(id)) {
		switch (form) {
		case SAY_DEFINITE:
			CALL1(sayDefinite, id)
			break;
		case SAY_INDEFINITE:
			CALL1(sayIndefinite, id)
			break;
		case SAY_NEGATIVE:
			CALL1(sayNegative, id)
			break;
		case SAY_PRONOUN:
			CALL1(sayPronoun, id)
			break;
		case SAY_SIMPLE:
			CALL1(say, id)
			break;
		default:
			syserr("Unexpected form in 'sayArticleOrForm()'");
		}
	} else {
		CALL1(say, id)
	}
}


/*======================================================================*/
void say(CONTEXT, int instance) {
	Aword previousInstance = current.instance;
	current.instance = instance;

	if (isHere(HERO, /*FALSE*/ TRANSITIVE)) {
		if (isLiteral(instance))
			sayLiteral(instance);
		else {
			verifyInstance(instance, "SAY");
			sayInstance(context, instance);
		}
	}
	current.instance = previousInstance;
}


/*======================================================================*/
void sayForm(CONTEXT, int instance, SayForm form) {
	Aword previousInstance = current.instance;
	current.instance = instance;

	sayArticleOrForm(context, instance, form);

	current.instance = previousInstance;
}


/*======================================================================*/
bool isDescribable(int instance) {
	return isAObject(instance) || isAActor(instance);
}


/*----------------------------------------------------------------------*/
static bool inheritsDescriptionFrom(int cls) {
	if (classes[cls].description != 0)
		return TRUE;
	else if (classes[cls].parent != 0)
		return inheritsDescriptionFrom(classes[cls].parent);
	else
		return FALSE;
}


/*======================================================================*/
bool hasDescription(int instance) {
	if (instances[instance].description != 0)
		return TRUE;
	else if (instances[instance].parent != 0)
		return inheritsDescriptionFrom(instances[instance].parent);
	else
		return FALSE;
}


/*----------------------------------------------------------------------*/
static void describeClass(CONTEXT, int instance) {
	if (classes[instance].description != 0) {
		/* This class has a description, run it */
		CALL1(interpret, classes[instance].description)
	} else {
		/* Search up the inheritance tree, if any, to find a description */
		if (classes[instance].parent != 0)
			CALL1(describeClass, classes[instance].parent)
	}
}


/*======================================================================*/
void describeAnything(CONTEXT, int instance) {
	if (instances[instance].description != 0) {
		/* This instance has its own description, run it */
		CALL1(interpret, instances[instance].description)
	} else {
		/* Search up the inheritance tree to find a description */
		if (instances[instance].parent != 0)
			CALL1(describeClass, instances[instance].parent)
	}
	admin[instance].alreadyDescribed = TRUE;
}


/*----------------------------------------------------------------------*/
static void describeObject(CONTEXT, int object) {
	if (hasDescription(object)) {
		CALL1(describeAnything, object)
	} else {
		printMessageWithInstanceParameter(M_SEE_START, object);
		printMessage(M_SEE_END);
		if (instances[object].container != 0)
			CALL1(describeContainer, object)
	}
	admin[object].alreadyDescribed = TRUE;
}


/*----------------------------------------------------------------------*/
static bool inheritedDescriptionCheck(CONTEXT, int cls) {
	if (cls == 0) return TRUE;

	bool flag;
	R0FUNC1(inheritedDescriptionCheck, flag, classes[cls].parent)
	if (!flag) return FALSE;

	if (classes[cls].descriptionChecks == 0) return TRUE;
	R0FUNC2(checksFailed, flag, classes[cls].descriptionChecks, TRUE)
	return !flag;
}

/*----------------------------------------------------------------------*/
static bool descriptionCheck(CONTEXT, int instance) {
	int previousInstance = current.instance;
	bool r;

	current.instance = instance;
	if (inheritedDescriptionCheck(context, instances[instance].parent)) {
		if (instances[instance].checks == 0)
			r = TRUE;
		else
			r = !checksFailed(context, instances[instance].checks, TRUE);
	}
	else
		r = FALSE;
	current.instance = previousInstance;
	return r;
}


/*======================================================================*/
void describeInstances(CONTEXT) {
	uint i;
	int lastInstanceFound = 0;
	int found = 0;

	/* First describe every object here with its own description */
	for (i = 1; i <= header->instanceMax; i++)
		if (admin[i].location == current.location && isAObject(i) &&
		        !admin[i].alreadyDescribed && hasDescription(i))
			CALL1(describe, i)

	/* Then list all things without a description */
	for (i = 1; i <= header->instanceMax; i++)
		if (admin[i].location == current.location
		        && !admin[i].alreadyDescribed
		        && isAObject(i)
		        && descriptionCheck(context, i)) {
			if (found == 0)
				printMessageWithInstanceParameter(M_SEE_START, i);
			else if (found > 1)
				printMessageWithInstanceParameter(M_SEE_COMMA, lastInstanceFound);
			admin[i].alreadyDescribed = TRUE;

			// TODO : isOpaque()
			if (instances[i].container && containerSize(i, DIRECT) > 0 && !getInstanceAttribute(i, OPAQUEATTRIBUTE)) {
				if (found > 0)
					printMessageWithInstanceParameter(M_SEE_AND, i);
				printMessage(M_SEE_END);
				CALL1(describeContainer, i)
				found = 0;
				continue;       /* Actually start another list. */
			}
			found++;
			lastInstanceFound = i;
		}

	if (found > 0) {
		if (found > 1) {
			printMessageWithInstanceParameter(M_SEE_AND, lastInstanceFound);
		}
		printMessage(M_SEE_END);
	}

	/* Finally all actors with a separate description */
	for (i = 1; i <= header->instanceMax; i++)
		if (admin[i].location == current.location && i != HERO && isAActor(i)
		        && !admin[i].alreadyDescribed)
			CALL1(describe, i)

	/* Clear the describe flag for all instances */
	for (i = 1; i <= header->instanceMax; i++)
		admin[i].alreadyDescribed = FALSE;
}


/*======================================================================*/
bool describe(CONTEXT, int instance) {
	bool descriptionOk;
	int previousInstance = current.instance;

	current.instance = instance;
	verifyInstance(instance, "DESCRIBE");

	if (descriptionCheck(context, instance)) {
		descriptionOk = TRUE;
		if (isAObject(instance)) {
			describeObject(context, instance);
		} else if (isAActor(instance)) {
			describeActor(context, instance);
		} else
			describeAnything(context, instance);
	} else
		descriptionOk = FALSE;

	current.instance = previousInstance;
	return descriptionOk;
}


/*----------------------------------------------------------------------*/
static void locateIntoContainer(CONTEXT, Aword theInstance, Aword theContainer) {
	if (!isA(theInstance, containers[instances[theContainer].container]._class))
		printMessageUsing2InstanceParameters(M_CANNOTCONTAIN, theContainer, theInstance);
	else if (passesContainerLimits(context, theContainer, theInstance))
		admin[theInstance].location = theContainer;
	else
		abortPlayerCommand(context);
}


/*----------------------------------------------------------------------*/
static void locateLocation(Aword loc, Aword whr) {
	Aint l = whr;

	/* Ensure this does not create a recursive location chain */
	while (l != 0) {
		if (admin[l].location == (int)loc)
			apperr("Locating a location that would create a recursive loop of locations containing each other.");
		else
			l = admin[l].location;
	}
	admin[loc].location = whr;
}


/*----------------------------------------------------------------------*/
static void locateObject(CONTEXT, Aword obj, Aword whr) {
	if (isAContainer(whr)) { /* Into a container */
		locateIntoContainer(context, obj, whr);
	} else {
		admin[obj].location = whr;
		/* Make sure the location is described since it's changed */
		admin[whr].visitsCount = 0;
	}
}


/*----------------------------------------------------------------------*/
static void traceEnteredClass(Aint theClass, bool empty) {
	printf("\n<ENTERED in class ");
	printf("%s", idOfClass(theClass));
	printf("[%d]%s>\n", theClass, empty ? " is empty" : ":");
}


/*----------------------------------------------------------------------*/
static void traceEnteredInstance(CONTEXT, Aint instance, bool empty) {
	printf("\n<ENTERED in instance ");
	traceSay(context, instance);
	printf("[%d]%s>\n", instance, empty ? " is empty" : "");
}


/*----------------------------------------------------------------------*/
static void executeInheritedEntered(CONTEXT, Aint theClass) {
	if (theClass == 0) return;
	CALL1(executeInheritedEntered, classes[theClass].parent)

	if (traceSectionOption)
		traceEnteredClass(theClass, classes[theClass].entered == 0);
	if (classes[theClass].entered) {
		CALL1(interpret, classes[theClass].entered)
	}
}


/*----------------------------------------------------------------------*/
static void executeEntered(CONTEXT, Aint instance) {
	int currentInstance = current.instance;
	current.instance = instance;
	if (admin[instance].location != 0)
		CALL1(executeEntered, admin[instance].location)
	CALL1(executeInheritedEntered, instances[instance].parent)

	if (traceSectionOption)
		CALL2(traceEnteredInstance, instance, instances[instance].entered == 0)
	if (instances[instance].entered != 0) {
		CALL1(interpret, instances[instance].entered)
	}
	current.instance = currentInstance;
}


/*----------------------------------------------------------------------*/
static int getVisits(int location) {
	return getInstanceAttribute(location, VISITSATTRIBUTE);
}


/*----------------------------------------------------------------------*/
static void incrementVisits(int location) {
	setInstanceAttribute(location, VISITSATTRIBUTE, getVisits(location) + 1);
	if (admin[location].location != 0)
		/* Nested location, so increment that too */
		incrementVisits(admin[location].location);
}


/*----------------------------------------------------------------------*/
static void revisited(CONTEXT) {
	if (anyOutput)
		para();
	CALL1(say, where(HERO, DIRECT))
	printMessage(M_AGAIN);
	newline();
	CALL0(describeInstances)
}


/*----------------------------------------------------------------------*/
static bool shouldBeDescribed(void) {
	if (!isPreBeta5(header->version))
		return getVisits(admin[HERO].location) % (current.visits + 1) == 0
		       || admin[admin[HERO].location].visitsCount == 0;
	else
		return admin[admin[HERO].location].visitsCount % (current.visits + 1) == 0;
}


/*----------------------------------------------------------------------*/
static void locateActor(CONTEXT, Aint movingActor, Aint whr) {
	Aint previousCurrentLocation = current.location;
	Aint previousActorLocation = admin[movingActor].location;
	Aint previousActor = current.actor;
	Aint previousInstance = current.instance;

	/* Before leaving, remember that we visited the location */
	if (!isPreBeta5(header->version))
		if (movingActor == (int)HERO)
			incrementVisits(where(HERO, DIRECT));

	/* TODO Actors locating into containers is dubious, anyway as it
	   is now it allows the hero to be located into a container. And what
	   happens with current location if so... */
	if (isAContainer(whr))
		CALL2(locateIntoContainer, movingActor, whr)
	else {
		current.location = whr;
		admin[movingActor].location = whr;
	}

	/* Now we have moved, so show what is needed... */
	current.instance = current.location;

	/* Execute possible entered */
	current.actor = movingActor;
	if (previousActorLocation != current.location) {
		CALL1(executeEntered, current.location)
	}
	current.instance = previousInstance;
	current.actor = previousActor;

	if (movingActor == (int)HERO) {
		if (shouldBeDescribed()) {
			CALL0(look)
		} else {
			CALL0(revisited)
		}
		admin[where(HERO, DIRECT)].visitsCount++;
	} else
		/* Ensure that the location will be described to the hero next time */
		admin[whr].visitsCount = 0;

	if (current.actor != movingActor)
		current.location = previousCurrentLocation;

	current.instance = previousInstance;
}


/*----------------------------------------------------------------------*/
static void traceExtract(CONTEXT, int instance, int containerId, const char *what) {
	if (traceSectionOption) {
		printf("\n<EXTRACT from ");
		traceSay(context, instance);
		printf("[%d, container %d], %s:>\n", instance, containerId, what);
	}
}


/*----------------------------------------------------------------------*/
static void containmentLoopError(CONTEXT, int instance, int whr) {
	ParameterArray parameters = newParameterArray();
	if (isPreBeta4(header->version))
		output("That would be to put something inside itself.");
	else if (whr == instance) {
		addParameterForInstance(parameters, instance);
		printMessageWithParameters(M_CONTAINMENT_LOOP, parameters);
	} else {
		addParameterForInstance(parameters, instance);
		addParameterForInstance(parameters, whr);
		printMessageWithParameters(M_CONTAINMENT_LOOP2, parameters);
	}
	free(parameters);
	CALL1(error, NO_MSG)
}


/*----------------------------------------------------------------------*/
static void runExtractStatements(CONTEXT, int instance, int containerId) {
	ContainerEntry *theContainer = &containers[containerId];

	if (theContainer->extractStatements != 0) {
		CALL3(traceExtract, instance, containerId, "Executing")
		CALL1(interpret, theContainer->extractStatements)
	}
}


/*----------------------------------------------------------------------*/
static bool runExtractChecks(CONTEXT, int instance, int containerId) {
	ContainerEntry *theContainer = &containers[containerId];

	if (theContainer->extractChecks != 0) {
		R0CALL3(traceExtract, instance, containerId, "Checking")
		if (checksFailed(context, theContainer->extractChecks, EXECUTE_CHECK_BODY_ON_FAIL)) {
			fail = TRUE;
			return FALSE;       /* Failed! */
		}
	}
	return TRUE;                /* Passed! */
}


/*======================================================================*/
void locate(CONTEXT, int instance, int whr) {
	int containerId;
	int previousInstance = current.instance;

	verifyInstance(instance, "LOCATE");
	verifyInstance(whr, "LOCATE AT");

	/* Will this create a containment loop? */
	if (whr == instance || (isAContainer(instance) && isIn(whr, instance, TRANSITIVE)))
		CALL2(containmentLoopError, instance, whr)

	/* First check if the instance is in a container, if so run extract checks */
	if (isAContainer(admin[instance].location)) {    /* In something? */
		int loc = admin[instance].location;

		/* Run all nested extraction checks */
		while (isAContainer(loc)) {
			current.instance = loc;
			containerId = instances[loc].container;

			if (!runExtractChecks(context, instance, containerId)) {
				current.instance = previousInstance;
				return;
			}
			runExtractStatements(context, instance, containerId);
			loc = admin[loc].location;
		}
		current.instance = previousInstance;
	}

	if (isAActor(instance)) {
		CALL2(locateActor, instance, whr)
	} else if (isALocation(instance)) {
		locateLocation(instance, whr);
	} else {
		CALL2(locateObject, instance, whr)
	}
	gameStateChanged = TRUE;
}

} // End of namespace Alan3
} // End of namespace Glk
