/* 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/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/scgamest.h"
#include "glk/glk.h"
#include "glk/events.h"

namespace Glk {
namespace Adrift {

/*
 * Module notes:
 *
 * o Gender enumerations are 0/1/2, but 1/2/3 in jAsea.  The 0/1/2 values
 *   seem to be right.  Is jAsea off by one?
 *
 * o jAsea tries to read Globals.CompileDate.  It's just CompileDate.
 *
 * o State_ and obstate are implemented, but not fully tested due to a lack
 *   of games that use them.
 */

/* Assorted definitions and constants. */
static const sc_uint VARS_MAGIC = 0xabcc7a71;
static const sc_char NUL = '\0';

/* Variables trace flag. */
static sc_bool var_trace = FALSE;

/* Table of numbers zero to twenty spelled out. */
enum { VAR_NUMBERS_SIZE = 21 };
static const sc_char *const VAR_NUMBERS[VAR_NUMBERS_SIZE] = {
	"zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
	"nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
	"sixteen", "seventeen", "eighteen", "nineteen", "twenty"
};

/* Variable entry, held on a list hashed by variable name. */
struct sc_var_s {
	struct sc_var_s *next;

	const sc_char *name;
	sc_int type;
	sc_vartype_t value;
};
typedef sc_var_s sc_var_t;
typedef sc_var_t *sc_varref_t;

/*
 * Variables set structure.  A self-contained set of variables on which
 * variables functions operate.  211 is prime, making it a reasonable hash
 * divisor.  There's no rehashing here; few games, if any, are likely to
 * exceed a fill factor of two (~422 variables).
 */
enum { VAR_HASH_TABLE_SIZE = 211 };
struct sc_var_set_s {
	sc_uint magic;
	sc_prop_setref_t bundle;
	sc_int referenced_character;
	sc_int referenced_object;
	sc_int referenced_number;
	sc_bool is_number_referenced;
	sc_char *referenced_text;
	sc_char *temporary;
	uint32 timestamp;
	sc_uint time_offset;
	sc_gameref_t game;
	sc_varref_t variable[VAR_HASH_TABLE_SIZE];
};
typedef sc_var_set_s sc_var_set_t;


/*
 * var_is_valid()
 *
 * Return TRUE if pointer is a valid variables set, FALSE otherwise.
 */
static sc_bool var_is_valid(sc_var_setref_t vars) {
	return vars && vars->magic == VARS_MAGIC;
}


/*
 * var_hash_name()
 *
 * Hash a variable name, modulo'ed to the number of buckets.
 */
static sc_uint var_hash_name(const sc_char *name) {
	return sc_hash(name) % VAR_HASH_TABLE_SIZE;
}


/*
 * var_create_empty()
 *
 * Create and return a new empty set of variables.
 */
static sc_var_setref_t var_create_empty(void) {
	sc_var_setref_t vars;
	sc_int index_;

	/* Create a clean set of variables. */
	vars = (sc_var_setref_t)sc_malloc(sizeof(*vars));
	vars->magic = VARS_MAGIC;
	vars->bundle = nullptr;
	vars->referenced_character = -1;
	vars->referenced_object = -1;
	vars->referenced_number = 0;
	vars->is_number_referenced = FALSE;
	vars->referenced_text = nullptr;
	vars->temporary = nullptr;
	vars->timestamp = g_vm->_events->getTotalPlayTicks() / 1000;
	vars->time_offset = 0;
	vars->game = nullptr;

	/* Clear all variable hash lists. */
	for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++)
		vars->variable[index_] = nullptr;

	return vars;
}


/*
 * var_destroy()
 *
 * Destroy a variable set, and free its heap memory.
 */
void var_destroy(sc_var_setref_t vars) {
	sc_int index_;
	assert(var_is_valid(vars));

	/*
	 * Free the content of each string variable, and variable entry.  String
	 * variable content needs to use mutable string instead of const string.
	 */
	for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++) {
		sc_varref_t var, next;

		for (var = vars->variable[index_]; var; var = next) {
			next = var->next;
			if (var->type == VAR_STRING)
				sc_free(var->value.mutable_string);
			sc_free(var);
		}
	}

	/* Free any temporary and reference text storage area. */
	sc_free(vars->temporary);
	sc_free(vars->referenced_text);

	/* Poison and free the variable set itself. */
	memset(vars, 0xaa, sizeof(*vars));
	sc_free(vars);
}


/*
 * var_find()
 * var_add()
 *
 * Find and return a pointer to a named variable structure, or nullptr if no such
 * variable exists, and add a new variable structure to the lists.
 */
static sc_varref_t var_find(sc_var_setref_t vars, const sc_char *name) {
	sc_uint hash;
	sc_varref_t var;

	/* Hash name, search list and return if name match found. */
	hash = var_hash_name(name);
	for (var = vars->variable[hash]; var; var = var->next) {
		if (strcmp(name, var->name) == 0)
			break;
	}

	/* Return variable, or nullptr if no such variable. */
	return var;
}

static sc_varref_t var_add(sc_var_setref_t vars, const sc_char *name, sc_int type) {
	sc_varref_t var;
	sc_uint hash;

	/* Create a new variable entry. */
	var = (sc_varref_t)sc_malloc(sizeof(*var));
	var->name = name;
	var->type = type;
	var->value.voidp = nullptr;

	/* Hash its name, and insert it at start of the relevant list. */
	hash = var_hash_name(name);
	var->next = vars->variable[hash];
	vars->variable[hash] = var;

	return var;
}


/*
 * var_get_scare_version()
 *
 * Return the value of %scare_version%.  Used to generate the system version
 * of this variable, and to re-initialize user versions initialized to zero.
 */
static sc_int var_get_scare_version(void) {
	sc_int major, minor, point, version;

	if (sscanf(SCARE_VERSION, "%ld.%ld.%ld", &major, &minor, &point) != 3) {
		sc_error("var_get_scare_version: unable to generate scare_version\n");
		return 0;
	}

	version = major * 10000 + minor * 100 + point;
	return version;
}


/*
 * var_put()
 *
 * Store a variable type in a named variable.  If not present, the variable
 * is created.  Type is one of 'I' or 'S' for integer or string.
 */
void var_put(sc_var_setref_t vars, const sc_char *name, sc_int type, sc_vartype_t vt_value) {
	sc_varref_t var;
	sc_bool is_modification;
	assert(var_is_valid(vars));
	assert(name);

	/* Check type is either integer or string. */
	switch (type) {
	case VAR_INTEGER:
	case VAR_STRING:
		break;

	default:
		sc_fatal("var_put: invalid variable type, %ld\n", type);
	}

	/* See if the user variable already exists. */
	var = var_find(vars, name);
	if (var) {
		/* Verify that nothing is trying to change the variable's type. */
		if (var->type != type)
			sc_fatal("var_put: variable type changed, %s\n", name);

		/*
		 * Special case %scare_version%.  If a game changes its value, it may
		 * compromise version checking, so warn here, but continue.
		 */
		if (strcmp(name, "scare_version") == 0) {
			if (var->value.integer != vt_value.integer)
				sc_error("var_put: warning: %%%s%% value changed\n", name);
		}

		is_modification = TRUE;
	} else {
		/*
		 * Special case %scare_version%.  If a game defines this and initializes
		 * it to zero, re-initialize it to SCARE's version number.  Games that
		 * define %scare_version%, initially zero, can use this to test if
		 * running under SCARE or Runner.
		 */
		if (strcmp(name, "scare_version") == 0 && vt_value.integer == 0) {
			vt_value.integer = var_get_scare_version();

			if (var_trace)
				sc_trace("Variable: %%%s%% [new] caught and mapped\n", name);
		}

		/*
		 * Create a new and empty variable entry.  The mutable string needs to
		 * be set to nullptr here so that realloc works correctly on assigning
		 * the value below.
		 */
		var = var_add(vars, name, type);
		var->value.mutable_string = nullptr;

		is_modification = FALSE;
	}

	/* Update the existing variable, or populate the new one fully. */
	switch (var->type) {
	case VAR_INTEGER:
		var->value.integer = vt_value.integer;
		break;

	case VAR_STRING:
		/* Use mutable string instead of const string. */
		var->value.mutable_string = (sc_char *)sc_realloc(var->value.mutable_string,
		                            strlen(vt_value.string) + 1);
		strcpy(var->value.mutable_string, vt_value.string);
		break;

	default:
		sc_fatal("var_put: invalid variable type, %ld\n", var->type);
	}

	if (var_trace) {
		sc_trace("Variable: %%%s%%%s = ",
		         name, is_modification ? "" : " [new]");
		switch (var->type) {
		case VAR_INTEGER:
			sc_trace("%ld", var->value.integer);
			break;
		case VAR_STRING:
			sc_trace("\"%s\"", var->value.string);
			break;

		default:
			sc_trace("[invalid variable type, %ld]", var->type);
			break;
		}
		sc_trace("\n");
	}
}


/*
 * var_append_temp()
 *
 * Helper for object listers.  Extends temporary, and appends the given text
 * to the string.
 */
static void var_append_temp(sc_var_setref_t vars, const sc_char *string) {
	sc_bool new_sentence;
	sc_int noted;

	if (!vars->temporary) {
		/* Create a new temporary area and copy string. */
		new_sentence = TRUE;
		noted = 0;
		vars->temporary = (sc_char *)sc_malloc(strlen(string) + 1);
		strcpy(vars->temporary, string);
	} else {
		/* Append string to existing temporary. */
		new_sentence = (vars->temporary[0] == NUL);
		noted = strlen(vars->temporary);
		vars->temporary = (sc_char *)sc_realloc(vars->temporary,
		                                        strlen(vars->temporary) +
		                                        strlen(string) + 1);
		strcat(vars->temporary, string);
	}

	if (new_sentence)
		vars->temporary[noted] = sc_toupper(vars->temporary[noted]);
}


/*
 * var_print_object_np
 * var_print_object
 *
 * Convenience functions to append an object's name, with and without any
 * prefix, to variables temporary.
 */
static void var_print_object_np(sc_gameref_t game, sc_int object) {
	const sc_var_setref_t vars = gs_get_vars(game);
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key[3];
	const sc_char *prefix, *normalized, *name;

	/* Get the object's prefix. */
	vt_key[0].string = "Objects";
	vt_key[1].integer = object;
	vt_key[2].string = "Prefix";
	prefix = prop_get_string(bundle, "S<-sis", vt_key);

	/*
	 * Try the same shenanigans as done by the equivalent function in the
	 * library.
	 */
	normalized = prefix;
	if (sc_compare_word(prefix, "a", 1)) {
		normalized = prefix + 1;
		var_append_temp(vars, "the");
	} else if (sc_compare_word(prefix, "an", 2)) {
		normalized = prefix + 2;
		var_append_temp(vars, "the");
	} else if (sc_compare_word(prefix, "the", 3)) {
		normalized = prefix + 3;
		var_append_temp(vars, "the");
	} else if (sc_compare_word(prefix, "some", 4)) {
		normalized = prefix + 4;
		var_append_temp(vars, "the");
	} else if (sc_strempty(prefix))
		var_append_temp(vars, "the ");

	/* As with the library, handle the remaining prefix. */
	if (!sc_strempty(normalized)) {
		var_append_temp(vars, normalized);
		var_append_temp(vars, " ");
	} else if (normalized > prefix)
		var_append_temp(vars, " ");

	/*
	 * Print the object's name, again, as with the library, stripping any
	 * leading article
	 */
	vt_key[2].string = "Short";
	name = prop_get_string(bundle, "S<-sis", vt_key);
	if (sc_compare_word(name, "a", 1))
		name += 1;
	else if (sc_compare_word(name, "an", 2))
		name += 2;
	else if (sc_compare_word(name, "the", 3))
		name += 3;
	else if (sc_compare_word(name, "some", 4))
		name += 4;
	var_append_temp(vars, name);
}

static void var_print_object(sc_gameref_t game, sc_int object) {
	const sc_var_setref_t vars = gs_get_vars(game);
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key[3];
	const sc_char *prefix, *name;

	/*
	 * Get the object's prefix.  As with the library, if the prefix is empty,
	 * put in an "a ".
	 */
	vt_key[0].string = "Objects";
	vt_key[1].integer = object;
	vt_key[2].string = "Prefix";
	prefix = prop_get_string(bundle, "S<-sis", vt_key);
	if (!sc_strempty(prefix)) {
		var_append_temp(vars, prefix);
		var_append_temp(vars, " ");
	} else
		var_append_temp(vars, "a ");

	/* Print the object's name. */
	vt_key[2].string = "Short";
	name = prop_get_string(bundle, "S<-sis", vt_key);
	var_append_temp(vars, name);
}


/*
 * var_select_plurality()
 *
 * Convenience function for listers.  Selects one of two responses depending
 * on whether an object appears singular or plural.
 */
static const sc_char *var_select_plurality(sc_gameref_t game, sc_int object,
		const sc_char *singular, const sc_char *plural) {
	return obj_appears_plural(game, object) ? plural : singular;
}


/*
 * var_list_in_object()
 *
 * List the objects in a given container object.
 */
static void var_list_in_object(sc_gameref_t game, sc_int container) {
	const sc_var_setref_t vars = gs_get_vars(game);
	sc_int object, count, trail;

	/* List out the objects contained in this object. */
	count = 0;
	trail = -1;
	for (object = 0; object < gs_object_count(game); object++) {
		/* Contained? */
		if (gs_object_position(game, object) == OBJ_IN_OBJECT
		        && gs_object_parent(game, object) == container) {
			if (count > 0) {
				if (count > 1)
					var_append_temp(vars, ", ");

				/* Print out the current list object. */
				var_print_object(game, trail);
			}
			trail = object;
			count++;
		}
	}
	if (count >= 1) {
		/* Print out final listed object. */
		if (count == 1) {
			var_print_object(game, trail);
			var_append_temp(vars,
			                var_select_plurality(game, trail,
			                                     " is inside ",
			                                     " are inside "));
		} else {
			var_append_temp(vars, " and ");
			var_print_object(game, trail);
			var_append_temp(vars, " are inside ");
		}

		/* Print out the container. */
		var_print_object_np(game, container);
		var_append_temp(vars, ".");
	}
}


/*
 * var_list_on_object()
 *
 * List the objects on a given surface object.
 */
static void var_list_on_object(sc_gameref_t game, sc_int supporter) {
	const sc_var_setref_t vars = gs_get_vars(game);
	sc_int object, count, trail;

	/* List out the objects standing on this object. */
	count = 0;
	trail = -1;
	for (object = 0; object < gs_object_count(game); object++) {
		/* Standing on? */
		if (gs_object_position(game, object) == OBJ_ON_OBJECT
		        && gs_object_parent(game, object) == supporter) {
			if (count > 0) {
				if (count > 1)
					var_append_temp(vars, ", ");

				/* Print out the current list object. */
				var_print_object(game, trail);
			}
			trail = object;
			count++;
		}
	}
	if (count >= 1) {
		/* Print out final listed object. */
		if (count == 1) {
			var_print_object(game, trail);
			var_append_temp(vars,
			                var_select_plurality(game, trail,
			                                     " is on ", " are on "));
		} else {
			var_append_temp(vars, " and ");
			var_print_object(game, trail);
			var_append_temp(vars, " are on ");
		}

		/* Print out the surface. */
		var_print_object_np(game, supporter);
		var_append_temp(vars, ".");
	}
}


/*
 * var_list_onin_object()
 *
 * List the objects on and in a given associate object.
 */
static void var_list_onin_object(sc_gameref_t game, sc_int associate) {
	const sc_var_setref_t vars = gs_get_vars(game);
	sc_int object, count, trail;
	sc_bool supporting;

	/* List out the objects standing on this object. */
	count = 0;
	trail = -1;
	supporting = FALSE;
	for (object = 0; object < gs_object_count(game); object++) {
		/* Standing on? */
		if (gs_object_position(game, object) == OBJ_ON_OBJECT
		        && gs_object_parent(game, object) == associate) {
			if (count > 0) {
				if (count > 1)
					var_append_temp(vars, ", ");

				/* Print out the current list object. */
				var_print_object(game, trail);
			}
			trail = object;
			count++;
		}
	}
	if (count >= 1) {
		/* Print out final listed object. */
		if (count == 1) {
			var_print_object(game, trail);
			var_append_temp(vars,
			                var_select_plurality(game, trail,
			                                     " is on ", " are on "));
		} else {
			var_append_temp(vars, " and ");
			var_print_object(game, trail);
			var_append_temp(vars, " are on ");
		}

		/* Print out the surface. */
		var_print_object_np(game, associate);
		supporting = TRUE;
	}

	/* List out the objects contained in this object. */
	count = 0;
	trail = -1;
	for (object = 0; object < gs_object_count(game); object++) {
		/* Contained? */
		if (gs_object_position(game, object) == OBJ_IN_OBJECT
		        && gs_object_parent(game, object) == associate) {
			if (count > 0) {
				if (count == 1) {
					if (supporting)
						var_append_temp(vars, ", and ");
				} else
					var_append_temp(vars, ", ");

				/* Print out the current list object. */
				var_print_object(game, trail);
			}
			trail = object;
			count++;
		}
	}
	if (count >= 1) {
		/* Print out final listed object. */
		if (count == 1) {
			if (supporting)
				var_append_temp(vars, ", and ");
			var_print_object(game, trail);
			var_append_temp(vars,
			                var_select_plurality(game, trail,
			                                     " is inside ",
			                                     " are inside "));
		} else {
			var_append_temp(vars, " and ");
			var_print_object(game, trail);
			var_append_temp(vars, " are inside");
		}

		/* Print out the container. */
		if (!supporting) {
			var_append_temp(vars, " ");
			var_print_object_np(game, associate);
		}
		var_append_temp(vars, ".");
	} else {
		if (supporting)
			var_append_temp(vars, ".");
	}
}


/*
 * var_return_integer()
 * var_return_string()
 *
 * Convenience helpers for var_get_system().  Provide convenience and some
 * mild syntactic sugar for making returning a value as a system variable
 * a bit easier.  Set appropriate values for return type and the relevant
 * return value field, and always return TRUE.  A macro was tempting here...
 */
static sc_bool var_return_integer(sc_int value, sc_int *type, sc_vartype_t *vt_rvalue) {
	*type = VAR_INTEGER;
	vt_rvalue->integer = value;
	return TRUE;
}

static sc_bool var_return_string(const sc_char *value, sc_int *type, sc_vartype_t *vt_rvalue) {
	*type = VAR_STRING;
	vt_rvalue->string = value;
	return TRUE;
}


/*
 * var_get_system()
 *
 * Construct a system variable, and return its type and value, or FALSE
 * if invalid name passed in.  Uses var_return_*() to reduce code untidiness.
 */
static sc_bool var_get_system(sc_var_setref_t vars, const sc_char *name,
		sc_int *type, sc_vartype_t *vt_rvalue) {
	const sc_prop_setref_t bundle = vars->bundle;
	const sc_gameref_t game = vars->game;

	/* Check name for known system variables. */
	if (strcmp(name, "author") == 0) {
		sc_vartype_t vt_key[2];
		const sc_char *author;

		/* Get and return the global gameauthor string. */
		vt_key[0].string = "Globals";
		vt_key[1].string = "GameAuthor";
		author = prop_get_string(bundle, "S<-ss", vt_key);
		if (sc_strempty(author))
			author = "[Author unknown]";

		return var_return_string(author, type, vt_rvalue);
	}

	else if (strcmp(name, "character") == 0) {
		/* See if there is a referenced character. */
		if (vars->referenced_character != -1) {
			sc_vartype_t vt_key[3];
			const sc_char *npc_name;

			/* Return the character name string. */
			vt_key[0].string = "NPCs";
			vt_key[1].integer = vars->referenced_character;
			vt_key[2].string = "Name";
			npc_name = prop_get_string(bundle, "S<-sis", vt_key);
			if (sc_strempty(npc_name))
				npc_name = "[Character unknown]";

			return var_return_string(npc_name, type, vt_rvalue);
		} else {
			sc_error("var_get_system: no referenced character yet\n");
			return var_return_string("[Character unknown]", type, vt_rvalue);
		}
	}

	else if (strcmp(name, "heshe") == 0 || strcmp(name, "himher") == 0) {
		/* See if there is a referenced character. */
		if (vars->referenced_character != -1) {
			sc_vartype_t vt_key[3];
			sc_int gender;
			const sc_char *retval;

			/* Return the appropriate character gender string. */
			vt_key[0].string = "NPCs";
			vt_key[1].integer = vars->referenced_character;
			vt_key[2].string = "Gender";
			gender = prop_get_integer(bundle, "I<-sis", vt_key);
			switch (gender) {
			case NPC_MALE:
				retval = (strcmp(name, "heshe") == 0) ? "he" : "him";
				break;
			case NPC_FEMALE:
				retval = (strcmp(name, "heshe") == 0) ? "she" : "her";
				break;
			case NPC_NEUTER:
				retval = "it";
				break;

			default:
				sc_error("var_get_system: unknown gender, %ld\n", gender);
				retval = "[Gender unknown]";
				break;
			}
			return var_return_string(retval, type, vt_rvalue);
		} else {
			sc_error("var_get_system: no referenced character yet\n");
			return var_return_string("[Gender unknown]", type, vt_rvalue);
		}
	}

	else if (strncmp(name, "in_", 3) == 0) {
		sc_int saved_ref_object = vars->referenced_object;

		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for in_\n");
			return var_return_string("[In_ unavailable]", type, vt_rvalue);
		}
		if (!uip_match("%object%", name + 3, game)) {
			sc_error("var_get_system: invalid object for in_\n");
			return var_return_string("[In_ unavailable]", type, vt_rvalue);
		}

		/* Clear any current temporary for appends. */
		vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
		strcpy(vars->temporary, "");

		/* Write what's in the object into temporary. */
		var_list_in_object(game, vars->referenced_object);

		/* Restore saved referenced object and return. */
		vars->referenced_object = saved_ref_object;
		return var_return_string(vars->temporary, type, vt_rvalue);
	}

	else if (strcmp(name, "maxscore") == 0) {
		sc_vartype_t vt_key[2];
		sc_int maxscore;

		/* Return the maximum score. */
		vt_key[0].string = "Globals";
		vt_key[1].string = "MaxScore";
		maxscore = prop_get_integer(bundle, "I<-ss", vt_key);

		return var_return_integer(maxscore, type, vt_rvalue);
	}

	else if (strcmp(name, "modified") == 0) {
		sc_vartype_t vt_key;
		const sc_char *compiledate;

		/* Return the game compilation date. */
		vt_key.string = "CompileDate";
		compiledate = prop_get_string(bundle, "S<-s", &vt_key);
		if (sc_strempty(compiledate))
			compiledate = "[Modified unknown]";

		return var_return_string(compiledate, type, vt_rvalue);
	}

	else if (strcmp(name, "number") == 0) {
		/* Return the referenced number, or 0 if none yet. */
		if (!vars->is_number_referenced)
			sc_error("var_get_system: no referenced number yet\n");

		return var_return_integer(vars->referenced_number, type, vt_rvalue);
	}

	else if (strcmp(name, "object") == 0) {
		/* See if we have a referenced object yet. */
		if (vars->referenced_object != -1) {
			/* Return object name with its prefix. */
			sc_vartype_t vt_key[3];
			const sc_char *prefix, *objname;

			vt_key[0].string = "Objects";
			vt_key[1].integer = vars->referenced_object;
			vt_key[2].string = "Prefix";
			prefix = prop_get_string(bundle, "S<-sis", vt_key);

			vars->temporary = (sc_char *)sc_realloc(vars->temporary, strlen(prefix) + 1);
			strcpy(vars->temporary, prefix);

			vt_key[2].string = "Short";
			objname = prop_get_string(bundle, "S<-sis", vt_key);

			vars->temporary = (sc_char *)sc_realloc(vars->temporary,
			                                        strlen(vars->temporary)
			                                        + strlen(objname) + 2);
			strcat(vars->temporary, " ");
			strcat(vars->temporary, objname);

			return var_return_string(vars->temporary, type, vt_rvalue);
		} else {
			sc_error("var_get_system: no referenced object yet\n");
			return var_return_string("[Object unknown]", type, vt_rvalue);
		}
	}

	else if (strcmp(name, "obstate") == 0) {
		sc_vartype_t vt_key[3];
		sc_bool is_statussed;
		sc_char *state;

		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for obstate\n");
			return var_return_string("[Obstate unavailable]", type, vt_rvalue);
		}
		if (vars->referenced_object == -1) {
			sc_error("var_get_system: no object for obstate\n");
			return var_return_string("[Obstate unavailable]", type, vt_rvalue);
		}

		/*
		 * If not a stateful object, Runner 4.0.45 crashes; we'll do something
		 * different here.
		 */
		vt_key[0].string = "Objects";
		vt_key[1].integer = vars->referenced_object;
		vt_key[2].string = "CurrentState";
		is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
		if (!is_statussed)
			return var_return_string("stateless", type, vt_rvalue);

		/* Get state, and copy to temporary. */
		state = obj_state_name(game, vars->referenced_object);
		if (!state) {
			sc_error("var_get_system: invalid state for obstate\n");
			return var_return_string("[Obstate unknown]", type, vt_rvalue);
		}
		vars->temporary = (sc_char *)sc_realloc(vars->temporary, strlen(state) + 1);
		strcpy(vars->temporary, state);
		sc_free(state);

		/* Return temporary. */
		return var_return_string(vars->temporary, type, vt_rvalue);
	}

	else if (strcmp(name, "obstatus") == 0) {
		sc_vartype_t vt_key[3];
		sc_bool is_openable;
		sc_int openness;
		const sc_char *retval;

		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for obstatus\n");
			return var_return_string("[Obstatus unavailable]", type, vt_rvalue);
		}
		if (vars->referenced_object == -1) {
			sc_error("var_get_system: no object for obstatus\n");
			return var_return_string("[Obstatus unavailable]", type, vt_rvalue);
		}

		/* If not an openable object, return unopenable to match Adrift. */
		vt_key[0].string = "Objects";
		vt_key[1].integer = vars->referenced_object;
		vt_key[2].string = "Openable";
		is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
		if (!is_openable)
			return var_return_string("unopenable", type, vt_rvalue);

		/* Return one of open, closed, or locked. */
		openness = gs_object_openness(game, vars->referenced_object);
		switch (openness) {
		case OBJ_OPEN:
			retval = "open";
			break;
		case OBJ_CLOSED:
			retval = "closed";
			break;
		case OBJ_LOCKED:
			retval = "locked";
			break;
		default:
			retval = "[Obstatus unknown]";
			break;
		}
		return var_return_string(retval, type, vt_rvalue);
	}

	else if (strncmp(name, "on_", 3) == 0) {
		sc_int saved_ref_object = vars->referenced_object;

		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for on_\n");
			return var_return_string("[On_ unavailable]", type, vt_rvalue);
		}
		if (!uip_match("%object%", name + 3, game)) {
			sc_error("var_get_system: invalid object for on_\n");
			return var_return_string("[On_ unavailable]", type, vt_rvalue);
		}

		/* Clear any current temporary for appends. */
		vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
		strcpy(vars->temporary, "");

		/* Write what's on the object into temporary. */
		var_list_on_object(game, vars->referenced_object);

		/* Restore saved referenced object and return. */
		vars->referenced_object = saved_ref_object;
		return var_return_string(vars->temporary, type, vt_rvalue);
	}

	else if (strncmp(name, "onin_", 5) == 0) {
		sc_int saved_ref_object = vars->referenced_object;

		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for onin_\n");
			return var_return_string("[Onin_ unavailable]", type, vt_rvalue);
		}
		if (!uip_match("%object%", name + 5, game)) {
			sc_error("var_get_system: invalid object for onin_\n");
			return var_return_string("[Onin_ unavailable]", type, vt_rvalue);
		}

		/* Clear any current temporary for appends. */
		vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
		strcpy(vars->temporary, "");

		/* Write what's on/in the object into temporary. */
		var_list_onin_object(game, vars->referenced_object);

		/* Restore saved referenced object and return. */
		vars->referenced_object = saved_ref_object;
		return var_return_string(vars->temporary, type, vt_rvalue);
	}

	else if (strcmp(name, "player") == 0) {
		sc_vartype_t vt_key[2];
		const sc_char *playername;

		/*
		 * Return player's name from properties, or just "Player" if not set
		 * in the properties.
		 */
		vt_key[0].string = "Globals";
		vt_key[1].string = "PlayerName";
		playername = prop_get_string(bundle, "S<-ss", vt_key);
		if (sc_strempty(playername))
			playername = "Player";

		return var_return_string(playername, type, vt_rvalue);
	}

	else if (strcmp(name, "room") == 0) {
		const sc_char *roomname;

		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for room\n");
			return var_return_string("[Room unavailable]", type, vt_rvalue);
		}

		/* Return the current player room. */
		roomname = lib_get_room_name(game, gs_playerroom(game));
		return var_return_string(roomname, type, vt_rvalue);
	}

	else if (strcmp(name, "score") == 0) {
		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for score\n");
			return var_return_integer(0, type, vt_rvalue);
		}

		/* Return the current game score. */
		return var_return_integer(game->score, type, vt_rvalue);
	}

	else if (strncmp(name, "state_", 6) == 0) {
		sc_int saved_ref_object = vars->referenced_object;
		sc_vartype_t vt_key[3];
		sc_bool is_statussed;
		sc_char *state;

		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for state_\n");
			return var_return_string("[State_ unavailable]", type, vt_rvalue);
		}
		if (!uip_match("%object%", name + 6, game)) {
			sc_error("var_get_system: invalid object for state_\n");
			return var_return_string("[State_ unavailable]", type, vt_rvalue);
		}

		/* Verify this is a stateful object. */
		vt_key[0].string = "Objects";
		vt_key[1].integer = vars->referenced_object;
		vt_key[2].string = "CurrentState";
		is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
		if (!is_statussed) {
			vars->referenced_object = saved_ref_object;
			sc_error("var_get_system: stateless object for state_\n");
			return var_return_string("[State_ unavailable]", type, vt_rvalue);
		}

		/* Get state, and copy to temporary. */
		state = obj_state_name(game, vars->referenced_object);
		if (!state) {
			vars->referenced_object = saved_ref_object;
			sc_error("var_get_system: invalid state for state_\n");
			return var_return_string("[State_ unknown]", type, vt_rvalue);
		}
		vars->temporary = (sc_char *)sc_realloc(vars->temporary, strlen(state) + 1);
		strcpy(vars->temporary, state);
		sc_free(state);

		/* Restore saved referenced object and return. */
		vars->referenced_object = saved_ref_object;
		return var_return_string(vars->temporary, type, vt_rvalue);
	}

	else if (strncmp(name, "status_", 7) == 0) {
		sc_int saved_ref_object = vars->referenced_object;
		sc_vartype_t vt_key[3];
		sc_bool is_openable;
		sc_int openness;
		const sc_char *retval;

		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for status_\n");
			return var_return_string("[Status_ unavailable]", type, vt_rvalue);
		}
		if (!uip_match("%object%", name + 7, game)) {
			sc_error("var_get_system: invalid object for status_\n");
			return var_return_string("[Status_ unavailable]", type, vt_rvalue);
		}

		/* Verify this is an openable object. */
		vt_key[0].string = "Objects";
		vt_key[1].integer = vars->referenced_object;
		vt_key[2].string = "Openable";
		is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
		if (!is_openable) {
			vars->referenced_object = saved_ref_object;
			sc_error("var_get_system: stateless object for status_\n");
			return var_return_string("[Status_ unavailable]", type, vt_rvalue);
		}

		/* Return one of open, closed, or locked. */
		openness = gs_object_openness(game, vars->referenced_object);
		switch (openness) {
		case OBJ_OPEN:
			retval = "open";
			break;
		case OBJ_CLOSED:
			retval = "closed";
			break;
		case OBJ_LOCKED:
			retval = "locked";
			break;
		default:
			retval = "[Status_ unknown]";
			break;
		}

		/* Restore saved referenced object and return. */
		vars->referenced_object = saved_ref_object;
		return var_return_string(retval, type, vt_rvalue);
	}

	else if (strcmp(name, "t_number") == 0) {
		/* See if we have a referenced number yet. */
		if (vars->is_number_referenced) {
			sc_int number;
			const sc_char *retval;

			/* Return the referenced number as a string. */
			number = vars->referenced_number;
			if (number >= 0 && number < VAR_NUMBERS_SIZE)
				retval = VAR_NUMBERS[number];
			else {
				vars->temporary = (sc_char *)sc_realloc(vars->temporary, 32);
				sprintf(vars->temporary, "%ld", number);
				retval = vars->temporary;
			}

			return var_return_string(retval, type, vt_rvalue);
		} else {
			sc_error("var_get_system: no referenced number yet\n");
			return var_return_string("[Number unknown]", type, vt_rvalue);
		}
	}

	else if (strncmp(name, "t_", 2) == 0) {
		sc_varref_t var;

		/* Find the variable; must be a user, not a system, one. */
		var = var_find(vars, name + 2);
		if (!var) {
			sc_error("var_get_system:"
			         " no such variable, %s\n", name + 2);
			return var_return_string("[Unknown variable]", type, vt_rvalue);
		} else if (var->type != VAR_INTEGER) {
			sc_error("var_get_system:"
			         " not an integer variable, %s\n", name + 2);
			return var_return_string(var->value.string, type, vt_rvalue);
		} else {
			sc_int number;
			const sc_char *retval;

			/* Return the variable value as a string. */
			number = var->value.integer;
			if (number >= 0 && number < VAR_NUMBERS_SIZE)
				retval = VAR_NUMBERS[number];
			else {
				vars->temporary = (sc_char *)sc_realloc(vars->temporary, 32);
				sprintf(vars->temporary, "%ld", number);
				retval = vars->temporary;
			}

			return var_return_string(retval, type, vt_rvalue);
		}
	}

	else if (strcmp(name, "text") == 0) {
		const sc_char *retval;

		/* Return any referenced text, otherwise a neutral string. */
		if (vars->referenced_text)
			retval = vars->referenced_text;
		else {
			sc_error("var_get_system: no text yet to reference\n");
			retval = "[Text unknown]";
		}

		return var_return_string(retval, type, vt_rvalue);
	}

	else if (strcmp(name, "theobject") == 0) {
		/* See if we have a referenced object yet. */
		if (vars->referenced_object != -1) {
			/* Return object name prefixed with "the"... */
			sc_vartype_t vt_key[3];
			const sc_char *prefix, *normalized, *objname;

			vt_key[0].string = "Objects";
			vt_key[1].integer = vars->referenced_object;
			vt_key[2].string = "Prefix";
			prefix = prop_get_string(bundle, "S<-sis", vt_key);

			vars->temporary = (sc_char *)sc_realloc(vars->temporary, strlen(prefix) + 5);
			strcpy(vars->temporary, "");

			normalized = prefix;
			if (sc_compare_word(prefix, "a", 1)) {
				strcat(vars->temporary, "the");
				normalized = prefix + 1;
			} else if (sc_compare_word(prefix, "an", 2)) {
				strcat(vars->temporary, "the");
				normalized = prefix + 2;
			} else if (sc_compare_word(prefix, "the", 3)) {
				strcat(vars->temporary, "the");
				normalized = prefix + 3;
			} else if (sc_compare_word(prefix, "some", 4)) {
				strcat(vars->temporary, "the");
				normalized = prefix + 4;
			} else if (sc_strempty(prefix))
				strcat(vars->temporary, "the ");

			if (!sc_strempty(normalized)) {
				strcat(vars->temporary, normalized);
				strcat(vars->temporary, " ");
			} else if (normalized > prefix)
				strcat(vars->temporary, " ");

			vt_key[2].string = "Short";
			objname = prop_get_string(bundle, "S<-sis", vt_key);
			if (sc_compare_word(objname, "a", 1))
				objname += 1;
			else if (sc_compare_word(objname, "an", 2))
				objname += 2;
			else if (sc_compare_word(objname, "the", 3))
				objname += 3;
			else if (sc_compare_word(objname, "some", 4))
				objname += 4;

			vars->temporary = (sc_char *)sc_realloc(vars->temporary,
			                                        strlen(vars->temporary)
			                                        + strlen(objname) + 1);
			strcat(vars->temporary, objname);

			return var_return_string(vars->temporary, type, vt_rvalue);
		} else {
			sc_error("var_get_system: no referenced object yet\n");
			return var_return_string("[Object unknown]", type, vt_rvalue);
		}
	}

	else if (strcmp(name, "time") == 0) {
		double delta;
		sc_int retval;

		/* Return the elapsed game time in seconds. */
		delta = vars->timestamp - (g_vm->_events->getTotalPlayTicks() / 1000);
		retval = (sc_int) delta + vars->time_offset;

		return var_return_integer(retval, type, vt_rvalue);
	}

	else if (strcmp(name, "title") == 0) {
		sc_vartype_t vt_key[2];
		const sc_char *gamename;

		/* Return the game's title. */
		vt_key[0].string = "Globals";
		vt_key[1].string = "GameName";
		gamename = prop_get_string(bundle, "S<-ss", vt_key);
		if (sc_strempty(gamename))
			gamename = "[Title unknown]";

		return var_return_string(gamename, type, vt_rvalue);
	}

	else if (strcmp(name, "turns") == 0) {
		/* Check there's enough information to return a value. */
		if (!game) {
			sc_error("var_get_system: no game for turns\n");
			return var_return_integer(0, type, vt_rvalue);
		}

		/* Return the count of game turns. */
		return var_return_integer(game->turns, type, vt_rvalue);
	}

	else if (strcmp(name, "version") == 0) {
		/* Return the Adrift emulation level of SCARE. */
		return var_return_integer(SCARE_EMULATION, type, vt_rvalue);
	}

	else if (strcmp(name, "scare_version") == 0) {
		/* Private system variable, return SCARE's version number. */
		return var_return_integer(var_get_scare_version(), type, vt_rvalue);
	}

	return FALSE;
}


/*
 * var_get_user()
 *
 * Retrieve a user variable, and return its type and value, or FALSE if the
 * name passed in is not a defined user variable.
 */
static sc_bool var_get_user(sc_var_setref_t vars, const sc_char *name,
		sc_int *type, sc_vartype_t *vt_rvalue) {
	sc_varref_t var;

	/* Check user variables for a reference to the named variable. */
	var = var_find(vars, name);
	if (var) {
		/* Copy out variable details. */
		*type = var->type;
		switch (var->type) {
		case VAR_INTEGER:
			vt_rvalue->integer = var->value.integer;
			break;
		case VAR_STRING:
			vt_rvalue->string = var->value.string;
			break;

		default:
			sc_fatal("var_get_user: invalid variable type, %ld\n", var->type);
		}

		/* Return success. */
		return TRUE;
	}

	return FALSE;
}


/*
 * var_get()
 *
 * Retrieve a variable, and return its value and type.  Returns FALSE if the
 * named variable does not exist.
 */
sc_bool var_get(sc_var_setref_t vars, const sc_char *name, sc_int *type, sc_vartype_t *vt_rvalue) {
	sc_bool status;
	assert(var_is_valid(vars));
	assert(name && type && vt_rvalue);

	/*
	 * Check user and system variables for a reference to the name.  User
	 * variables take precedence over system ones; that is, they may override
	 * them in a game.
	 */
	status = var_get_user(vars, name, type, vt_rvalue);
	if (!status)
		status = var_get_system(vars, name, type, vt_rvalue);

	if (var_trace) {
		if (status) {
			sc_trace("Variable: %%%s%% retrieved, ", name);
			switch (*type) {
			case VAR_INTEGER:
				sc_trace("%ld", vt_rvalue->integer);
				break;
			case VAR_STRING:
				sc_trace("\"%s\"", vt_rvalue->string);
				break;

			default:
				sc_trace("Variable: invalid variable type, %ld\n", *type);
				break;
			}
			sc_trace("\n");
		} else
			sc_trace("Variable: \"%s\", no such variable\n", name);
	}

	return status;
}


/*
 * var_put_integer()
 * var_get_integer()
 *
 * Convenience functions to store and retrieve an integer variable.  It is
 * an error for the variable not to exist or to have the wrong type.
 */
void var_put_integer(sc_var_setref_t vars, const sc_char *name, sc_int value) {
	sc_vartype_t vt_value;
	assert(var_is_valid(vars));

	vt_value.integer = value;
	var_put(vars, name, VAR_INTEGER, vt_value);
}

sc_int var_get_integer(sc_var_setref_t vars, const sc_char *name) {
	sc_vartype_t vt_rvalue;
	sc_int type;
	assert(var_is_valid(vars));

	if (!var_get(vars, name, &type, &vt_rvalue))
		sc_fatal("var_get_integer: no such variable, %s\n", name);
	else if (type != VAR_INTEGER)
		sc_fatal("var_get_integer: not an integer, %s\n", name);

	return vt_rvalue.integer;
}


/*
 * var_put_string()
 * var_get_string()
 *
 * Convenience functions to store and retrieve a string variable.  It is
 * an error for the variable not to exist or to have the wrong type.
 */
void var_put_string(sc_var_setref_t vars, const sc_char *name, const sc_char *string) {
	sc_vartype_t vt_value;
	assert(var_is_valid(vars));

	vt_value.string = string;
	var_put(vars, name, VAR_STRING, vt_value);
}

const sc_char *var_get_string(sc_var_setref_t vars, const sc_char *name) {
	sc_vartype_t vt_rvalue;
	sc_int type;
	assert(var_is_valid(vars));

	if (!var_get(vars, name, &type, &vt_rvalue))
		sc_fatal("var_get_string: no such variable, %s\n", name);
	else if (type != VAR_STRING)
		sc_fatal("var_get_string: not a string, %s\n", name);

	return vt_rvalue.string;
}


/*
 * var_create()
 *
 * Create and return a new set of variables.  Variables are created from the
 * properties bundle passed in.
 */
sc_var_setref_t var_create(sc_prop_setref_t bundle) {
	sc_var_setref_t vars;
	sc_int var_count, index_;
	sc_vartype_t vt_key[3];
	assert(bundle);

	/* Create a clean set of variables to fill from the bundle. */
	vars = var_create_empty();
	vars->bundle = bundle;

	/* Retrieve the count of variables. */
	vt_key[0].string = "Variables";
	var_count = prop_get_child_count(bundle, "I<-s", vt_key);

	/* Create a variable for each variable property held. */
	for (index_ = 0; index_ < var_count; index_++) {
		const sc_char *name;
		sc_int var_type;
		const sc_char *value;

		/* Retrieve variable name, type, and string initial value. */
		vt_key[1].integer = index_;
		vt_key[2].string = "Name";
		name = prop_get_string(bundle, "S<-sis", vt_key);

		vt_key[2].string = "Type";
		var_type = prop_get_integer(bundle, "I<-sis", vt_key);

		vt_key[2].string = "Value";
		value = prop_get_string(bundle, "S<-sis", vt_key);

		/* Handle numerics and strings differently. */
		switch (var_type) {
		case TAFVAR_NUMERIC: {
			sc_int integer_value;
			if (sscanf(value, "%ld", &integer_value) != 1) {
				sc_error("var_create:"
				         " invalid numeric variable %s, %s\n", name, value);
				integer_value = 0;
			}
			var_put_integer(vars, name, integer_value);
			break;
		}

		case TAFVAR_STRING:
			var_put_string(vars, name, value);
			break;

		default:
			sc_fatal("var_create: invalid variable type, %ld\n", var_type);
		}
	}

	return vars;
}


/*
 * var_register_game()
 *
 * Register the game, used by variables to satisfy requests for selected
 * system variables.  To ensure integrity, the game being registered must
 * reference this variable set.
 */
void var_register_game(sc_var_setref_t vars, sc_gameref_t game) {
	assert(var_is_valid(vars));
	assert(gs_is_game_valid(game));

	if (vars != gs_get_vars(game))
		sc_fatal("var_register_game: game binding error\n");

	vars->game = game;
}


/*
 * var_set_ref_character()
 * var_set_ref_object()
 * var_set_ref_number()
 * var_set_ref_text()
 *
 * Set the "referenced" character, object, number, and text.
 */
void var_set_ref_character(sc_var_setref_t vars, sc_int character) {
	assert(var_is_valid(vars));
	vars->referenced_character = character;
}

void var_set_ref_object(sc_var_setref_t vars, sc_int object) {
	assert(var_is_valid(vars));
	vars->referenced_object = object;
}

void var_set_ref_number(sc_var_setref_t vars, sc_int number) {
	assert(var_is_valid(vars));
	vars->referenced_number = number;
	vars->is_number_referenced = TRUE;
}

void var_set_ref_text(sc_var_setref_t vars, const sc_char *text) {
	assert(var_is_valid(vars));

	/* Take a copy of the string, and retain it. */
	vars->referenced_text = (sc_char *)sc_realloc(vars->referenced_text, strlen(text) + 1);
	strcpy(vars->referenced_text, text);
}


/*
 * var_get_ref_character()
 * var_get_ref_object()
 * var_get_ref_number()
 * var_get_ref_text()
 *
 * Get the "referenced" character, object, number, and text.
 */
sc_int var_get_ref_character(sc_var_setref_t vars) {
	assert(var_is_valid(vars));
	return vars->referenced_character;
}

sc_int var_get_ref_object(sc_var_setref_t vars) {
	assert(var_is_valid(vars));
	return vars->referenced_object;
}

sc_int var_get_ref_number(sc_var_setref_t vars) {
	assert(var_is_valid(vars));
	return vars->referenced_number;
}

const sc_char *var_get_ref_text(sc_var_setref_t vars) {
	assert(var_is_valid(vars));

	/*
	 * If currently nullptr, return "".  A game may check restrictions involving
	 * referenced text before any value has been set; returning "" here for
	 * this case prevents problems later (strcmp (nullptr, ...), for example).
	 */
	return vars->referenced_text ? vars->referenced_text : "";
}


/*
 * var_get_elapsed_seconds()
 * var_set_elapsed_seconds()
 *
 * Get a count of seconds elapsed since the variables were created (start
 * of game), and set the count to a given value (game restore).
 */
sc_uint var_get_elapsed_seconds(sc_var_setref_t vars) {
	double delta;
	assert(var_is_valid(vars));

	delta = vars->timestamp - g_vm->_events->getTotalPlayTicks();
	return (sc_uint) delta + vars->time_offset;
}

void var_set_elapsed_seconds(sc_var_setref_t vars, sc_uint seconds) {
	assert(var_is_valid(vars));

	/*
	 * Reset the timestamp to now, and store seconds in offset.  This is sort-of
	 * forced by the fact that ANSI offers difftime but no 'settime' -- here,
	 * we'd really want to set the timestamp to now less seconds.
	 */
	vars->timestamp = g_vm->_events->getTotalPlayTicks() / 1000;
	vars->time_offset = seconds;
}


/*
 * var_debug_trace()
 *
 * Set variable tracing on/off.
 */
void var_debug_trace(sc_bool flag) {
	var_trace = flag;
}


/*
 * var_debug_dump()
 *
 * Print out a complete variables set.
 */
void var_debug_dump(sc_var_setref_t vars) {
	sc_int index_;
	sc_varref_t var;
	assert(var_is_valid(vars));

	/* Dump complete structure. */
	sc_trace("Variable: debug dump follows...\n");
	sc_trace("vars->bundle = %p\n", (void *) vars->bundle);
	sc_trace("vars->referenced_character = %ld\n", vars->referenced_character);
	sc_trace("vars->referenced_object = %ld\n", vars->referenced_object);
	sc_trace("vars->referenced_number = %ld\n", vars->referenced_number);
	sc_trace("vars->is_number_referenced = %s\n",
	         vars->is_number_referenced ? "true" : "false");

	sc_trace("vars->referenced_text = ");
	if (vars->referenced_text)
		sc_trace("\"%s\"\n", vars->referenced_text);
	else
		sc_trace("(nil)\n");

	sc_trace("vars->temporary = %p\n", (void *) vars->temporary);
	sc_trace("vars->timestamp = %lu\n", (sc_uint) vars->timestamp);
	sc_trace("vars->game = %p\n", (void *) vars->game);

	sc_trace("vars->variables =\n");
	for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++) {
		for (var = vars->variable[index_]; var; var = var->next) {
			if (var == vars->variable[index_])
				sc_trace("%3ld : ", index_);
			else
				sc_trace("    : ");
			switch (var->type) {
			case VAR_STRING:
				sc_trace("[String ] %s = \"%s\"", var->name, var->value.string);
				break;
			case VAR_INTEGER:
				sc_trace("[Integer] %s = %ld", var->name, var->value.integer);
				break;

			default:
				sc_trace("[Invalid] %s = %p", var->name, var->value.voidp);
				break;
			}
			sc_trace("\n");
		}
	}
}

} // End of namespace Adrift
} // End of namespace Glk
