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

namespace Glk {
namespace Adrift {

/* Assorted definitions and constants. */
static const sc_uint DEBUG_MAGIC = 0xc4584d2e;
enum { DEBUG_BUFFER_SIZE = 256 };

/* Debugging command and command argument type. */
enum sc_command_t {
	DEBUG_NONE = 0, DEBUG_CONTINUE, DEBUG_STEP, DEBUG_BUFFER, DEBUG_RESOURCES,
	DEBUG_HELP, DEBUG_GAME,
	DEBUG_PLAYER, DEBUG_ROOMS, DEBUG_OBJECTS, DEBUG_NPCS, DEBUG_EVENTS,
	DEBUG_TASKS, DEBUG_VARIABLES,
	DEBUG_OLDPLAYER, DEBUG_OLDROOMS, DEBUG_OLDOBJECTS, DEBUG_OLDNPCS,
	DEBUG_OLDEVENTS, DEBUG_OLDTASKS, DEBUG_OLDVARIABLES,
	DEBUG_WATCHPLAYER, DEBUG_WATCHOBJECTS, DEBUG_WATCHNPCS, DEBUG_WATCHEVENTS,
	DEBUG_WATCHTASKS, DEBUG_WATCHVARIABLES,
	DEBUG_CLEARPLAYER, DEBUG_CLEAROBJECTS, DEBUG_CLEARNPCS, DEBUG_CLEAREVENTS,
	DEBUG_CLEARTASKS, DEBUG_CLEARVARIABLES,
	DEBUG_WATCHALL, DEBUG_CLEARALL, DEBUG_RANDOM,
	DEBUG_QUIT
};

enum sc_command_type_t { COMMAND_QUERY = 0, COMMAND_RANGE, COMMAND_ONE, COMMAND_ALL };

/* Table connecting debugging command strings to commands. */
struct sc_strings_t {
	const sc_char *const command_string;
	const sc_command_t command;
};
static const sc_strings_t DEBUG_COMMANDS[] = {
	{"continue", DEBUG_CONTINUE}, {"step", DEBUG_STEP}, {"buffer", DEBUG_BUFFER},
	{"resources", DEBUG_RESOURCES}, {"help", DEBUG_HELP}, {"game", DEBUG_GAME},
	{"player", DEBUG_PLAYER}, {"rooms", DEBUG_ROOMS}, {"objects", DEBUG_OBJECTS},
	{"npcs", DEBUG_NPCS}, {"events", DEBUG_EVENTS}, {"tasks", DEBUG_TASKS},
	{"variables", DEBUG_VARIABLES},
	{"oldplayer", DEBUG_OLDPLAYER}, {"oldrooms", DEBUG_OLDROOMS},
	{"oldobjects", DEBUG_OLDOBJECTS}, {"oldnpcs", DEBUG_OLDNPCS},
	{"oldevents", DEBUG_OLDEVENTS}, {"oldtasks", DEBUG_OLDTASKS},
	{"oldvariables", DEBUG_OLDVARIABLES},
	{"watchplayer", DEBUG_WATCHPLAYER}, {"clearplayer", DEBUG_CLEARPLAYER},
	{"watchobjects", DEBUG_WATCHOBJECTS}, {"watchnpcs", DEBUG_WATCHNPCS},
	{"watchevents", DEBUG_WATCHEVENTS}, {"watchtasks", DEBUG_WATCHTASKS},
	{"watchvariables", DEBUG_WATCHVARIABLES},
	{"clearobjects", DEBUG_CLEAROBJECTS}, {"clearnpcs", DEBUG_CLEARNPCS},
	{"clearevents", DEBUG_CLEAREVENTS}, {"cleartasks", DEBUG_CLEARTASKS},
	{"clearvariables", DEBUG_CLEARVARIABLES}, {"watchall", DEBUG_WATCHALL},
	{"clearall", DEBUG_CLEARALL}, {"random", DEBUG_RANDOM}, {"quit", DEBUG_QUIT},
	{nullptr, DEBUG_NONE}
};

/*
 * Debugging control information structure.  The structure is created and
 * added to the game on enabling debug, and removed and destroyed on
 * disabling debugging.
 */
struct sc_debugger_s {
	sc_uint magic;
	sc_bool *watch_objects;
	sc_bool *watch_npcs;
	sc_bool *watch_events;
	sc_bool *watch_tasks;
	sc_bool *watch_variables;
	sc_bool watch_player;
	sc_bool single_step;
	sc_bool quit_pending;
	sc_uint elapsed_seconds;
};
typedef sc_debugger_s sc_debugger_t;


/*
 * debug_is_valid()
 *
 * Return TRUE if pointer is a valid debugger, FALSE otherwise.
 */
static sc_bool debug_is_valid(sc_debuggerref_t debug) {
	return debug && debug->magic == DEBUG_MAGIC;
}


/*
 * debug_get_debugger()
 *
 * Return the debugger reference from a game, or NULL if none.
 */
static sc_debuggerref_t debug_get_debugger(sc_gameref_t game) {
	assert(gs_is_game_valid(game));

	return game->debugger;
}


/*
 * debug_variable_count()
 *
 * Common helper to return the count of variables defined in a game.
 */
static sc_int debug_variable_count(sc_gameref_t game) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key;
	sc_int variable_count;

	/* Find and return the variables count. */
	vt_key.string = "Variables";
	variable_count = prop_get_child_count(bundle, "I<-s", &vt_key);

	return variable_count;
}


/*
 * debug_initialize()
 *
 * Create a new set of debug control information, and append it to the
 * game passed in.
 */
static void debug_initialize(sc_gameref_t game) {
	sc_debuggerref_t debug;

	/* Create the easy bits of the new debugging set. */
	debug = (sc_debuggerref_t)sc_malloc(sizeof(*debug));
	debug->magic = DEBUG_MAGIC;
	debug->watch_player = FALSE;
	debug->single_step = FALSE;
	debug->quit_pending = FALSE;
	debug->elapsed_seconds = 0;

	/* Allocate watchpoints for everything we can watch. */
	debug->watch_objects = (sc_bool *)sc_malloc(gs_object_count(game)
	                       * sizeof(*debug->watch_objects));
	debug->watch_npcs = (sc_bool *)sc_malloc(gs_npc_count(game)
	                    * sizeof(*debug->watch_npcs));
	debug->watch_events = (sc_bool *)sc_malloc(gs_event_count(game)
	                      * sizeof(*debug->watch_events));
	debug->watch_tasks = (sc_bool *)sc_malloc(gs_task_count(game)
	                     * sizeof(*debug->watch_tasks));
	debug->watch_variables = (sc_bool *)sc_malloc(debug_variable_count(game)
	                         * sizeof(*debug->watch_variables));

	/* Clear all watchpoint arrays. */
	memset(debug->watch_objects, FALSE,
	       gs_object_count(game) * sizeof(*debug->watch_objects));
	memset(debug->watch_npcs, FALSE,
	       gs_npc_count(game) * sizeof(*debug->watch_npcs));
	memset(debug->watch_events, FALSE,
	       gs_event_count(game) * sizeof(*debug->watch_events));
	memset(debug->watch_tasks, FALSE,
	       gs_task_count(game) * sizeof(*debug->watch_tasks));
	memset(debug->watch_variables, FALSE,
	       debug_variable_count(game) * sizeof(*debug->watch_variables));

	/* Append the new debugger set to the game. */
	assert(!game->debugger);
	game->debugger = debug;
}


/*
 * debug_finalize()
 *
 * Destroy a debug data set, free its heap memory, and remove its reference
 * from the game.
 */
static void debug_finalize(sc_gameref_t game) {
	sc_debuggerref_t debug = debug_get_debugger(game);
	assert(debug_is_valid(debug));

	/* Free all allocated watchpoint arrays. */
	sc_free(debug->watch_objects);
	sc_free(debug->watch_npcs);
	sc_free(debug->watch_events);
	sc_free(debug->watch_tasks);
	sc_free(debug->watch_variables);

	/* Poison and free the debugger itself. */
	memset(debug, 0xaa, sizeof(*debug));
	sc_free(debug);

	/* Remove the debug reference from the game. */
	game->debugger = nullptr;
}


/*
 * debug_help()
 *
 * Print debugging help.
 */
static void debug_help(sc_command_t topic) {
	/* Is help general, or specific? */
	if (topic == DEBUG_NONE) {
		if_print_debug(
		    "The following debugging commands examine game state:\n\n");
		if_print_debug(
		    " game               -- Print general game information,"
		    " and class counts\n"
		    " player             -- Show the player location and position\n"
		    " rooms [Range]      -- Print information on game rooms\n"
		    " objects [Range]    -- Print information on objects in the game\n"
		    " npcs [Range]       -- Print information on game NPCs\n"
		    " events [Range]     -- Print information on the game's events\n"
		    " tasks [Range]      -- Print information on the game's tasks\n"
		    " variables [Range]  -- Show variables defined by the game\n\n");
		if_print_debug(
		    "Most commands take range inputs.  This can be a single number, to"
		    " apply the command to just that item, a range such as '0 to 10' (or"
		    " '0 - 10', '0 .. 10', or simply '0 10') to apply to that range of"
		    " items, or '*' to apply the command to all items of the class.  If"
		    " omitted, the command is applied only to the items of the class"
		    " 'relevant' to the current game state; see the help for specific"
		    " commands for more on what is 'relevant'.\n\n");
		if_print_debug(
		    "The 'player', 'objects', 'npcs', 'events', 'tasks', and 'variables'"
		    " commands may be prefixed with 'old', in which case the values"
		    " printed will be those for the previous game turn, rather than the"
		    " current values.\n\n");
		if_print_debug(
		    "These debugging commands manage watchpoints:\n\n");
		if_print_debug(
		    "The 'player', 'objects', 'npcs', 'events', 'tasks', and 'variables'"
		    " commands may be prefixed with 'watch', to set watchpoints."
		    "  Watchpoints automatically enter the debugger when the item changes"
		    " state during a game turn.  For example 'watchobject 10' monitors"
		    " object 10 for changes, and 'watchnpc *' monitors all NPCs.  A"
		    " 'watch' command with no range prints out all watchpoints set for"
		    " that class.\n\n");
		if_print_debug(
		    "Prefix commands with 'clear' to clear watchpoints, for example"
		    " 'clearnpcs *'.  Use 'watchall' to obtain a complete list of every"
		    " watchpoint set, and 'clearall' to clear all watchpoints in one go."
		    "  A 'clear' command with no range behaves the same as a 'watch'"
		    " command with no range.\n\n");
		if_print_debug(
		    "These debugging commands print details of game output and control the"
		    " debugger and interpreter:\n\n");
		if_print_debug(
		    " buffer          -- Show the current buffered game text\n"
		    " resources       -- Show current and requested game resources\n"
		    " random [Seed]   -- Control the random number generator\n"
		    " step            -- Run one game turn, then re-enter the debugger\n"
		    " continue        -- Leave the debugger and resume the game\n"
		    " quit            -- Exit the interpreter main loop\n"
		    " help [Command]  -- Print help specific to Command\n\n");
		if_print_debug(
		    "Debugging commands may be abbreviated to their shortest unambiguous"
		    " form.\n\n");
		if_print_debug(
		    "Use the 'debug' or '#debug' command in a game, typed at the usual"
		    " game prompt, to return to the debugger.\n");
		return;
	}

	/* Command-specific help. */
	switch (topic) {
	case DEBUG_HELP:
		if_print_debug(
		    "Give the name of the command you want help on, for example 'help"
		    " continue'.\n");
		break;

	case DEBUG_CONTINUE:
		if_print_debug(
		    "Leave the debugger and resume the game.  Use the 'debug' or '#debug'"
		    " command in a game, typed at the usual game prompt, to return to the"
		    " debugger.\n");
		break;

	case DEBUG_STEP:
		if_print_debug(
		    "Run one game turn, then re-enter the debugger.  Useful for games that"
		    " intercept empty input lines, which otherwise catch the 'debug'"
		    " command before SCARE can get to it.\n");
		break;

	case DEBUG_QUIT:
		if_print_debug(
		    "Exit the interpreter main loop.  Equivalent to a confirmed 'quit'"
		    " from within the game itself, this ends the interpreter session.\n");
		break;

	case DEBUG_BUFFER:
		if_print_debug(
		    "Print the current text that the game has buffered for output.  The"
		    " debugger catches games before they have printed their turn output"
		    " -- this is the text that will be filtered and printed on exiting the"
		    " debugger.\n");
		break;

	case DEBUG_RESOURCES:
		if_print_debug(
		    "Print any resources currently active, and any requested by the game"
		    " on the current turn.  The requested resources will become the active"
		    " ones on exiting the debugger.\n");
		break;

	case DEBUG_RANDOM:
		if_print_debug(
		    "If no seed is given, report the current random number generator"
		    " setting.  Otherwise, seed the random number generator with the value"
		    " given.  This is useful for persuading games with random sections to"
		    " behave predictably.  A new seed value of zero is invalid.\n");
		break;

	case DEBUG_GAME:
		if_print_debug(
		    "Print general game information, including the number of rooms,"
		    " objects, events, tasks, and variables that the game defines\n");
		break;

	case DEBUG_PLAYER:
		if_print_debug(
		    "Print out the current player room and position, and any parent object"
		    " of the player character.\n");
		break;

	case DEBUG_OLDPLAYER:
		if_print_debug(
		    "Print out the player room and position from the previous turn, and"
		    " any parent object of the player character.\n");
		break;

	case DEBUG_ROOMS:
		if_print_debug(
		    "Print out the name and contents of rooms in the range.  If no range,"
		    " print details of the room containing the player.\n");
		break;

	case DEBUG_OLDROOMS:
		if_print_debug(
		    "Print out the name and contents of rooms in the range for the"
		    " previous turn.  If no range, print details of the room that"
		    " contained the player on the previous turn.\n");
		break;

	case DEBUG_OBJECTS:
		if_print_debug(
		    "Print out details of all objects in the range.  If no range, print"
		    " details of objects in the room containing the player, and visible to"
		    " the player.\n");
		break;

	case DEBUG_OLDOBJECTS:
		if_print_debug(
		    "Print out details of all objects in the range for the previous turn."
		    "  If no range, print details of objects in the room that contained"
		    " the player, and were visible to the player.\n");
		break;

	case DEBUG_NPCS:
		if_print_debug(
		    "Print out details of all NPCs in the range.  If no range, print"
		    " details of only NPCs in the room containing the player.\n");
		break;

	case DEBUG_OLDNPCS:
		if_print_debug(
		    "Print out details of all NPCs in the range for the previous turn."
		    "  If no range, print details of only NPCs in the room that contained"
		    " the player.\n");
		break;

	case DEBUG_EVENTS:
		if_print_debug(
		    "Print out details of all events in the range.  If no range, print"
		    " details of only events currently running.\n");
		break;

	case DEBUG_OLDEVENTS:
		if_print_debug(
		    "Print out details of all events in the range for the previous turn."
		    "  If no range, print details of only events running on the previous"
		    " turn.\n");
		break;

	case DEBUG_TASKS:
		if_print_debug(
		    "Print out details of all tasks in the range.  If no range, print"
		    " details of only tasks that are runnable, for the current state of"
		    " the game.\n");
		break;

	case DEBUG_OLDTASKS:
		if_print_debug(
		    "Print out details of all tasks in the range for the previous turn."
		    "  If no range, print details of only tasks that were runnable, for"
		    " the previous state of the game.\n");
		break;

	case DEBUG_VARIABLES:
		if_print_debug(
		    "Print out the names, types, and values of all game variables in the"
		    " range.  If no range, print details of all variables (equivalent to"
		    " 'variables *').\n");
		break;

	case DEBUG_OLDVARIABLES:
		if_print_debug(
		    "Print out the names, types, and values at the previous turn of all"
		    " game variables in the range.  If no range, print details of all"
		    " variables (equivalent to 'variables *').\n");
		break;

	case DEBUG_WATCHPLAYER:
		if_print_debug(
		    "If no range is given, list any watchpoint on player movement.  If"
		    " range '0' is given, set a watchpoint on player movement.  Other"
		    " usages of 'watchplayer' behave as if no range is given.\n");
		break;

	case DEBUG_WATCHOBJECTS:
		if_print_debug(
		    "Set watchpoints on all objects in the range.  If no range, list out"
		    " object watchpoints currently set.\n");
		break;

	case DEBUG_WATCHNPCS:
		if_print_debug(
		    "Set watchpoints on all NPCs in the range.  If no range, list out NPC"
		    " watchpoints currently set.\n");
		break;

	case DEBUG_WATCHEVENTS:
		if_print_debug(
		    "Set watchpoints on all events in the range.  If no range, list out"
		    " event watchpoints currently set.\n");
		break;

	case DEBUG_WATCHTASKS:
		if_print_debug(
		    "Set watchpoints on all tasks in the range.  If no range, list out"
		    " task watchpoints currently set.\n");
		break;

	case DEBUG_WATCHVARIABLES:
		if_print_debug(
		    "Set watchpoints on all game variables in the range.  If no range,"
		    " list variable watchpoints currently set.\n");
		break;

	case DEBUG_CLEARPLAYER:
		if_print_debug(
		    "Clear any watchpoint set on player movements.\n");
		break;

	case DEBUG_CLEAROBJECTS:
		if_print_debug(
		    "Clear watchpoints on all objects in the range.  If no range, list"
		    " out object watchpoints currently set.\n");
		break;

	case DEBUG_CLEARNPCS:
		if_print_debug(
		    "Clear watchpoints on all NPCs in the range.  If no range, list out"
		    " NPC watchpoints currently set.\n");
		break;

	case DEBUG_CLEAREVENTS:
		if_print_debug(
		    "Clear watchpoints on all events in the range.  If no range, list out"
		    " event watchpoints currently set.\n");
		break;

	case DEBUG_CLEARTASKS:
		if_print_debug(
		    "Clear watchpoints on all tasks in the range.  If no range, list out"
		    " task watchpoints currently set.\n");
		break;

	case DEBUG_CLEARVARIABLES:
		if_print_debug(
		    "Clear watchpoints on all game variables in the range.  If no range,"
		    " list variable watchpoints currently set.\n");
		break;

	case DEBUG_WATCHALL:
		if_print_debug(
		    "Print out a list of all all watchpoints set for all the classes of"
		    " item on which watchpoints can be used.\n");
		break;

	case DEBUG_CLEARALL:
		if_print_debug(
		    "Clear all watchpoints set, on all classes of item on which"
		    " watchpoints can be used.\n");
		break;

	default:
		if_print_debug(
		    "Sorry, there is no help available on that at the moment.\n");
		break;
	}
}


/*
 * debug_print_quoted()
 * debug_print_player()
 * debug_print_room()
 * debug_print_object()
 * debug_print_npc()
 * debug_print_event()
 * debug_print_task()
 * debug_print_variable()
 *
 * Low level output helpers.
 */
static void debug_print_quoted(const sc_char *string) {
	if_print_debug_character('"');
	if_print_debug(string);
	if_print_debug_character('"');
}

static void debug_print_player(sc_gameref_t game) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key[2];
	const sc_char *playername;

	vt_key[0].string = "Globals";
	vt_key[1].string = "PlayerName";
	playername = prop_get_string(bundle, "S<-ss", vt_key);
	if_print_debug("Player ");
	debug_print_quoted(playername);
}

static void debug_print_room(sc_gameref_t game, sc_int room) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key[3];
	sc_char buffer[32];
	const sc_char *name;

	if_print_debug("Room ");
	if (room < 0 || room >= gs_room_count(game)) {
		sprintf(buffer, "%ld ", room);
		if_print_debug(buffer);
		if_print_debug("[Out of range]");
		return;
	}

	vt_key[0].string = "Rooms";
	vt_key[1].integer = room;
	vt_key[2].string = "Short";
	name = prop_get_string(bundle, "S<-sis", vt_key);
	sprintf(buffer, "%ld ", room);
	if_print_debug(buffer);
	debug_print_quoted(name);
}

static void debug_print_object(sc_gameref_t game, sc_int object) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key[3];
	sc_bool bstatic;
	sc_char buffer[32];
	const sc_char *prefix, *name;

	if (object < 0 || object >= gs_object_count(game)) {
		if_print_debug("Object ");
		sprintf(buffer, "%ld ", object);
		if_print_debug(buffer);
		if_print_debug("[Out of range]");
		return;
	}

	vt_key[0].string = "Objects";
	vt_key[1].integer = object;
	vt_key[2].string = "Static";
	bstatic = prop_get_boolean(bundle, "B<-sis", vt_key);
	vt_key[2].string = "Prefix";
	prefix = prop_get_string(bundle, "S<-sis", vt_key);
	vt_key[2].string = "Short";
	name = prop_get_string(bundle, "S<-sis", vt_key);
	if (bstatic)
		if_print_debug("Static ");
	else
		if_print_debug("Dynamic ");
	sprintf(buffer, "%ld ", object);
	if_print_debug(buffer);
	debug_print_quoted(prefix);
	if_print_debug_character(' ');
	debug_print_quoted(name);
}

static void debug_print_npc(sc_gameref_t game, sc_int npc) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key[3];
	sc_char buffer[32];
	const sc_char *prefix, *name;

	if_print_debug("NPC ");
	if (npc < 0 || npc >= gs_npc_count(game)) {
		sprintf(buffer, "%ld ", npc);
		if_print_debug(buffer);
		if_print_debug("[Out of range]");
		return;
	}

	vt_key[0].string = "NPCs";
	vt_key[1].integer = npc;
	vt_key[2].string = "Prefix";
	prefix = prop_get_string(bundle, "S<-sis", vt_key);
	vt_key[2].string = "Name";
	name = prop_get_string(bundle, "S<-sis", vt_key);
	sprintf(buffer, "%ld ", npc);
	if_print_debug(buffer);
	debug_print_quoted(prefix);
	if_print_debug_character(' ');
	debug_print_quoted(name);
}

static void debug_print_event(sc_gameref_t game, sc_int event) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key[3];
	sc_char buffer[32];
	const sc_char *name;

	if_print_debug("Event ");
	if (event < 0 || event >= gs_event_count(game)) {
		sprintf(buffer, "%ld ", event);
		if_print_debug(buffer);
		if_print_debug("[Out of range]");
		return;
	}

	vt_key[0].string = "Events";
	vt_key[1].integer = event;
	vt_key[2].string = "Short";
	name = prop_get_string(bundle, "S<-sis", vt_key);
	sprintf(buffer, "%ld ", event);
	if_print_debug(buffer);
	debug_print_quoted(name);
}

static void debug_print_task(sc_gameref_t game, sc_int task) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_vartype_t vt_key[4];
	sc_char buffer[32];
	const sc_char *command;

	if_print_debug("Task ");
	if (task < 0 || task >= gs_task_count(game)) {
		sprintf(buffer, "%ld ", task);
		if_print_debug(buffer);
		if_print_debug("[Out of range]");
		return;
	}

	vt_key[0].string = "Tasks";
	vt_key[1].integer = task;
	vt_key[2].string = "Command";
	vt_key[3].integer = 0;
	command = prop_get_string(bundle, "S<-sisi", vt_key);
	sprintf(buffer, "%ld ", task);
	if_print_debug(buffer);
	debug_print_quoted(command);
}

static void debug_print_variable(sc_gameref_t game, sc_int variable) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	const sc_var_setref_t vars = gs_get_vars(game);
	sc_vartype_t vt_key[3], vt_rvalue;
	sc_char buffer[32];
	sc_int var_type;
	const sc_char *name;

	if (variable < 0 || variable >= debug_variable_count(game)) {
		if_print_debug("Variable ");
		sprintf(buffer, "%ld ", variable);
		if_print_debug(buffer);
		if_print_debug("[Out of range]");
		return;
	}

	vt_key[0].string = "Variables";
	vt_key[1].integer = variable;
	vt_key[2].string = "Name";
	name = prop_get_string(bundle, "S<-sis", vt_key);

	if (var_get(vars, name, &var_type, &vt_rvalue)) {
		switch (var_type) {
		case VAR_INTEGER:
			if_print_debug("Integer ");
			break;
		case VAR_STRING:
			if_print_debug("String ");
			break;
		default:
			if_print_debug("[Invalid type] ");
			break;
		}
	} else
		if_print_debug("[Invalid variable] ");
	sprintf(buffer, "%ld ", variable);
	if_print_debug(buffer);
	debug_print_quoted(name);
}


/*
 * debug_game()
 *
 * Display overall game details.
 */
static void debug_game(sc_gameref_t game, sc_command_type_t type) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	const sc_debuggerref_t debug = debug_get_debugger(game);
	sc_vartype_t vt_key[2];
	const sc_char *version, *gamename, *compiledate, *gameauthor;
	sc_int perspective, waitturns;
	sc_bool has_sound, has_graphics, has_battle;
	sc_char buffer[32];
	assert(debug_is_valid(debug));

	if (type != COMMAND_QUERY) {
		if_print_debug("The Game command takes no arguments.\n");
		return;
	}

	if_print_debug("Game ");
	vt_key[0].string = "Globals";
	vt_key[1].string = "GameName";
	gamename = prop_get_string(bundle, "S<-ss", vt_key);
	debug_print_quoted(gamename);
	if_print_debug_character('\n');

	if_print_debug("    Compiled ");
	vt_key[0].string = "CompileDate";
	compiledate = prop_get_string(bundle, "S<-s", vt_key);
	debug_print_quoted(compiledate);

	if_print_debug(", Author ");
	vt_key[0].string = "Globals";
	vt_key[1].string = "GameAuthor";
	gameauthor = prop_get_string(bundle, "S<-ss", vt_key);
	debug_print_quoted(gameauthor);
	if_print_debug_character('\n');

	vt_key[0].string = "VersionString";
	version = prop_get_string(bundle, "S<-s", vt_key);
	if_print_debug("    Version ");
	if_print_debug(version);

	vt_key[0].string = "Globals";
	vt_key[1].string = "Perspective";
	perspective = prop_get_integer(bundle, "I<-ss", vt_key);
	switch (perspective) {
	case 0:
		if_print_debug(", First person");
		break;
	case 1:
		if_print_debug(", Second person");
		break;
	case 2:
		if_print_debug(", Third person");
		break;
	default:
		if_print_debug(", [Unknown perspective]");
		break;
	}

	vt_key[0].string = "Globals";
	vt_key[1].string = "WaitTurns";
	waitturns = prop_get_integer(bundle, "I<-ss", vt_key);
	if_print_debug(", Waitturns ");
	sprintf(buffer, "%ld", waitturns);
	if_print_debug(buffer);

	vt_key[0].string = "Globals";
	vt_key[1].string = "Sound";
	has_sound = prop_get_boolean(bundle, "B<-ss", vt_key);
	vt_key[1].string = "Graphics";
	has_graphics = prop_get_boolean(bundle, "B<-ss", vt_key);
	if (has_sound)
		if_print_debug(", Sound");
	if (has_graphics)
		if_print_debug(", Graphics");
	if_print_debug_character('\n');

	vt_key[0].string = "Globals";
	vt_key[1].string = "BattleSystem";
	has_battle = prop_get_boolean(bundle, "B<-ss", vt_key);
	if (has_battle)
		if_print_debug("    Battle system\n");

	if_print_debug("    Room count ");
	sprintf(buffer, "%ld", gs_room_count(game));
	if_print_debug(buffer);

	if_print_debug(", Object count ");
	sprintf(buffer, "%ld", gs_object_count(game));
	if_print_debug(buffer);

	if_print_debug(", NPC count ");
	sprintf(buffer, "%ld", gs_npc_count(game));
	if_print_debug(buffer);
	if_print_debug_character('\n');

	if_print_debug("    Event count ");
	sprintf(buffer, "%ld", gs_event_count(game));
	if_print_debug(buffer);

	if_print_debug(", Task count ");
	sprintf(buffer, "%ld", gs_task_count(game));
	if_print_debug(buffer);

	if_print_debug(", Variable count ");
	sprintf(buffer, "%ld", debug_variable_count(game));
	if_print_debug(buffer);
	if_print_debug_character('\n');

	if (game->is_running)
		if_print_debug("    Running");
	else
		if_print_debug("    Not running");
	if (game->has_completed)
		if_print_debug(", Completed");
	else
		if_print_debug(", Not completed");
	if (game->verbose)
		if_print_debug(", Verbose");
	else
		if_print_debug(", Not verbose");
	if (game->bold_room_names)
		if_print_debug(", Bold");
	else
		if_print_debug(", Not bold");
	if (game->undo_available)
		if_print_debug(", Undo");
	else
		if_print_debug(", No undo");
	if_print_debug_character('\n');

	if_print_debug("    Score ");
	sprintf(buffer, "%ld", game->score);
	if_print_debug(buffer);
	if_print_debug(", Turns ");
	sprintf(buffer, "%ld", game->turns);
	if_print_debug(buffer);
	if_print_debug(", Seconds ");
	sprintf(buffer, "%lu", debug->elapsed_seconds);
	if_print_debug(buffer);
	if_print_debug_character('\n');
}


/*
 * debug_player()
 *
 * Print a few brief details about the player status.
 */
static void debug_player(sc_gameref_t game, sc_command_t command, sc_command_type_t type) {
	if (type != COMMAND_QUERY) {
		if_print_debug("The Player command takes no arguments.\n");
		return;
	}

	if (command == DEBUG_OLDPLAYER) {
		if (!game->undo_available) {
			if_print_debug("There is no previous game state to examine.\n");
			return;
		}

		game = game->undo;
		assert(gs_is_game_valid(game));
	}

	debug_print_player(game);
	if_print_debug_character('\n');

	if (gs_playerroom(game) == -1)
		if_print_debug("    Hidden!\n");
	else {
		if_print_debug("    In ");
		debug_print_room(game, gs_playerroom(game));
		if_print_debug_character('\n');
	}

	switch (gs_playerposition(game)) {
	case 0:
		if_print_debug("    Standing\n");
		break;
	case 1:
		if_print_debug("    Sitting\n");
		break;
	case 2:
		if_print_debug("    Lying\n");
		break;
	default:
		if_print_debug("    [Invalid position]\n");
		break;
	}

	if (gs_playerparent(game) != -1) {
		if_print_debug("    Parent is ");
		debug_print_object(game, gs_playerparent(game));
		if_print_debug_character('\n');
	}
}


/*
 * debug_normalize_arguments()
 *
 * Normalize a set of arguments parsed from a debugger command line, for
 * debug commands that take ranges.
 */
static sc_bool debug_normalize_arguments(sc_command_type_t type, sc_int *arg1, sc_int *arg2, sc_int limit) {
	sc_int low = 0, high = 0;

	/* Set range low and high depending on the command type. */
	switch (type) {
	case COMMAND_QUERY:
	case COMMAND_ALL:
		low = 0;
		high = limit - 1;
		break;
	case COMMAND_ONE:
		low = *arg1;
		high = *arg1;
		break;
	case COMMAND_RANGE:
		low = *arg1;
		high = *arg2;
		break;
	default:
		sc_fatal("debug_normalize_arguments: bad command type\n");
	}

	/* If range is valid, copy out and return TRUE. */
	if (low >= 0 && low < limit && high >= 0 && high < limit && high >= low) {
		*arg1 = low;
		*arg2 = high;
		return TRUE;
	}

	/* Input range is invalid. */
	return FALSE;
}


/*
 * debug_filter_room()
 * debug_dump_room()
 *
 * Print details of rooms and their direct contents.
 */
static sc_bool debug_filter_room(sc_gameref_t game, sc_int room) {
	return room == gs_playerroom(game);
}

static void debug_dump_room(sc_gameref_t game, sc_int room) {
	sc_int object, npc;

	debug_print_room(game, room);
	if_print_debug_character('\n');

	if (gs_room_seen(game, room))
		if_print_debug("    Visited\n");
	else
		if_print_debug("    Not visited\n");

	if (gs_playerroom(game) == room) {
		if_print_debug("    ");
		debug_print_player(game);
		if_print_debug_character('\n');
	}

	for (object = 0; object < gs_object_count(game); object++) {
		if (obj_indirectly_in_room(game, object, room)) {
			if_print_debug("    ");
			debug_print_object(game, object);
			if_print_debug_character('\n');
		}
	}

	for (npc = 0; npc < gs_npc_count(game); npc++) {
		if (npc_in_room(game, npc, room)) {
			if_print_debug("    ");
			debug_print_npc(game, npc);
			if_print_debug_character('\n');
		}
	}
}


/*
 * debug_filter_object()
 * debug_dump_object()
 *
 * Print the changeable details of game objects.
 */
static sc_bool debug_filter_object(sc_gameref_t game, sc_int object) {
	return obj_indirectly_in_room(game, object, gs_playerroom(game));
}

static void debug_dump_object(sc_gameref_t game, sc_int object) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	sc_int openness;
	sc_vartype_t vt_key[3];
	sc_bool bstatic, is_statussed;
	sc_int position, parent;

	debug_print_object(game, object);
	if_print_debug_character('\n');

	vt_key[0].string = "Objects";
	vt_key[1].integer = object;
	vt_key[2].string = "Static";
	bstatic = prop_get_boolean(bundle, "B<-sis", vt_key);

	if (gs_object_seen(game, object))
		if_print_debug("    Seen");
	else
		if_print_debug("    Not seen");
	if (bstatic) {
		if (gs_object_static_unmoved(game, object))
			if_print_debug(", Not relocated");
		else
			if_print_debug(", Relocated");
	} else {
		vt_key[2].string = "OnlyWhenNotMoved";
		if (prop_get_integer(bundle, "I<-sis", vt_key) == 1) {
			if (gs_object_unmoved(game, object))
				if_print_debug(", Not moved");
			else
				if_print_debug(", Moved");
		}
	}
	openness = gs_object_openness(game, object);
	switch (openness) {
	case OBJ_OPEN:
		if_print_debug(", Open");
		break;
	case OBJ_CLOSED:
		if_print_debug(", Closed");
		break;
	case OBJ_LOCKED:
		if_print_debug(", Locked");
		break;
	default:
		if_print_debug(", Unknown openness");
		break;
	}
	if_print_debug_character('\n');

	position = gs_object_position(game, object);
	parent = gs_object_parent(game, object);
	switch (position) {
	case OBJ_HIDDEN:
		if (bstatic)
			if_print_debug("    Static default\n");
		else
			if_print_debug("    Hidden\n");
		break;
	case OBJ_HELD_PLAYER:
		if_print_debug("    Held by ");
		debug_print_player(game);
		if_print_debug_character('\n');
		break;
	case OBJ_HELD_NPC:
		if_print_debug("    Held by ");
		debug_print_npc(game, parent);
		if_print_debug_character('\n');
		break;
	case OBJ_WORN_PLAYER:
		if_print_debug("    Worn by ");
		debug_print_player(game);
		if_print_debug_character('\n');
		break;
	case OBJ_WORN_NPC:
		if_print_debug("    Worn by ");
		debug_print_npc(game, parent);
		if_print_debug_character('\n');
		break;
	case OBJ_PART_NPC:
		if_print_debug("    Part of ");
		if (parent == -1)
			debug_print_player(game);
		else
			debug_print_npc(game, parent);
		if_print_debug_character('\n');
		break;
	case OBJ_ON_OBJECT:
		if_print_debug("    On ");
		debug_print_object(game, parent);
		if_print_debug_character('\n');
		break;
	case OBJ_IN_OBJECT:
		if_print_debug("    Inside ");
		debug_print_object(game, parent);
		if_print_debug_character('\n');
		break;
	default:
		if_print_debug("    In ");
		debug_print_room(game, position - 1);
		if_print_debug_character('\n');
		break;
	}

	vt_key[0].string = "Objects";
	vt_key[1].integer = object;
	vt_key[2].string = "CurrentState";
	is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
	if (is_statussed) {
		sc_char buffer[32];
		const sc_char *states;

		if_print_debug("    State ");
		sprintf(buffer, "%ld", gs_object_state(game, object));
		if_print_debug(buffer);

		vt_key[2].string = "States";
		states = prop_get_string(bundle, "S<-sis", vt_key);
		if_print_debug(" of ");
		debug_print_quoted(states);
		if_print_debug_character('\n');
	}
}


/*
 * debug_filter_npc()
 * debug_dump_npc()
 *
 * Print stuff about NPCs.
 */
static sc_bool debug_filter_npc(sc_gameref_t game, sc_int npc) {
	return npc_in_room(game, npc, gs_playerroom(game));
}

static void debug_dump_npc(sc_gameref_t game, sc_int npc) {
	debug_print_npc(game, npc);
	if_print_debug_character('\n');

	if (gs_npc_seen(game, npc))
		if_print_debug("    Seen\n");
	else
		if_print_debug("    Not seen\n");

	if (gs_npc_location(game, npc) - 1 == -1)
		if_print_debug("    Hidden\n");
	else {
		if_print_debug("    In ");
		debug_print_room(game, gs_npc_location(game, npc) - 1);
		if_print_debug_character('\n');
	}

	switch (gs_npc_position(game, npc)) {
	case 0:
		if_print_debug("    Standing\n");
		break;
	case 1:
		if_print_debug("    Sitting\n");
		break;
	case 2:
		if_print_debug("    Lying\n");
		break;
	default:
		if_print_debug("    [Invalid position]\n");
		break;
	}

	if (gs_npc_parent(game, npc) != -1) {
		if_print_debug("    Parent is ");
		debug_print_object(game, gs_npc_parent(game, npc));
		if_print_debug_character('\n');
	}

	if (gs_npc_walkstep_count(game, npc) > 0) {
		sc_char buffer[32];
		sc_int walk;

		if_print_debug("    Walkstep count ");
		sprintf(buffer, "%ld", gs_npc_walkstep_count(game, npc));
		if_print_debug(buffer);
		if_print_debug(", Walks { ");
		for (walk = 0; walk < gs_npc_walkstep_count(game, npc); walk++) {
			sprintf(buffer, "%ld", gs_npc_walkstep(game, npc, walk));
			if_print_debug(buffer);
			if_print_debug_character(' ');
		}
		if_print_debug("}.\n");
	}
}


/*
 * debug_filter_event()
 * debug_dump_event()
 *
 * Print stuff about events.
 */
static sc_bool debug_filter_event(sc_gameref_t game, sc_int event) {
	return gs_event_state(game, event) == ES_RUNNING;
}

static void debug_dump_event(sc_gameref_t game, sc_int event) {
	sc_char buffer[32];

	debug_print_event(game, event);
	if_print_debug_character('\n');

	switch (gs_event_state(game, event)) {
	case ES_WAITING:
		if_print_debug("    Waiting\n");
		break;
	case ES_RUNNING:
		if_print_debug("    Running\n");
		break;
	case ES_AWAITING:
		if_print_debug("    Awaiting\n");
		break;
	case ES_FINISHED:
		if_print_debug("    Finished\n");
		break;
	case ES_PAUSED:
		if_print_debug("    Paused\n");
		break;
	default:
		if_print_debug("    [Invalid state]\n");
		break;
	}

	if_print_debug("    Time ");
	sprintf(buffer, "%ld\n", gs_event_time(game, event));
	if_print_debug(buffer);
}


/*
 * debug_filter_task()
 * debug_dump_task()
 *
 * Print stuff about tasks.
 */
static sc_bool debug_filter_task(sc_gameref_t game, sc_int task) {
	return task_can_run_task(game, task);
}

static void debug_dump_task(sc_gameref_t game, sc_int task) {
	debug_print_task(game, task);
	if_print_debug_character('\n');

	if (task_can_run_task(game, task))
		if_print_debug("    Runnable");
	else
		if_print_debug("    Not runnable");
	if (gs_task_done(game, task))
		if_print_debug(", Done");
	else
		if_print_debug(", Not done");
	if (gs_task_scored(game, task))
		if_print_debug(", Scored\n");
	else
		if_print_debug(", Not scored\n");
}


/*
 * debug_dump_variable()
 *
 * Print stuff about variables.
 */
static void debug_dump_variable(sc_gameref_t game, sc_int variable) {
	const sc_prop_setref_t bundle = gs_get_bundle(game);
	const sc_var_setref_t vars = gs_get_vars(game);
	sc_vartype_t vt_key[3], vt_rvalue;
	const sc_char *name;
	sc_int var_type;

	debug_print_variable(game, variable);
	if_print_debug_character('\n');

	vt_key[0].string = "Variables";
	vt_key[1].integer = variable;
	vt_key[2].string = "Name";
	name = prop_get_string(bundle, "S<-sis", vt_key);

	if_print_debug("    Value = ");
	if (var_get(vars, name, &var_type, &vt_rvalue)) {
		switch (var_type) {
		case VAR_INTEGER: {
			sc_char buffer[32];

			sprintf(buffer, "%ld", vt_rvalue.integer);
			if_print_debug(buffer);
			break;
		}
		case VAR_STRING:
			debug_print_quoted(vt_rvalue.string);
			break;
		default:
			if_print_debug("[Unknown]");
			break;
		}
	} else
		if_print_debug("[Unknown]");
	if_print_debug_character('\n');
}


/*
 * debug_dump_common()
 *
 * Common handler for iterating dumps of classes.
 */
static void debug_dump_common(sc_gameref_t game, sc_command_t command,
		sc_command_type_t type, sc_int arg1, sc_int arg2) {
	sc_int low = arg1, high = arg2;
	sc_int limit, index_;
	const sc_char *class_;
	sc_bool(*filter_function)(sc_gameref_t, sc_int);
	void (*dumper_function)(sc_gameref_t, sc_int);
	sc_bool printed = FALSE;

	/* Initialize variables to avoid gcc warnings. */
	limit = 0;
	class_ = nullptr;
	filter_function = nullptr;
	dumper_function = nullptr;

	/* Switch to undo game on relevant commands. */
	switch (command) {
	case DEBUG_OLDROOMS:
	case DEBUG_OLDOBJECTS:
	case DEBUG_OLDNPCS:
	case DEBUG_OLDEVENTS:
	case DEBUG_OLDTASKS:
	case DEBUG_OLDVARIABLES:
		if (!game->undo_available) {
			if_print_debug("There is no previous game state to examine.\n");
			return;
		}

		game = game->undo;
		assert(gs_is_game_valid(game));
		break;

	default:
		break;
	}

	/* Demultiplex dump command. */
	switch (command) {
	case DEBUG_ROOMS:
	case DEBUG_OLDROOMS:
		class_ = "Room";
		filter_function = debug_filter_room;
		dumper_function = debug_dump_room;
		limit = gs_room_count(game);
		break;
	case DEBUG_OBJECTS:
	case DEBUG_OLDOBJECTS:
		class_ = "Object";
		filter_function = debug_filter_object;
		dumper_function = debug_dump_object;
		limit = gs_object_count(game);
		break;
	case DEBUG_NPCS:
	case DEBUG_OLDNPCS:
		class_ = "NPC";
		filter_function = debug_filter_npc;
		dumper_function = debug_dump_npc;
		limit = gs_npc_count(game);
		break;
	case DEBUG_EVENTS:
	case DEBUG_OLDEVENTS:
		class_ = "Event";
		filter_function = debug_filter_event;
		dumper_function = debug_dump_event;
		limit = gs_event_count(game);
		break;
	case DEBUG_TASKS:
	case DEBUG_OLDTASKS:
		class_ = "Task";
		filter_function = debug_filter_task;
		dumper_function = debug_dump_task;
		limit = gs_task_count(game);
		break;
	case DEBUG_VARIABLES:
	case DEBUG_OLDVARIABLES:
		class_ = "Variable";
		filter_function = nullptr;
		dumper_function = debug_dump_variable;
		limit = debug_variable_count(game);
		break;
	default:
		sc_fatal("debug_dump_common: invalid command\n");
	}

	/* Normalize to this limit. */
	if (!debug_normalize_arguments(type, &low, &high, limit)) {
		if (limit == 0) {
			if_print_debug("There is nothing of type ");
			debug_print_quoted(class_);
			if_print_debug(" to print.\n");
		} else {
			if_print_debug("Invalid item or range for ");
			debug_print_quoted(class_);
			if (limit == 1)
				if_print_debug("; only 0 is valid.\n");
			else {
				sc_char buffer[32];

				if_print_debug("; valid values are 0 to ");
				sprintf(buffer, "%ld", limit - 1);
				if_print_debug(buffer);
				if_print_debug(".\n");
			}
		}
		return;
	}

	/* Print each item of the class, filtering on query commands. */
	for (index_ = low; index_ <= high; index_++) {
		if (type == COMMAND_QUERY
		        && filter_function && !filter_function(game, index_))
			continue;

		if (printed)
			if_print_debug_character('\n');
		dumper_function(game, index_);
		printed = TRUE;
	}
	if (!printed) {
		if_print_debug("Nothing of type ");
		debug_print_quoted(class_);
		if_print_debug(" is relevant.\nTry \"");
		if_print_debug(class_);
		if_print_debug(" *\" to show all items of this type.\n");
	}
}


/*
 * debug_buffer()
 *
 * Print the current raw printfilter contents.
 */
static void debug_buffer(sc_gameref_t game, sc_command_type_t type) {
	const sc_filterref_t filter = gs_get_filter(game);
	const sc_char *buffer;

	if (type != COMMAND_QUERY) {
		if_print_debug("The Buffer command takes no arguments.\n");
		return;
	}

	buffer = pf_get_buffer(filter);
	if (buffer)
		if_print_debug(buffer);
	else
		if_print_debug("There is no game text buffered.\n");
}


/*
 * debug_print_resource()
 *
 * Helper for debug_resources().
 */
static void debug_print_resource(const sc_resource_t *resource) {
	sc_char buffer[32];

	debug_print_quoted(resource->name);
	if_print_debug(", offset ");
	sprintf(buffer, "%ld", resource->offset);
	if_print_debug(buffer);
	if_print_debug(", length ");
	sprintf(buffer, "%ld", resource->length);
	if_print_debug(buffer);
}


/*
 * debug_resources()
 *
 * Print any active and requested resources.
 */
static void debug_resources(sc_gameref_t game, sc_command_type_t type) {
	sc_bool printed = FALSE;

	if (type != COMMAND_QUERY) {
		if_print_debug("The Resources command takes no arguments.\n");
		return;
	}

	if (game->stop_sound) {
		if_print_debug("Sound stop");
		if (strlen(game->requested_sound.name) > 0)
			if_print_debug(" before new sound");
		if_print_debug(" requested");
		if (game->sound_active)
			if_print_debug(", sound active");
		if_print_debug(".\n");
		printed = TRUE;
	}
	if (!res_compare_resource(&game->requested_sound,
	                          &game->playing_sound)) {
		if_print_debug("Requested Sound ");
		debug_print_resource(&game->requested_sound);
		if_print_debug(".\n");
		printed = TRUE;
	}
	if (!res_compare_resource(&game->requested_graphic,
	                          &game->displayed_graphic)) {
		if_print_debug("Requested Graphic ");
		debug_print_resource(&game->requested_graphic);
		if_print_debug(".\n");
		printed = TRUE;
	}

	if (strlen(game->playing_sound.name) > 0) {
		if_print_debug("Playing Sound ");
		debug_print_resource(&game->playing_sound);
		if_print_debug(".\n");
		printed = TRUE;
	}
	if (strlen(game->displayed_graphic.name) > 0) {
		if_print_debug("Displaying Graphic ");
		debug_print_resource(&game->displayed_graphic);
		if_print_debug(".\n");
		printed = TRUE;
	}

	if (!printed)
		if_print_debug("There is no game resource activity.\n");
}


/*
 * debug_random()
 *
 * Report the PRNG in use, and seed the random number generator to the
 * given value.
 */
static void debug_random(sc_command_type_t type, sc_int new_seed) {
	const sc_char *random_type;
	sc_char buffer[32];

	if (type != COMMAND_ONE && type != COMMAND_QUERY) {
		if_print_debug("The Random command takes either one argument or"
		               " no arguments.\n");
		return;
	}

	random_type = sc_is_congruential_random() ? "congruential" : "platform";

	if (type == COMMAND_QUERY) {
		if_print_debug("The ");
		if_print_debug(random_type);
		if_print_debug(" random number generator is selected.\n");
		return;
	}

	if (new_seed == 0) {
		if_print_debug("The seed value may not be zero.\n");
		return;
	}

	sc_seed_random(new_seed);

	if_print_debug("Set seed ");
	sprintf(buffer, "%ld", new_seed);
	if_print_debug(buffer);
	if_print_debug(" for the ");
	if_print_debug(random_type);
	if_print_debug(" random number generator.\n");
}


/*
 * debug_watchpoint_common()
 *
 * Common handler for setting and clearing watchpoints.
 */
static void debug_watchpoint_common(sc_gameref_t game, sc_command_t command,
		sc_command_type_t type, sc_int arg1, sc_int arg2) {
	const sc_debuggerref_t debug = debug_get_debugger(game);
	sc_int low = arg1, high = arg2;
	sc_int limit, index_;
	const sc_char *class_;
	sc_bool *watchpoints, action;
	sc_char buffer[32];
	assert(debug_is_valid(debug));

	/* Initialize variables to avoid gcc warnings. */
	limit = 0;
	class_ = nullptr;
	watchpoints = nullptr;
	action = FALSE;

	/* Set action to TRUE or FALSE, for setting/clearing watchpoints. */
	switch (command) {
	case DEBUG_WATCHPLAYER:
	case DEBUG_WATCHOBJECTS:
	case DEBUG_WATCHNPCS:
	case DEBUG_WATCHEVENTS:
	case DEBUG_WATCHTASKS:
	case DEBUG_WATCHVARIABLES:
		action = TRUE;
		break;
	case DEBUG_CLEARPLAYER:
	case DEBUG_CLEAROBJECTS:
	case DEBUG_CLEARNPCS:
	case DEBUG_CLEAREVENTS:
	case DEBUG_CLEARTASKS:
	case DEBUG_CLEARVARIABLES:
		action = FALSE;
		break;
	default:
		sc_fatal("debug_watchpoint_common: invalid command\n");
	}

	/* Handle player watchpoint setting. */
	if (command == DEBUG_WATCHPLAYER || command == DEBUG_CLEARPLAYER) {
		if (command == DEBUG_CLEARPLAYER) {
			debug->watch_player = action;
			if_print_debug("Cleared Player watchpoint.\n");
		} else if (type == COMMAND_ONE && arg1 == 0) {
			debug->watch_player = action;
			if_print_debug("Set Player watchpoint.\n");
		} else {
			if (debug->watch_player)
				if_print_debug("Player watchpoint is set.\n");
			else
				if_print_debug("No Player watchpoint is set; to set one, use"
				               " \"Watchplayer 0\".\n");
		}
		return;
	}

	/* Demultiplex watchpoint command. */
	switch (command) {
	case DEBUG_WATCHOBJECTS:
	case DEBUG_CLEAROBJECTS:
		class_ = "Object";
		watchpoints = debug->watch_objects;
		limit = gs_object_count(game);
		break;
	case DEBUG_WATCHNPCS:
	case DEBUG_CLEARNPCS:
		class_ = "NPC";
		watchpoints = debug->watch_npcs;
		limit = gs_npc_count(game);
		break;
	case DEBUG_WATCHEVENTS:
	case DEBUG_CLEAREVENTS:
		class_ = "Event";
		watchpoints = debug->watch_events;
		limit = gs_event_count(game);
		break;
	case DEBUG_WATCHTASKS:
	case DEBUG_CLEARTASKS:
		class_ = "Task";
		watchpoints = debug->watch_tasks;
		limit = gs_task_count(game);
		break;
	case DEBUG_WATCHVARIABLES:
	case DEBUG_CLEARVARIABLES:
		class_ = "Variable";
		watchpoints = debug->watch_variables;
		limit = debug_variable_count(game);
		break;
	default:
		sc_fatal("debug_watchpoint_common: invalid command\n");
	}

	/* Normalize to this limit. */
	if (!debug_normalize_arguments(type, &low, &high, limit)) {
		if (limit == 0) {
			if_print_debug("There is nothing of type ");
			debug_print_quoted(class_);
			if_print_debug(" to watch.\n");
		} else {
			if_print_debug("Invalid item or range for ");
			debug_print_quoted(class_);
			if (limit == 1)
				if_print_debug("; only 0 is valid.\n");
			else {
				if_print_debug("; valid values are 0 to ");
				sprintf(buffer, "%ld", limit - 1);
				if_print_debug(buffer);
				if_print_debug(".\n");
			}
		}
		return;
	}

	/* On query, search the array for set flags, and print out. */
	if (type == COMMAND_QUERY) {
		sc_bool printed = FALSE;

		/* Scan for set watchpoints, and list each found. */
		for (index_ = low; index_ <= high; index_++) {
			if (watchpoints[index_]) {
				if (!printed) {
					if_print_debug("Watchpoints are set for ");
					if_print_debug(class_);
					if_print_debug(" { ");
				}
				sprintf(buffer, "%ld", index_);
				if_print_debug(buffer);
				if_print_debug_character(' ');
				printed = TRUE;
			}
		}
		if (printed)
			if_print_debug("}.\n");
		else {
			if_print_debug("No ");
			if_print_debug(class_);
			if_print_debug(" watchpoints are set.\n");
		}
		return;
	}

	/*
	 * For non-queries, set watchpoint flags as defined in action for
	 * the range determined, and print confirmation.
	 */
	for (index_ = low; index_ <= high; index_++)
		watchpoints[index_] = action;

	if (action)
		if_print_debug("Set ");
	else
		if_print_debug("Cleared ");
	sprintf(buffer, "%ld ", high - low + 1);
	if_print_debug(buffer);
	if_print_debug(class_);
	if (high == low)
		if_print_debug(" watchpoint.\n");
	else
		if_print_debug(" watchpoints.\n");
}


/*
 * debug_watchall_common()
 *
 * Common handler to list out and clear all set watchpoints at a stroke.
 */
static void debug_watchall_common(sc_gameref_t game, sc_command_t command, sc_command_type_t type) {
	const sc_debuggerref_t debug = debug_get_debugger(game);
	assert(debug_is_valid(debug));

	if (type != COMMAND_QUERY) {
		if (command == DEBUG_WATCHALL)
			if_print_debug("The Watchall command takes no arguments.\n");
		else
			if_print_debug("The Clearall command takes no arguments.\n");
		return;
	}

	/* Query all set watchpoints using common watchpoint handler... */
	if (command == DEBUG_WATCHALL) {
		debug_watchpoint_common(game,
		                        DEBUG_WATCHPLAYER, COMMAND_QUERY, 0, 0);
		debug_watchpoint_common(game,
		                        DEBUG_WATCHOBJECTS, COMMAND_QUERY, 0, 0);
		debug_watchpoint_common(game,
		                        DEBUG_WATCHNPCS, COMMAND_QUERY, 0, 0);
		debug_watchpoint_common(game,
		                        DEBUG_WATCHEVENTS, COMMAND_QUERY, 0, 0);
		debug_watchpoint_common(game,
		                        DEBUG_WATCHTASKS, COMMAND_QUERY, 0, 0);
		debug_watchpoint_common(game,
		                        DEBUG_WATCHVARIABLES, COMMAND_QUERY, 0, 0);
		return;
	}

	/* ...but reset all the fast way, with memset(). */
	assert(command == DEBUG_CLEARALL);
	debug->watch_player = FALSE;
	memset(debug->watch_objects, FALSE,
	       gs_object_count(game) * sizeof(*debug->watch_objects));
	memset(debug->watch_npcs, FALSE,
	       gs_npc_count(game) * sizeof(*debug->watch_npcs));
	memset(debug->watch_events, FALSE,
	       gs_event_count(game) * sizeof(*debug->watch_events));
	memset(debug->watch_tasks, FALSE,
	       gs_task_count(game) * sizeof(*debug->watch_tasks));
	memset(debug->watch_variables, FALSE,
	       debug_variable_count(game) * sizeof(*debug->watch_variables));
	if_print_debug("Cleared all watchpoints.\n");
}


/*
 * debug_compare_object()
 *
 * Compare two objects, and return TRUE if the same.
 */
static sc_bool debug_compare_object(sc_gameref_t from, sc_gameref_t with, sc_int object) {
	const sc_objectstate_t *from_object = from->objects + object;
	const sc_objectstate_t *with_object = with->objects + object;

	return from_object->unmoved == with_object->unmoved
	       && from_object->static_unmoved == with_object->static_unmoved
	       && from_object->position == with_object->position
	       && from_object->parent == with_object->parent
	       && from_object->openness == with_object->openness
	       && from_object->state == with_object->state
	       && from_object->seen == with_object->seen;
}


/*
 * debug_compare_npc()
 *
 * Compare two NPCs, and return TRUE if the same.
 */
static sc_bool debug_compare_npc(sc_gameref_t from, sc_gameref_t with, sc_int npc) {
	const sc_npcstate_t *from_npc = from->npcs + npc;
	const sc_npcstate_t *with_npc = with->npcs + npc;

	if (from_npc->walkstep_count != with_npc->walkstep_count)
		sc_fatal("debug_compare_npc: walkstep count error\n");

	return from_npc->location == with_npc->location
	       && from_npc->position == with_npc->position
	       && from_npc->parent == with_npc->parent
	       && from_npc->seen == with_npc->seen
	       && memcmp(from_npc->walksteps, with_npc->walksteps,
	                 from_npc->walkstep_count
	                 * sizeof(*from_npc->walksteps)) == 0;
}


/*
 * debug_compare_event()
 *
 * Compare two events, and return TRUE if the same.
 */
static sc_bool debug_compare_event(sc_gameref_t from, sc_gameref_t with, sc_int event) {
	const sc_eventstate_t *from_event = from->events + event;
	const sc_eventstate_t *with_event = with->events + event;

	return from_event->state == with_event->state
	       && from_event->time == with_event->time;
}


/*
 * debug_compare_task()
 *
 * Compare two tasks, and return TRUE if the same.
 */
static sc_bool debug_compare_task(sc_gameref_t from, sc_gameref_t with, sc_int task) {
	const sc_taskstate_t *from_task = from->tasks + task;
	const sc_taskstate_t *with_task = with->tasks + task;

	return from_task->done == with_task->done
	       && from_task->scored == with_task->scored;
}


/*
 * debug_compare_variable()
 *
 * Compare two variables, and return TRUE if the same.
 */
static sc_bool debug_compare_variable(sc_gameref_t from, sc_gameref_t with, sc_int variable) {
	const sc_prop_setref_t bundle = from->bundle;
	const sc_var_setref_t from_var = from->vars;
	const sc_var_setref_t with_var = with->vars;
	sc_vartype_t vt_key[3], vt_rvalue, vt_rvalue2;
	const sc_char *name;
	sc_int var_type, var_type2;
	sc_bool equal = FALSE;
	vt_rvalue.voidp = vt_rvalue2.voidp = nullptr;

	if (from->bundle != with->bundle)
		sc_fatal("debug_compare_variable: property sharing malfunction\n");

	vt_key[0].string = "Variables";
	vt_key[1].integer = variable;
	vt_key[2].string = "Name";
	name = prop_get_string(bundle, "S<-sis", vt_key);

	if (!var_get(from_var, name, &var_type, &vt_rvalue)
	        || !var_get(with_var, name, &var_type2, &vt_rvalue2))
		sc_fatal("debug_compare_variable: can't find variable %s\n", name);
	else if (var_type != var_type2)
		sc_fatal("debug_compare_variable: variable type mismatch %s\n", name);

	switch (var_type) {
	case VAR_INTEGER:
		equal = (vt_rvalue.integer == vt_rvalue2.integer);
		break;
	case VAR_STRING:
		equal = !strcmp(vt_rvalue.string, vt_rvalue2.string);
		break;
	default:
		sc_fatal("debug_compare_variable:"
		         " invalid variable type, %ld\n", var_type);
	}

	return equal;
}


/*
 * debug_check_class()
 *
 * Central handler for checking watchpoints.  Compares a number of items
 * of a class using the comparison function given, where indicated by a
 * watchpoints flags array.  Prints entries that differ, and returns TRUE
 * if any differed.
 */
static sc_bool debug_check_class(sc_gameref_t from, sc_gameref_t with, const sc_char *class_,
		sc_int class_count, const sc_bool *watchpoints,
		sc_bool(*const compare_function) (sc_gameref_t, sc_gameref_t, sc_int)) {
	sc_int index_;
	sc_bool triggered = FALSE;

	/*
	 * Scan the watchpoints array for set watchpoints, comparing classes
	 * where the watchpoint flag is set.
	 */
	for (index_ = 0; index_ < class_count; index_++) {
		if (!watchpoints[index_])
			continue;

		if (!compare_function(from, with, index_)) {
			sc_char buffer[32];

			if (!triggered) {
				if_print_debug("--- ");
				if_print_debug(class_);
				if_print_debug(" watchpoint triggered { ");
			}
			sprintf(buffer, "%ld ", index_);
			if_print_debug(buffer);
			triggered = TRUE;
		}
	}
	if (triggered)
		if_print_debug("}.\n");

	/* Return TRUE if anything differed. */
	return triggered;
}


/*
 * debug_check_watchpoints()
 *
 * Checks the game against the undo game for all set watchpoints.  Returns
 * TRUE if any triggered, FALSE if none (or if the undo game isn't available,
 * in which case no check is possible).
 */
static sc_bool debug_check_watchpoints(sc_gameref_t game) {
	const sc_debuggerref_t debug = debug_get_debugger(game);
	const sc_gameref_t undo = game->undo;
	sc_bool triggered;
	assert(debug_is_valid(debug) && gs_is_game_valid(undo));

	/* If no undo is present, no check is possible. */
	if (!game->undo_available)
		return FALSE;

	/* Check first for player watchpoint. */
	triggered = FALSE;
	if (debug->watch_player) {
		if (gs_playerroom(game) != gs_playerroom(undo)
		        || gs_playerposition(game) != gs_playerposition(undo)
		        || gs_playerparent(game) != gs_playerparent(undo)) {
			if_print_debug("--- Player watchpoint triggered.\n");
			triggered |= TRUE;
		}
	}

	/* Now check other classes of watchpoint. */
	triggered |= debug_check_class(game, undo,
	                               "Object", gs_object_count(game),
	                               debug->watch_objects, debug_compare_object);
	triggered |= debug_check_class(game, undo,
	                               "NPC", gs_npc_count(game),
	                               debug->watch_npcs, debug_compare_npc);
	triggered |= debug_check_class(game, undo,
	                               "Event", gs_event_count(game),
	                               debug->watch_events, debug_compare_event);
	triggered |= debug_check_class(game, undo,
	                               "Task", gs_task_count(game),
	                               debug->watch_tasks, debug_compare_task);
	triggered |= debug_check_class(game, undo,
	                               "Variable", debug_variable_count(game),
	                               debug->watch_variables,
	                               debug_compare_variable);

	return triggered;
}


/*
 * debug_parse_command()
 *
 * Given a debugging command string, try to parse it and return the
 * appropriate command and its arguments.  Returns DEBUG_NONE if the parse
 * fails.
 */
static sc_command_t debug_parse_command(const sc_char *command_string,
		sc_command_type_t *type, sc_int *arg1, sc_int *arg2, sc_command_t *help_topic) {
	sc_command_t return_command;
	sc_command_type_t return_type;
	sc_int val1, val2, converted, matches;
	sc_char *help, *string, junk, wildcard;
	sc_bool is_help, is_parsed, is_wildcard;
	const sc_strings_t *entry;

	/* Allocate temporary strings long enough to take a copy of the input. */
	string = (sc_char *)sc_malloc(strlen(command_string) + 1);
	help = (sc_char *)sc_malloc(strlen(command_string) + 1);

	/*
	 * Parse the input line, in a very simplistic fashion.  The argument count
	 * is one less than sscanf converts.
	 */
	is_parsed = is_wildcard = is_help = FALSE;
	val1 = val2 = 0;
	converted = sscanf(command_string, " %s %s %c", help, string, &junk);
	if (converted == 2 && sc_strcasecmp(help, "help") == 0) {
		is_help = TRUE;
		is_parsed = TRUE;
	}
	sc_free(help);
	if (!is_parsed) {
		converted = sscanf(command_string,
		                   " %s %ld to %ld %c", string, &val1, &val2, &junk);
		if (converted != 3)
			converted = sscanf(command_string,
			                   " %s %ld - %ld %c", string, &val1, &val2, &junk);
		if (converted != 3)
			converted = sscanf(command_string,
			                   " %s %ld .. %ld %c", string, &val1, &val2, &junk);
		if (converted != 3)
			converted = sscanf(command_string,
			                   " %s %ld %ld %c", string, &val1, &val2, &junk);
		is_parsed |= converted == 3;
	}
	if (!is_parsed) {
		converted = sscanf(command_string,
		                   " %s %ld %c", string, &val1, &junk);
		is_parsed |= converted == 2;
	}
	if (!is_parsed) {
		converted = sscanf(command_string,
		                   " %s %c %c", string, &wildcard, &junk);
		if (converted == 2 && wildcard == '*') {
			is_wildcard = TRUE;
			is_parsed = TRUE;
		} else
			is_parsed |= converted == 1;
	}
	if (!is_parsed) {
		if_print_debug("Invalid debug command.");
		if_print_debug("  Type 'help' for a list of valid commands.\n");
		sc_free(string);
		return DEBUG_NONE;
	}

	/* Decide on a command type based on the parse. */
	if (is_wildcard)
		return_type = COMMAND_ALL;
	else if (converted == 3)
		return_type = COMMAND_RANGE;
	else if (converted == 2)
		return_type = COMMAND_ONE;
	else
		return_type = COMMAND_QUERY;

	/*
	 * Find the first unambiguous command matching the string.  If none,
	 * return DEBUG_NONE.
	 */
	matches = 0;
	return_command = DEBUG_NONE;
	for (entry = DEBUG_COMMANDS; entry->command_string; entry++) {
		if (sc_strncasecmp(string, entry->command_string, strlen(string)) == 0) {
			matches++;
			return_command = entry->command;
		}
	}
	if (matches != 1) {
		if (matches > 1)
			if_print_debug("Ambiguous debug command.");
		else
			if_print_debug("Unrecognized debug command.");
		if_print_debug("  Type 'help' for a list of valid commands.\n");
		sc_free(string);
		return DEBUG_NONE;
	}

	/* Done with temporary command parse area. */
	sc_free(string);

	/*
	 * Return the command type, arguments, and the debugging command.  For help
	 * <topic>, the command is help, with the command on which help requested
	 * in *help_topic.  All clear, then?
	 */
	*type = return_type;
	*arg1 = val1;
	*arg2 = val2;
	*help_topic = is_help ? return_command : DEBUG_NONE;
	return is_help ? DEBUG_HELP : return_command;
}


/*
 * debug_dispatch()
 *
 * Dispatch a debugging command to the appropriate handler.
 */
static void debug_dispatch(sc_gameref_t game, sc_command_t command, sc_command_type_t type,
		sc_int arg1, sc_int arg2, sc_command_t help_topic) {
	/* Demultiplex debugging command, and call handlers. */
	switch (command) {
	case DEBUG_HELP:
		debug_help(help_topic);
		break;
	case DEBUG_BUFFER:
		debug_buffer(game, type);
		break;
	case DEBUG_RESOURCES:
		debug_resources(game, type);
		break;
	case DEBUG_RANDOM:
		debug_random(type, arg1);
		break;
	case DEBUG_GAME:
		debug_game(game, type);
		break;
	case DEBUG_PLAYER:
	case DEBUG_OLDPLAYER:
		debug_player(game, command, type);
		break;
	case DEBUG_ROOMS:
	case DEBUG_OBJECTS:
	case DEBUG_NPCS:
	case DEBUG_EVENTS:
	case DEBUG_TASKS:
	case DEBUG_VARIABLES:
	case DEBUG_OLDROOMS:
	case DEBUG_OLDOBJECTS:
	case DEBUG_OLDNPCS:
	case DEBUG_OLDEVENTS:
	case DEBUG_OLDTASKS:
	case DEBUG_OLDVARIABLES:
		debug_dump_common(game, command, type, arg1, arg2);
		break;
	case DEBUG_WATCHPLAYER:
	case DEBUG_WATCHOBJECTS:
	case DEBUG_WATCHNPCS:
	case DEBUG_WATCHEVENTS:
	case DEBUG_WATCHTASKS:
	case DEBUG_WATCHVARIABLES:
	case DEBUG_CLEARPLAYER:
	case DEBUG_CLEAROBJECTS:
	case DEBUG_CLEARNPCS:
	case DEBUG_CLEAREVENTS:
	case DEBUG_CLEARTASKS:
	case DEBUG_CLEARVARIABLES:
		debug_watchpoint_common(game, command, type, arg1, arg2);
		break;
	case DEBUG_WATCHALL:
	case DEBUG_CLEARALL:
		debug_watchall_common(game, command, type);
		break;
	case DEBUG_NONE:
		break;
	default:
		sc_fatal("debug_dispatch: invalid debug command\n");
	}
}


/*
 * debug_dialog()
 *
 * Create a small debugging dialog with the user.
 */
static void debug_dialog(CONTEXT, sc_gameref_t game) {
	const sc_filterref_t filter = gs_get_filter(game);
	const sc_debuggerref_t debug = debug_get_debugger(game);
	const sc_var_setref_t vars = gs_get_vars(game);
	assert(debug_is_valid(debug));

	/*
	 * Note elapsed seconds, so time stands still while debugging, and clear
	 * any pending game quit left over from prior dialogs (just in case).
	 */
	debug->elapsed_seconds = var_get_elapsed_seconds(vars);
	debug->quit_pending = FALSE;

	/* Handle debug commands until debugger quit or game quit. */
	while (TRUE) {
		sc_char buffer[DEBUG_BUFFER_SIZE];
		sc_command_t command, help_topic;
		sc_command_type_t type;
		sc_int arg1, arg2;

		/* Get a debugging command string from the user. */
		do {
			if_read_debug(buffer, sizeof(buffer));

			if (g_vm->shouldQuit())
				return;
		} while (sc_strempty(buffer));

		/* Parse the command read, and handle dialog exit commands. */
		command = debug_parse_command(buffer,
		                              &type, &arg1, &arg2, &help_topic);
		if (command == DEBUG_CONTINUE || command == DEBUG_STEP) {
			if (!game->is_running) {
				if_print_debug("The game is no longer running.\n");
				continue;
			}

			debug->single_step = (command == DEBUG_STEP);
			break;
		} else if (command == DEBUG_QUIT) {
			/*
			 * If the game is not running, we can't halt it, and we don't need
			 * to confirm the quit (either the player "quit" or the game
			 * completed), so leave the dialog loop.
			 */
			if (!game->is_running)
				break;

			/*
			 * The game is still running, so confirm quit by requiring a repeat,
			 * or if this is the confirmation, force the game to a halt.
			 */
			if (!debug->quit_pending) {
				if_print_debug("Use 'quit' again to confirm, or another"
				               " debugger command to cancel.\n");
				debug->quit_pending = TRUE;
				continue;
			}

			/* Drop printfilter contents and quit the game. */
			pf_empty(filter);
			CALL1(run_quit, game);

			/* Just in case... */
			if_print_debug("Unable to quit from the game.  Sorry.\n");
			continue;
		}

		/* Dispatch the remaining debugging commands, and clear quit flag. */
		debug_dispatch(game, command, type, arg1, arg2, help_topic);
		debug->quit_pending = FALSE;
	}

	/* Restart time, and clear any pending game quit. */
	var_set_elapsed_seconds(vars, debug->elapsed_seconds);
	debug->quit_pending = FALSE;
}


/*
 * debug_run_command()
 *
 * Handle a single debugging command line from the outside world.  Returns
 * TRUE if valid, FALSE if invalid (parse failed, not understood).
 */
sc_bool debug_run_command(sc_gameref_t game, const sc_char *debug_command) {
	const sc_debuggerref_t debug = debug_get_debugger(game);
	sc_command_t command, help_topic;
	sc_command_type_t type;
	sc_int arg1, arg2;

	/* If debugging disallowed (not initialized), refuse the call. */
	if (debug) {
		/*
		 * Parse the command string passed in, and return FALSE if the parse
		 * fails, or if it returns DEBUG_CONTINUE, DEBUG_STEP, or DEBUG_QUIT,
		 * none of which make any sense in this context.
		 */
		command = debug_parse_command(debug_command,
		                              &type, &arg1, &arg2, &help_topic);
		if (command == DEBUG_NONE
		        || command == DEBUG_CONTINUE || command == DEBUG_STEP
		        || command == DEBUG_QUIT)
			return FALSE;

		/* Dispatch the remaining debugging commands, return successfully. */
		debug_dispatch(game, command, type, arg1, arg2, help_topic);
		return TRUE;
	}

	return FALSE;
}


/*
 * debug_cmd_debugger()
 *
 * Called by the run main loop on user "debug" command request.  Prints
 * a polite refusal if debugging is not enabled, otherwise runs a debugging
 * dialog.  Uses if_print_string() as this isn't debug output.
 */
sc_bool debug_cmd_debugger(sc_gameref_t game) {
	const sc_debuggerref_t debug = debug_get_debugger(game);
	Context context;

	/* If debugging disallowed (not initialized), ignore the call. */
	if (debug) {
		debug_dialog(context, game);
	} else {
		if_print_string("SCARE's game debugger is not enabled.  Sorry.\n");
	}
	/*
	 * Set as administrative command, so as not to consume a game turn, and
	 * return successfully.
	 */
	game->is_admin = TRUE;
	return TRUE;
}


/*
 * debug_game_started()
 * debug_game_ended()
 *
 * The first is called on entry to the game main loop, and gives us a chance
 * to look at things before any turns are run, and to set watchpoints to
 * catch things in games that use catch-all command tasks on startup (The PK
 * Girl, for example).
 *
 * The second is called on exit from the game, and may make a final sweep for
 * watchpoints and offer the debug dialog one last time.
 */
void debug_game_started(CONTEXT, sc_gameref_t game) {
	const sc_debuggerref_t debug = debug_get_debugger(game);

	/* If debugging disallowed (not initialized), ignore the call. */
	if (debug) {
		/* Starting a new game, or a restore or undo of an old one? */
		if (!gs_room_seen(game, gs_playerroom(game))) {
			/*
			 * It's a new game starting or restarting.  Print a banner, and
			 * run the debugger dialog.
			 */
			if_print_debug("\n--- SCARE " SCARE_VERSION SCARE_PATCH_LEVEL
			               " Game Debugger\n"
			               "--- Type 'help' for a list of commands.\n");
			CALL1(debug_dialog, game);
		} else {
			/*
			 * It's a restore or undo through memos, so run the dialog only if
			 * single-stepping; no need to check watchpoints for this case as
			 * none can be set -- no undo.
			 */
			if (debug->single_step) {
				CALL1(debug_dialog, game);
			}
		}
	}
}

void debug_game_ended(CONTEXT, sc_gameref_t game) {
	const sc_debuggerref_t debug = debug_get_debugger(game);

	/* If debugging disallowed (not initialized), ignore the call. */
	if (debug) {
		/*
		 * Using our carnal knowledge of the run main loop, we know here that
		 * if the loop exited with do_restart or do_restore, we'll get a call to
		 * debug_game_start() when the loop restarts.  So in this case, ignore
		 * the call (even if single stepping).
		 */
		if (game->do_restart || game->do_restore)
			return;

		/*
		 * Check for any final watchpoints, and print a message describing why
		 * we're here.  Suppress the check for watchpoints if the user exited
		 * the game, as it'll only be a repeat of any found last turn update.
		 */
		if (!game->is_running) {
			if (game->has_completed) {
				debug_check_watchpoints(game);
				if_print_debug("\n--- The game has completed.\n");
			} else
				if_print_debug("\n--- The game has exited.\n");
		} else {
			debug_check_watchpoints(game);
			if_print_debug("\n--- The game is still running!\n");
		}

		/* Run a final dialog. */
		CALL1(debug_dialog, game);
	}
}


/*
 * debug_turn_update()
 *
 * Called after each turn by the main game loop.  Checks for any set
 * watchpoints, and triggers a debug dialog when any fire.
 */
void debug_turn_update(CONTEXT, sc_gameref_t game) {
	const sc_debuggerref_t debug = debug_get_debugger(game);

	/* If debugging disallowed (not initialized), ignore the call. */
	if (debug) {
		/*
		 * Again using carnal knowledge of the run main loop, if we're in
		 * mid-wait, ignore the call.  Also, ignore the call if the game is
		 * no longer running, as we'll see a debug_game_ended() call come
		 * along to handle that.
		 */
		if (game->waitcounter > 0 || !game->is_running)
			return;

		/*
		 * Run debugger dialog if any watchpoints triggered, or if single
		 * stepping (even if none triggered).
		 */
		if (debug_check_watchpoints(game) || debug->single_step) {
			CALL1(debug_dialog, game);
		}
	}
}


/*
 * debug_set_enabled()
 * debug_get_enabled()
 *
 * Enable/disable debugging, and return debugging status.  Debugging is
 * enabled when there is a debugger reference in the game, and disabled
 * when it's NULL -- that's the flag.  To avoid lugging about all the
 * watchpoint memory with a game, debugger data is allocated on enabling,
 * and free'd on disabling; as a result, any set watchpoints are lost on
 * disabling.
 */
void debug_set_enabled(sc_gameref_t game, sc_bool enable) {
	const sc_debuggerref_t debug = debug_get_debugger(game);

	/*
	 * If enabling and not already enabled, or disabling and not already
	 * disabled, either initialize or finalize..
	 */
	if ((enable && !debug) || (!enable && debug)) {
		/* Initialize or finalize debugging, as appropriate. */
		if (enable)
			debug_initialize(game);
		else
			debug_finalize(game);
	}
}

sc_bool debug_get_enabled(sc_gameref_t game) {
	const sc_debuggerref_t debug = debug_get_debugger(game);

	return debug != nullptr;
}

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