/* 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 "common/config-manager.h"
#include "common/debug-channels.h"
#include "glk/archetype/archetype.h"
#include "glk/archetype/crypt.h"
#include "glk/archetype/expression.h"
#include "glk/archetype/heap_sort.h"
#include "glk/archetype/misc.h"
#include "glk/archetype/saveload.h"
#include "glk/archetype/sys_object.h"
#include "glk/archetype/timestamp.h"
#include "glk/archetype/game_stat.h"

namespace Glk {
namespace Archetype {

#define MAX_INPUT_LINE 100

Archetype *g_vm;

Archetype::Archetype(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
		_saveSlot(-1) {
	g_vm = this;

	DebugMan.addDebugChannel(DEBUG_BYTES, "bytes", "Memory usage");
	DebugMan.addDebugChannel(DEBUG_MSGS, "messages", "Messages debugging");
	DebugMan.addDebugChannel(DEBUG_EXPR, "expressions", "Expressions debugging");
	DebugMan.addDebugChannel(DEBUG_STMT, "statements", "Statements debugging");
}

void Archetype::runGame() {
	initialize();
	interpret();
	deinitialize();
}

bool Archetype::initialize() {
	crypt_init();
	expression_init();
	heap_sort_init();
	misc_init();
	saveload_init();
	sys_object_init();
	timestamp_init();

	// keywords
	new_xarray(Literals);
	new_xarray(Vocabulary);

	new_xarray(Type_ID_List);
	new_xarray(Object_ID_List);
	new_xarray(Attribute_ID_List);

	// parser
	Abbreviate = 0x7fffffff;
	new_list(Proximate);
	new_list(object_names);
	new_list(verb_names);

	// semantic
	new_xarray(Type_List);
	new_xarray(Object_List);
	NullStr = NewConstStr("null");

	// GLK window
	_mainWindow = glk_window_open(nullptr, 0, 0, wintype_TextBuffer);
	glk_set_window(_mainWindow);

	// Check for savegame to load
	_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;

	return true;
}

void Archetype::deinitialize() {
	glk_window_close(_mainWindow);
}

Common::Error Archetype::readSaveData(Common::SeekableReadStream *rs) {
	return load_game_state(rs, Object_List) ? Common::kNoError : Common::kNoGameDataFoundError;
}

Common::Error Archetype::writeGameData(Common::WriteStream *ws) {
	save_game_state(ws, Object_List);

	return Common::kNoError;
}

Common::Error Archetype::loadLauncherSavegame() {
	Common::Error result = loadGameState(_saveSlot);
	_saveSlot = -2;
	return result;
}

void Archetype::interpret() {
	Translating = false;
	bool success = load_game(&_gameFile);
	_gameFile.close();

	if (!success)
		error("Could not load game");

	ContextType context;
	ResultType result;

	undefine(result);

	if (!send_message(OP_SEND, find_message("START"), MainObject, result, context))
		error("Cannot execute; no ''START'' message for main object.");

	cleanup(result);
}

void Archetype::write(const String fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	Common::String s = Common::String::vformat(fmt.c_str(), ap);
	va_end(ap);

	_lastOutputText = s;

	if (!loadingSavegame())
		glk_put_buffer(s.c_str(), s.size());
}

void Archetype::writeln(const String fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	Common::String s = Common::String::vformat(fmt.c_str(), ap);
	va_end(ap);

	s += '\n';
	_lastOutputText = s;

	if (!loadingSavegame())
		glk_put_buffer(s.c_str(), s.size());
}

String Archetype::readLine() {
	// WORKAROUND: THe original archetype games prompt for save file names due to script
	// code before calling the save/load code. It's a bit hacky, but we detect the occurance
	// of save/load in the text just before the readLine call and skip waiting for text
	String text = _lastOutputText;
	text.toLowercase();
	if (text.contains("save") || text.contains("load")) {
		writeln();
		return "";

	} else if (loadingSavegame()) {
		// Automatically trigger a load action if a savegame needs loading from the launcher
		return String("load");

	} else if (_saveSlot == -2) {
		_saveSlot = -1;
		return String("look");
	}

	event_t ev;
	char buffer[MAX_INPUT_LINE + 1];

	glk_request_line_event(_mainWindow, buffer, MAX_INPUT_LINE, 0);

	do {
		glk_select(&ev);
		if (ev.type == evtype_Quit) {
			glk_cancel_line_event(_mainWindow, &ev);
			return "";
		} else if (ev.type == evtype_LineInput)
			break;
	} while (ev.type != evtype_Quit);

	buffer[ev.val1] = 0;

	return String(buffer);
}

char Archetype::readKey() {
	glk_request_char_event(_mainWindow);

	event_t ev;
	while (ev.type != evtype_CharInput) {
		glk_select(&ev);

		if (ev.type == evtype_Quit) {
			glk_cancel_char_event(_mainWindow);
			return '\0';
		}
	}

	return (char)ev.val1;
}

void Archetype::lookup(int the_obj, int the_attr, ResultType &result, ContextType &context,
		DesiredType desired) {
	NodePtr np;
	bool done, first_pass;
	ListType attrs;
	int parent;
	void *p, *original;
	ExprTree e;
	ContextType c;

	cleanup(result);

	if (desired == NAME) {
		result._kind = IDENT;
		result._data._ident.ident_kind = ATTRIBUTE_ID;
		result._data._ident.ident_int = the_attr;
		return;
	}

	if (the_obj == 0)
		// system object - all attributes UNDEFINED
		return;

	if (!index_xarray(Object_List, the_obj, original)) {
		g_vm->writeln("Internal error: cannot reference object %d", the_obj);
		return;
	}

	// Return UNDEFINED for attempting to reference any attribute of a destroyed object
	if (original == nullptr)
		return;

	// It is important to change the context before a lookup so that any non-scalar expressions
	// that are referenced will be evaluated in the light of that object's context
	c = context;
	c.self = the_obj;              // references to self must be right
	c.each = 0;

	first_pass = true;
	p = original;
	done = false;

	// Inheritance loop
	do {
		ObjectType &obj = *((ObjectPtr)p);
		attrs = obj.attributes;
		parent = obj.inherited_from;

		np = find_item(attrs, the_attr);
		if (np != nullptr || parent == 0) {
			done = true;
		} else {
			// Track back
			if (!index_xarray(Type_List, parent, p)) {
				writeln("Internal error:  lookup cannot find parent type %d", parent);
				return;
			}

			first_pass = false;
		}
	} while (!done);

	if (np == nullptr)
		// not found anywhere
		return;

	switch (desired) {
	case RVALUE:
		eval_expr((ExprPtr)np->data, result, c, RVALUE);
		break;

	// Getting an inherited LVALUE is tricky.  We must remember that since we have come
	// this far, we definitely will return an ATTR_PTR result
	case LVALUE:
		if (first_pass) {
			result._kind = ATTR_PTR;
			result._data._attr.acl_attr = np;
		} else {
			// inherited - must create new node
			result._kind = ATTR_PTR;
			result._data._attr.acl_attr = new NodeType();

			e = new ExprNode();
			undefine(*e);
			eval_expr((ExprPtr)np->data, *e, c, RVALUE);

			result._data._attr.acl_attr->data = e;
			result._data._attr.acl_attr->key = the_attr;
			insert_item(((ObjectPtr)original)->attributes, result._data._attr.acl_attr);
		}
		break;

	default:
		break;
	}
}

static int messageCtr = 0;

bool Archetype::send_message(int transport, int message_sent, int recipient,
		ResultType &result, ContextType &context) {
	bool done, find_other;
	ObjectPtr op, original;
	ResultType r;
	NodePtr np;
	StatementPtr st;
	void *p;
	ContextType c;

	if (message_sent == 0) {
		cleanup(result);
		return false;
	}

	if (DebugMan.isDebugChannelEnabled(DEBUG_MSGS)) {
		++messageCtr;
		//debugN(String::format("%d  ", messageCtr).c_str());

		r._kind = IDENT;
		r._data._ident.ident_kind = OBJECT_ID;
		r._data._ident.ident_int = context.self;

		debugN(" : ");
		display_result(r);

		if (transport == OP_SEND)
			debugN(" sending ");
		else
			debugN(" passing ");

		if (index_xarray(Vocabulary, message_sent, p)) {
			String str = String::format("'%s'", ((StringPtr)p)->c_str());
			debugN("%s", str.c_str());
		}

		if (transport == OP_SEND_TO_TYPE)
			r._data._ident.ident_kind = TYPE_ID;

		debugN(" to ");
		r._data._ident.ident_int = recipient;
		display_result(r);
		debug("%s", "");
	}

	// Trying to send a message to a destroyed object results in UNDEFINED

	if ((((transport == OP_SEND_TO_TYPE) && index_xarray(Type_List, recipient, p))
			|| index_xarray(Object_List, recipient, p))
			&& (p != nullptr)) {
		c = context;
		c.each = 0;
		c.message = message_sent;
		if (transport == OP_SEND) {
			c.sender = context.self;
			c.self = recipient;
		}

		op = (ObjectPtr)p;
		original = op;
		done = false;
		find_other = false;
		while (!done) {
			if (find_other) {
				st = op->other;
			} else {
				np = find_item(op->methods, message_sent);
				if (np != nullptr)
					st = (StatementPtr)np->data;
				else
					st = nullptr;
			}

			if (st != nullptr) {
				// found it
				exec_stmt(st, result, c);
				return true;
			} else {
				// no message for recipient
				if (op->inherited_from == 0) {
					if (find_other) {
						done = true;
					} else {
						op = original;
						find_other = true;
					}
				} else if (index_xarray(Type_List, op->inherited_from, p)) {
					op = (ObjectPtr)p;
				} else {
					error("Internal error:  invalid inheritance");
					return false;
				}
			}
		}
	}

	// If we get here, it means that there was not even a "default" handler for
	// the message in the given object or its lineage.  Return ABSENT
	result._kind = RESERVED;
	result._data._reserved.keyword = RW_ABSENT;

	return false;
}

void Archetype::eval_expr(ExprTree the_expr, ResultType &result, ContextType &context, DesiredType desired) {
	ResultType r1, r2;
	int i;
	ExprTree e;

	// It is very important to make sure that the "kind" fields of our temporary result variables
	// are properly set to RESERVED/UNDEFINED before doing anything with them, so that if someone
	// tries to clean them up later on, they won"t try to dispose of a string that isn't there
	undefine(r1);
	undefine(r2);

	cleanup(result);

	if (the_expr == nullptr)
		return;

	// Check:  if this is a lone attribute, look it up in this object"s table
	if (the_expr->_kind == IDENT && the_expr->_data._ident.ident_kind == ATTRIBUTE_ID)
		lookup(context.self, the_expr->_data._ident.ident_int, result, context, desired);

	else if (the_expr->_kind == RESERVED) {
		// it is a special reserved word that requires an action
		switch (the_expr->_data._reserved.keyword) {
		case RW_READ:
		case RW_KEY:
			result._kind = STR_PTR;
			if (the_expr->_data._reserved.keyword == RW_READ)
				result._data._str.acl_str = MakeNewDynStr(readLine());	// read full line
			else {
				String s;
				s += readKey();
				result._data._str.acl_str = MakeNewDynStr(s);	// read single key
			}
			break;

		case RW_MESSAGE:
			result._kind = MESSAGE;
			result._data._msgTextQuote.index = context.message;
			break;

		case RW_EACH:
		case RW_SELF:
		case RW_SENDER:
			result._kind = IDENT;
			result._data._ident.ident_kind = OBJECT_ID;

			switch (the_expr->_data._reserved.keyword) {
			case RW_EACH:
				result._data._ident.ident_int = context.each;
				break;
			case RW_SELF:
				result._data._ident.ident_int = context.self;
				break;
			case RW_SENDER:
				result._data._ident.ident_int = context.sender;
				break;
			default:
				break;
			}
			break;

		default:
			result = *the_expr;
			break;
		}
	} else if (the_expr->_kind == OPER) {
		// It's an operator, need to evaulate it
		switch (the_expr->_data._oper.op_name) {
		case OP_SEND:
		case OP_PASS:
			eval_expr(the_expr->_data._oper.left, r1, context, RVALUE);
			eval_expr(the_expr->_data._oper.right, r2, context, RVALUE);

			if (r2._kind == IDENT && (r2._data._ident.ident_kind == OBJECT_ID || r2._data._ident.ident_kind == TYPE_ID)) {
				// Object 0 is the system object and always receives string messages

				if (r2._data._ident.ident_kind == OBJECT_ID && r2._data._ident.ident_int == 0) {
					if (convert_to(STR_PTR, r1))
						send_to_system(the_expr->_data._oper.op_name, *r1._data._str.acl_str, result, context);

				} else if (convert_to(MESSAGE, r1)) {
					if (r2._data._ident.ident_kind == TYPE_ID)
						send_message(OP_SEND_TO_TYPE, r1._data._msgTextQuote.index, r2._data._ident.ident_int,
							result, context);
					else
						send_message(the_expr->_data._oper.op_name, r1._data._msgTextQuote.index,
							r2._data._ident.ident_int, result, context);
				}
			}
			break;

		case OP_DOT:
			eval_expr(the_expr->_data._oper.left, r1, context, RVALUE);

			if (r1._kind == IDENT && r1._data._ident.ident_kind == OBJECT_ID) {
				eval_expr(the_expr->_data._oper.right, r2, context, NAME);
				if (r2._kind == IDENT && r2._data._ident.ident_kind == ATTRIBUTE_ID)
					lookup(r1._data._ident.ident_int, r2._data._ident.ident_int, result, context, desired);
			}
			break;

		case OP_ASSIGN:
			if (desired == NAME)
				return;

			eval_expr(the_expr->_data._oper.right, result, context, RVALUE);
			eval_expr(the_expr->_data._oper.left, r1, context, LVALUE);

			if (!assignment(r1, result))
				cleanup(result);
			else if (desired == LVALUE) {
				cleanup(result);
				result._kind = ATTR_PTR;
				result._data._attr.acl_attr = r1._data._attr.acl_attr;
			}
			break;

		case OP_C_MULTIPLY:
		case OP_C_DIVIDE:
		case OP_C_PLUS:
		case OP_C_MINUS:
		case OP_C_CONCAT:
			if (desired == NAME)
				return;

			// Do the two operations using a dummy expression node
			e = new ExprNode();
			*e = *the_expr;

			switch (the_expr->_data._oper.op_name) {
			case OP_C_MULTIPLY:
				e->_data._oper.op_name = OP_MULTIPLY;
				break;
			case OP_C_DIVIDE:
				e->_data._oper.op_name = OP_DIVIDE;
				break;
			case OP_C_PLUS:
				e->_data._oper.op_name = OP_PLUS;
				break;
			case OP_C_MINUS:
				e->_data._oper.op_name = OP_MINUS;
				break;
			case OP_C_CONCAT:
				e->_data._oper.op_name = OP_CONCAT;
				break;
			default:
				break;
			}

			eval_expr(e, r1, context, RVALUE);
			e->_data._oper.op_name = OP_ASSIGN;
			e->_data._oper.right = &r1;

			eval_expr(e, result, context, desired);
			delete e;
			break;

		case OP_CHS:
		case OP_NUMERIC:
			eval_expr(the_expr->_data._oper.right, result, context, RVALUE);
			if (!convert_to(NUMERIC, result))
				cleanup(result);
			else if (the_expr->_data._oper.op_name == OP_CHS)
				result._data._numeric.acl_int = -result._data._numeric.acl_int;
			break;

		case OP_STRING:
			eval_expr(the_expr->_data._oper.right, result, context, RVALUE);
			if (!convert_to(STR_PTR, result))
				cleanup(result);
			break;

		case OP_LENGTH:
			eval_expr(the_expr->_data._oper.right, r1, context, RVALUE);
			if (convert_to(STR_PTR, r1)) {
				result._kind = NUMERIC;
				result._data._numeric.acl_int = r1._data._str.acl_str->size();
			}
			break;

			// For the random operator, we must be careful:  ? "01234" should select a random digit
			// out of that set, not attempt to convert it to 1234 and take a random number in the
			// range 1 - 1234. However, we can neither immediately convert it to string, because
			// ? 6 should produce a value in the range 1 - 6, not the character "6"
		case OP_RANDOM:
			eval_expr(the_expr->_data._oper.right, result, context, RVALUE);
			if (result._kind == NUMERIC)
				// convert x < range to 1 <= x <= range
				result._data._numeric.acl_int = g_vm->getRandomNumber(result._data._numeric.acl_int - 1) + 1;
			else if (convert_to(STR_PTR, result)) {
				// Replace the string with a single random character for it
				String &s = *result._data._str.acl_str;
				s = s[g_vm->getRandomNumber(s.size() - 1)];
			}
			break;

		case OP_NOT:
			result._kind = RESERVED;
			if (eval_condition(the_expr->_data._oper.right, context))
				result._data._reserved.keyword = RW_FALSE;
			else
				result._data._reserved.keyword = RW_TRUE;
			break;

		case OP_PLUS:
		case OP_MINUS:
		case OP_MULTIPLY:
		case OP_DIVIDE:
			eval_expr(the_expr->_data._oper.left, r1, context, RVALUE);
			eval_expr(the_expr->_data._oper.right, r2, context, RVALUE);
			if (convert_to(NUMERIC, r1) && convert_to(NUMERIC, r2)) {
				result._kind = NUMERIC;
				switch (the_expr->_data._oper.op_name) {
				case OP_PLUS:
					result._data._numeric.acl_int = r1._data._numeric.acl_int + r2._data._numeric.acl_int;
					break;
				case OP_MINUS:
					result._data._numeric.acl_int = r1._data._numeric.acl_int - r2._data._numeric.acl_int;
					break;
				case OP_MULTIPLY:
					result._data._numeric.acl_int = r1._data._numeric.acl_int * r2._data._numeric.acl_int;
					break;
				case OP_DIVIDE:
					result._data._numeric.acl_int = r1._data._numeric.acl_int / r2._data._numeric.acl_int;
					break;
				default:
					break;
				}
			}
			break;

		case OP_AND:
			result._kind = RESERVED;
			if (eval_condition(the_expr->_data._oper.left, context) && eval_condition(the_expr->_data._oper.right, context))
				result._data._reserved.keyword = RW_TRUE;
			else
				result._data._reserved.keyword = RW_FALSE;
			break;

		case OP_OR:
			if (eval_condition(the_expr->_data._oper.left, context) || eval_condition(the_expr->_data._oper.right, context))
				result._data._reserved.keyword = RW_TRUE;
			else
				result._data._reserved.keyword = RW_FALSE;
			break;

		case OP_POWER:
			eval_expr(the_expr->_data._oper.right, r2, context, RVALUE);
			eval_expr(the_expr->_data._oper.left, r1, context, RVALUE);
			if (convert_to(NUMERIC, r2) && convert_to(NUMERIC, r1)) {
				result._kind = NUMERIC;
				result._data._numeric.acl_int = 1;
				for (i = 1; i <= r2._data._numeric.acl_int; ++i)
					result._data._numeric.acl_int *= r1._data._numeric.acl_int;
			}
			break;

		case OP_CONCAT:
			eval_expr(the_expr->_data._oper.left, r1, context, RVALUE);
			eval_expr(the_expr->_data._oper.right, r2, context, RVALUE);
			if (convert_to(STR_PTR, r1) && convert_to(STR_PTR, r2)) {
				result._kind = STR_PTR;
				result._data._str.acl_str = MakeNewDynStr(*r1._data._str.acl_str + *r2._data._str.acl_str);
			}
			break;

		case OP_LEFTFROM:
		case OP_RIGHTFROM:
			eval_expr(the_expr->_data._oper.left, r1, context, RVALUE);
			eval_expr(the_expr->_data._oper.right, r2, context, RVALUE);
			if (convert_to(STR_PTR, r1) && convert_to(NUMERIC, r2)) {
				result._kind = STR_PTR;
				if (the_expr->_data._oper.op_name == OP_LEFTFROM)
					result._data._str.acl_str = MakeNewDynStr(r1._data._str.acl_str->left(r2._data._numeric.acl_int));
				else
					result._data._str.acl_str = MakeNewDynStr(r1._data._str.acl_str->right(
						r1._data._str.acl_str->size() - r2._data._numeric.acl_int + 1));
			}
			break;

		case OP_WITHIN:
			eval_expr(the_expr->_data._oper.left, r1, context, RVALUE);
			eval_expr(the_expr->_data._oper.right, r2, context, RVALUE);
			if (convert_to(STR_PTR, r1) && convert_to(STR_PTR, r2)) {
				result._kind = NUMERIC;
				result._data._numeric.acl_int = r2._data._str.acl_str->indexOf(*r1._data._str.acl_str) + 1;
				if (result._data._numeric.acl_int == 0)
					cleanup(result);
			}
			break;

		case OP_EQ:
		case OP_NE:
		case OP_LT:
		case OP_GT:
		case OP_LE:
		case OP_GE:
			eval_expr(the_expr->_data._oper.left, r1, context, RVALUE);
			eval_expr(the_expr->_data._oper.right, r2, context, RVALUE);

			result._kind = RESERVED;
			if (result_compare(the_expr->_data._oper.op_name, r1, r2))
				result._data._reserved.keyword = RW_TRUE;
			else
				result._data._reserved.keyword = RW_FALSE;
			break;

		default:
			g_vm->writeln("Internal error: \"%s\" not yet supported", Operators[the_expr->_data._oper.op_name]);
			break;
		}

		cleanup(r1);
		cleanup(r2);

		if (DebugMan.isDebugChannelEnabled(DEBUG_EXPR)) {
			debugN(" -- ");
			display_expr(the_expr);
			debugN("  ==>  ");
			display_result(result);
			debug("%s", "");
		}
	} else {
		switch (desired) {
		case RVALUE:
			copy_result(result, *the_expr);
			break;
		case LVALUE:
			result = *the_expr;
			break;
		default:
			break;
		}
	}
}

bool Archetype::eval_condition(ExprTree the_expr, ContextType &context) {
	ResultType result;
	bool failure;

	undefine(result);
	eval_expr(the_expr, result, context, RVALUE);

	failure = (result._kind == RESERVED) && (
		result._data._reserved.keyword == RW_UNDEFINED ||
		result._data._reserved.keyword == RW_FALSE ||
		result._data._reserved.keyword == RW_ABSENT
	);

	cleanup(result);
	return !failure;
}

void Archetype::exec_stmt(StatementPtr the_stmt, ResultType &result, ContextType &context) {
	NodePtr np;
	void *p, *q;
	ResultType r1, r2;
	CasePairPtr this_case;
	bool b;
	ContextType c;
	int i;
	ObjectPtr the_object;
	bool verbose;

	undefine(r1);
	undefine(r2);
	cleanup(result);

	verbose = DebugMan.isDebugChannelEnabled(DEBUG_STMT);

	if (verbose)
		debugN(" == ");

	switch (the_stmt->_kind) {
	case COMPOUND:
		np = nullptr;
		b = false;
		while (!b && iterate_list(the_stmt->_data._compound.statements, np)) {
			cleanup(result);
			exec_stmt((StatementPtr)np->data, result, context);

			b = (result._kind == RESERVED) && (result._data._reserved.keyword == RW_BREAK);

			if (shouldQuit())
				return;
		}
		break;

	case ST_EXPR:
		if (verbose)
			display_expr(the_stmt->_data._expr.expression);

		switch (the_stmt->_data._expr.expression->_kind) {
		case QUOTE_LIT:
			if (index_xarray(Literals, the_stmt->_data._expr.expression->_data._msgTextQuote.index, p)) {
				result._kind = TEXT_LIT;
				result._data._msgTextQuote.index = the_stmt->_data._expr.expression->_data._msgTextQuote.index;
				writeln(*((StringPtr)p));
			}
			break;

		case MESSAGE:
			b = send_message(OP_PASS, the_stmt->_data._expr.expression->_data._msgTextQuote.index,
				context.self, result, context);
			break;

		default:
			eval_expr(the_stmt->_data._expr.expression, result, context, RVALUE);
			break;
		}
		break;

	case ST_WRITE:
	case ST_WRITES:
	case ST_STOP:
		if (verbose) {
			switch (the_stmt->_kind) {
			case ST_WRITE:
				debugN("write ");
				break;
			case ST_WRITES:
				debugN("writes ");
				break;
			case ST_STOP:
				debugN("stop ");
				break;
			default:
				break;
			}

			debugN(" ");
			np = nullptr;
			while (iterate_list(the_stmt->_data._write.print_list, np)) {
				display_expr((ExprTree)np->data);
				if (np->next != the_stmt->_data._write.print_list)
					debugN(", ");
			}

			debug("%s", "");
		}

		np = nullptr;
		while (iterate_list(the_stmt->_data._write.print_list, np)) {
			cleanup(result);
			eval_expr((ExprTree)np->data, result, context, RVALUE);
			String line = get_result_string(result);
			g_vm->write("%s", line.c_str());
		}

		if (the_stmt->_kind == ST_WRITE) {
			g_vm->writeln();
		} else if (the_stmt->_kind == ST_STOP) {
			// End the game
			g_vm->writeln();
			g_vm->quitGame();
		}
		break;

	case ST_IF:
		if (verbose) {
			debugN("if: Testing ");
			display_expr(the_stmt->_data._if.condition);
		}
		if (eval_condition(the_stmt->_data._if.condition, context)) {
			if (verbose)
				debug(" Evaluated TRUE; executing then branch");
			exec_stmt(the_stmt->_data._if.then_branch, result, context);

		} else if (the_stmt->_data._if.else_branch != nullptr) {
			if (verbose)
				debug(" Evaluated FALSE; executing else branch");
			exec_stmt(the_stmt->_data._if.else_branch, result, context);
		}
		break;

	case ST_CASE:
		if (verbose) {
			debugN("case ");
			display_expr(the_stmt->_data._case.test_expr);
			debug(" of");
		}

		eval_expr(the_stmt->_data._case.test_expr, r1, context, RVALUE);
		np = nullptr;

		while (iterate_list(the_stmt->_data._case.cases, np)) {
			this_case = (CasePairPtr)np->data;

			//with this_case^ do begin
			eval_expr(this_case->value, r2, context, RVALUE);
			if ((r2._kind == RESERVED && r2._data._reserved.keyword == RW_DEFAULT)
				|| result_compare(OP_EQ, r1, r2)) {
				exec_stmt(this_case->action, result, context);
				cleanup(r1);
				cleanup(r2);
				return;
			}

			cleanup(r2);
		}

		cleanup(result);
		cleanup(r1);
		break;

	case ST_BREAK:
		result._kind = RESERVED;
		result._data._reserved.keyword = RW_BREAK;
		break;

	case ST_FOR:
		b = false;
		c = context;
		c.each = 1;

		while (!b && c.each <= (int)Object_List.size()) {
			if (eval_condition(the_stmt->_data._loop.selection, c)) {
				exec_stmt(the_stmt->_data._loop.action, result, c);
				b = (result._kind == RESERVED) && (result._data._reserved.keyword == RW_BREAK);
				cleanup(result);
			}

			++c.each;
		}
		break;

	case ST_WHILE:
		b = false;
		while (!b && eval_condition(the_stmt->_data._loop.selection, context)) {
			exec_stmt(the_stmt->_data._loop.action, result, context);
			b = (result._kind == RESERVED) && (result._data._reserved.keyword == RW_BREAK);
			cleanup(result);

			if (shouldQuit())
				return;
		}
		break;

	case ST_CREATE:
		eval_expr(the_stmt->_data._create.new_name, r1, context, LVALUE);

		// Attempt a dummy assignment just to see if it works
		result._kind = IDENT;
		result._data._ident.ident_kind = OBJECT_ID;
		result._data._ident.ident_int = 0;

		if (!assignment(r1, result)) {
			cleanup(result);
		} else {
			// do it for real
			the_object = new ObjectType();
			the_object->inherited_from = the_stmt->_data._create.archetype;
			new_list(the_object->attributes);
			new_list(the_object->methods);
			the_object->other = nullptr;
			p = the_object;

			// NOTE:  Search the list for an empty slot; if none found, append
			i = Dynamic;
			b = true;
			while (access_xarray(Object_List, i, q, PEEK_ACCESS) && q != nullptr)
				++i;

			if (i > (int)Object_List.size())
				append_to_xarray(Object_List, p);
			else
				b = access_xarray(Object_List, i, p, POKE_ACCESS);

			// Now we know its number; go back and update the result"s object reference.
			// "Return" this same value
			((ExprPtr)r1._data._attr.acl_attr->data)->_data._ident.ident_int = i;
			copy_result(result, *(ExprPtr)r1._data._attr.acl_attr->data);

			cleanup(r1);
		}
		break;

		// Just dispose of the indicated object in the Object_List.  Shrink the list only if
		// the very last object was destroyed
	case ST_DESTROY:
		eval_expr(the_stmt->_data._destroy.victim, result, context, RVALUE);
		if (result._kind == IDENT && result._data._ident.ident_kind == OBJECT_ID
				&& index_xarray(Object_List, result._data._ident.ident_int, p)) {
			the_object = (ObjectPtr)p;
			dispose_object(the_object);
			p = nullptr;
			b = access_xarray(Object_List, result._data._ident.ident_int, p, POKE_ACCESS);
			if (result._data._ident.ident_int == (int)Object_List.size())
				shrink_xarray(Object_List);
		} else {
			error("Can only destroy previously created objects");
		}

		cleanup(result);
		break;

	default:
		error("Internal error:  statement not supported yet");
		break;
	}

	if (verbose)
		debug("%s", "");		// finish off dangling lines
}

} // End of namespace Archetype
} // End of namespace Glk
