/* 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/>.
 *
 */

/*
 * Based on ScottFree interpreter version 1.14 developed by Swansea
 * University Computer Society without disassembly of any other game
 * drivers, only of game databases as permitted by EEC law (for purposes
 * of compatibility).
 *
 * Licensed under GPLv2
 *
 * https://github.com/angstsmurf/spatterlight/tree/master/terps/scott
 */

#include "common/config-manager.h"
#include "common/translation.h"
#include "common/ustr.h"
#include "glk/quetzal.h"
#include "glk/scott/scott.h"
#include "glk/scott/command_parser.h"
#include "glk/scott/definitions.h"
#include "glk/scott/load_game.h"
#include "glk/scott/game_info.h"
#include "glk/scott/globals.h"
#include "glk/scott/hulk.h"
#include "glk/scott/layout_text.h"
#include "glk/scott/line_drawing.h"
#include "glk/scott/saga_draw.h"
#include "glk/scott/restore_state.h"
#include "glk/scott/robin_of_sherwood.h"
#include "glk/scott/gremlins.h"
#include "glk/scott/seas_of_blood.h"
#include "glk/scott/game_specific.h"
#include "glk/scott/ti99_4a_terp.h"

namespace Glk {
namespace Scott {

Scott *g_scott;

Scott::Scott(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
	g_scott = this;
	Common::fill(&_nounText[0], &_nounText[16], '\0');
}

void Scott::runGame() {
	int vb, no;
	initialize();

	glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Proportional, 0);
	glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Indentation, 20);
	glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_ParaIndentation, 20);
	glk_stylehint_set(wintype_TextBuffer, style_Preformatted, stylehint_Justification, stylehint_just_Centered);

	_G(_bottomWindow) = glk_window_open(0, 0, 0, wintype_TextBuffer, GLK_BUFFER_ROCK);
	if (_G(_bottomWindow) == nullptr)
		glk_exit();
	glk_set_window(_G(_bottomWindow));

	for (int i = 0; i < MAX_SYSMESS; i++) {
		_G(_sys)[i] = g_sysDict[i];
	}

	const char **dictpointer;

	if (_G(_options) & YOUARE)
		dictpointer = g_sysDict;
	else
		dictpointer = g_sysDictIAm;

	for (int i = 0; i < MAX_SYSMESS && dictpointer[i] != nullptr; i++) {
		_G(_sys)[i] = dictpointer[i];
	}

	loadGameFile(&_gameFile);

	if (CURRENT_GAME == SCOTTFREE)
		loadDatabase(&_gameFile, (_G(_options) & DEBUGGING) ? 1 : 0);

	if (!CURRENT_GAME)
		fatal("Unsupported game!");

	if (CURRENT_GAME != SCOTTFREE && CURRENT_GAME != TI994A) {
		_G(_options) |= SPECTRUM_STYLE;
		_splitScreen = 1;
	} else {
		if (CURRENT_GAME != TI994A)
			_G(_options) |= TRS80_STYLE;
		_splitScreen = 1;
	}

	if (!_G(_titleScreen).empty()) {
		if (_splitScreen)
			printTitleScreenGrid();
		else
			printTitleScreenBuffer();
	}

	if (_G(_options) & TRS80_STYLE) {
		_topWidth = 64;
		_topHeight = 11;
	} else {
		_topWidth = 80;
		_topHeight = 10;
	}

	if (CURRENT_GAME == TI994A) {
		display(_G(_bottomWindow), "In this adventure, you may abbreviate any word \
				by typing its first %d letters, and directions by typing \
				one letter.\n\nDo you want to restore previously saved game?\n",
				_G(_gameHeader)->_wordLength);
		if (yesOrNo())
			loadGame();
		clearScreen();
	}

	openTopWindow();

	if (CURRENT_GAME == SCOTTFREE)
		output("ScummVM support adapted from Scott Free, A Scott Adams game driver in C.\n\n");

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

	_G(_initialState) = saveCurrentState();

	while (!shouldQuit()) {
		glk_tick();

		if (_G(_shouldRestart))
			restartGame();

		if (!_G(_stopTime))
			performActions(0, 0);

		if (shouldQuit())
			break;

		if (!(_G(_currentCommand) && _G(_currentCommand)->_allFlag && !(_G(_currentCommand)->_allFlag & LASTALL))) {
			_printLookToTranscript = _G(_shouldLookInTranscript);
			look();
			_printLookToTranscript = _G(_shouldLookInTranscript) = 0;
			if (!_G(_stopTime) && !_G(_shouldRestart))
				saveUndo();
		}

		if (_G(_shouldRestart))
			continue;

		if (getInput(&vb, &no) == 1)
			continue;
		if (g_vm->shouldQuit())
			break;

		switch (performActions(vb, no)) {
		case ER_RAN_ALL_LINES_NO_MATCH:
			if (!recheckForExtraCommand()) {
				output(_G(_sys)[I_DONT_UNDERSTAND]);
				freeCommands();
			}
			break;
		case ER_RAN_ALL_LINES:
			output(_G(_sys)[YOU_CANT_DO_THAT_YET]);
			freeCommands();
			break;
		default:
			_G(_justStarted) = 0;
		}

		/* Brian Howarth games seem to use -1 for forever */
		if (_G(_items)[LIGHT_SOURCE]._location != DESTROYED && _G(_gameHeader)->_lightTime != -1 && !_G(_stopTime)) {
			_G(_gameHeader)->_lightTime--;
			if (_G(_gameHeader)->_lightTime < 1) {
				_G(_bitFlags) |= (1 << LIGHTOUTBIT);
				if (_G(_items)[LIGHT_SOURCE]._location == CARRIED || _G(_items)[LIGHT_SOURCE]._location == MY_LOC) {
					output(_G(_sys)[LIGHT_HAS_RUN_OUT]);
				}
				if ((_G(_options) & PREHISTORIC_LAMP) || (_G(_game)->_subType & MYSTERIOUS) || CURRENT_GAME == TI994A)
					_G(_items)[LIGHT_SOURCE]._location = DESTROYED;
			} else if (_G(_gameHeader)->_lightTime < 25) {
				if (_G(_items)[LIGHT_SOURCE]._location == CARRIED || _G(_items)[LIGHT_SOURCE]._location == MY_LOC) {
					if ((_G(_options) & SCOTTLIGHT) || (_G(_game)->_subType & MYSTERIOUS)) {
						display(_G(_bottomWindow), "%s %d %s\n", _G(_sys)[LIGHT_RUNS_OUT_IN].c_str(), _G(_gameHeader)->_lightTime, _G(_sys)[TURNS].c_str());
					} else {
						if (_G(_gameHeader)->_lightTime % 5 == 0)
							output(_G(_sys)[LIGHT_GROWING_DIM].c_str());
					}
				}
			}
		}
		if (_G(_stopTime))
			_G(_stopTime)--;
	}
}

void Scott::initialize() {
	if (ConfMan.hasKey("YOUARE")) {
		if (ConfMan.getBool("YOUARE"))
			_G(_options) |= YOUARE;
		else
			_G(_options) &= ~YOUARE;
	}
	if (gDebugLevel > 0)
		_G(_options) |= DEBUGGING;
	if (ConfMan.hasKey("SCOTTLIGHT") && ConfMan.getBool("SCOTTLIGHT"))
		_G(_options) |= SCOTTLIGHT;
	if (ConfMan.hasKey("TRS80_STYLE") && ConfMan.getBool("TRS80_STYLE"))
		_G(_options) |= TRS80_STYLE;
	if (ConfMan.hasKey("PREHISTORIC_LAMP") && ConfMan.getBool("PREHISTORIC_LAMP"))
		_G(_options) |= PREHISTORIC_LAMP;
}

void Scott::display(winid_t w, const char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	Common::String msg = Common::String::vformat(fmt, ap);
	va_end(ap);

	glk_put_string_stream(glk_window_get_stream(w), msg.c_str());
	if (_G(_transcript))
		glk_put_string_stream(_G(_transcript), msg.c_str());
}

void Scott::display_u32_internal(winid_t w, const Common::U32String *fmt, ...) {
	Common::U32String msg;

	va_list ap;

	va_start(ap, fmt);
	Common::U32String::vformat(fmt->begin(), fmt->end(), msg, ap);
	va_end(ap);

	glk_put_string_stream_uni(glk_window_get_stream(w), msg.u32_str());
	if (_G(_transcript))
		glk_put_string_stream_uni(_G(_transcript), msg.u32_str());
}

void Scott::updateSettings() {
	if (drawingVector())
		glk_request_timer_events(20);

	PaletteType previousPal = _G(_palChosen);
	if (_G(_options) & FORCE_PALETTE_ZX)
		_G(_palChosen) = ZXOPT;
	else if (_G(_options) & FORCE_PALETTE_C64) {
		if (_G(_game)->_pictureFormatVersion == 99)
			_G(_palChosen) = C64A;
		else
			_G(_palChosen) = C64B;
	} else
		_G(_palChosen) = _G(_game)->_palette;
	if (_G(_palChosen) != previousPal) {
		definePalette();
		if (_G(_vectorState) != NO_VECTOR_IMAGE)
			drawSomeVectorPixels(1);
	}
}

uint Scott::getRandomNumber(uint max) {
	return _random.getRandomNumber(max);
}

void Scott::updates(event_t ev) {
	if (ev.type == evtype_Arrange) {
		updateSettings();

		_G(_vectorState) = NO_VECTOR_IMAGE;

		closeGraphicsWindow();
		openGraphicsWindow();

		if (_splitScreen) {
			look();
		}
	} else if (ev.type == evtype_Timer) {
		switch (_G(_game->_type)) {
		case SHERWOOD_VARIANT:
			updateRobinOfSherwoodAnimations();
			break;
		case GREMLINS_VARIANT:
			updateGremlinsAnimations();
			break;
		case SECRET_MISSION_VARIANT:
			// TODO
			// UpdateSecretAnimations();
			break;
		default:
			if (_G(_game)->_pictureFormatVersion == 99 && drawingVector())
				drawSomeVectorPixels((_G(_vectorState) == NO_VECTOR_IMAGE));
			break;
		}
	}
}

void Scott::delay(double seconds) {
	if (_G(_options) & NO_DELAYS)
		return;

	event_t ev;

	if (!glk_gestalt(gestalt_Timer, 0))
		return;

	glk_request_char_event(_G(_bottomWindow));
	glk_cancel_char_event(_G(_bottomWindow));

	if (drawingVector()) {
		do {
			glk_select(&ev);
			updates(ev);
		} while (drawingVector());
		if (_G(_gliSlowDraw))
			seconds = 0.5;
	}

	glk_request_timer_events(1000 * seconds);

	do {
		glk_select(&ev);
		updates(ev);
	} while (ev.type != evtype_Timer);

	glk_request_timer_events(0);
}

void Scott::fatal(const char *x) {
	display(_G(_bottomWindow), "%s\n", x);
	cleanupAndExit();
}

void Scott::clearScreen(void) {
	glk_window_clear(_G(_bottomWindow));
}

bool Scott::randomPercent(uint n) {
	return _random.getRandomNumber(99) < n;
}

int Scott::countCarried(void) {
	int ct = 0;
	int n = 0;
	while (ct <= _G(_gameHeader)->_numItems) {
		if (_G(_items)[ct]._location == CARRIED)
			n++;
		ct++;
	}
	return n;
}

const char *Scott::mapSynonym(int noun) {
	int n = 1;
	const char *tp;
	static char lastword[16]; // Last non synonym
	while (n <= _G(_gameHeader)->_numWords) {
		tp = _G(_nouns)[n].c_str();
		if (*tp == '*')
			tp++;
		else
			Common::strcpy_s(lastword, tp);
		if (n == noun)
			return lastword;
		n++;
	}
	return nullptr;
}

int Scott::matchUpItem(int noun, int loc) {
	const char *word = mapSynonym(noun);
	int ct = 0;

	if (word == nullptr)
		word = _G(_nouns)[noun].c_str();

	while (ct <= _G(_gameHeader)->_numItems) {
		if (!_G(_items)[ct]._autoGet.empty() && _G(_items)[ct]._location == loc &&
			scumm_strnicmp(_G(_items)[ct]._autoGet.c_str(), word, _G(_gameHeader)->_wordLength) == 0)
			return ct;
		ct++;
	}

	return -1;
}

Common::String Scott::readString(Common::SeekableReadStream *f) {
	char tmp[1024];
	int c, nc;
	int ct = 0;
	do {
		c = f->readByte();
	} while (f->pos() < f->size() && Common::isSpace(c));
	if (c != '"') {
		fatal("Initial quote expected");
	}

	for (;;) {
		if (f->pos() >= f->size())
			fatal("EOF in string");

		c = f->readByte();
		if (c == '"') {
			nc = f->readByte();
			if (nc != '"') {
				f->seek(-1, SEEK_CUR);
				break;
			}
		}
		if (c == '`')
			c = '"'; // pdd

		// Ensure a valid Glk newline is sent.
		if (c == '\n')
			tmp[ct++] = 10;
		// Special case: assume CR is part of CRLF in a DOS-formatted file, and ignore it.
		else if (c == 13)
			;
		// Pass only ASCII to Glk; the other reasonable option would be to pass Latin-1,
		// but it's probably safe to assume that Scott Adams games are ASCII only.
		else if ((c >= 32 && c <= 126))
			tmp[ct++] = c;
		else
			tmp[ct++] = '?';
	}

	tmp[ct] = 0;
	return Common::String(tmp);
}

void Scott::loadDatabase(Common::SeekableReadStream *f, bool loud) {
	int unused, ni, na, nw, nr, mc, pr, tr, wl, lt, mn, trm;
	int ct;
	int lo;

	// Load the header
	readInts(f, 12, &unused, &ni, &na, &nw, &nr, &mc, &pr, &tr, &wl, &lt, &mn, &trm);

	_G(_gameHeader)->_numItems = ni;
	_G(_items).resize(ni + 1);
	_G(_gameHeader)->_numActions = na;
	_G(_actions).resize(na + 1);
	_G(_gameHeader)->_numWords = nw;
	_G(_gameHeader)->_wordLength = wl;
	_G(_verbs).resize(nw + 1);
	_G(_nouns).resize(nw + 1);
	_G(_gameHeader)->_numRooms = nr;
	_G(_rooms).resize(nr + 1);
	_G(_gameHeader)->_maxCarry = mc;
	_G(_gameHeader)->_playerRoom = pr;
	_G(_gameHeader)->_treasures = tr;
	_G(_gameHeader)->_lightTime = lt;
	_G(_lightRefill) = lt;
	_G(_gameHeader)->_numMessages = mn;
	_G(_messages).resize(mn + 1);
	_G(_gameHeader)->_treasureRoom = trm;

	// Load the actions
	if (loud)
		debug("Reading %d actions.", na);

	for (int idx = 0; idx < na + 1; ++idx) {
		Action &a = _G(_actions)[idx];
		readInts(f, 8,
				 &a._vocab, &a._condition[0], &a._condition[1], &a._condition[2],
				 &a._condition[3], &a._condition[4], &a._action[0], &a._action[1]);
	}

	if (loud)
		debug("Reading %d word pairs.", nw);
	for (int idx = 0; idx < nw + 1; ++idx) {
		_G(_verbs)[idx] = readString(f);
		_G(_nouns)[idx] = readString(f);
	}

	if (loud)
		debug("Reading %d rooms.", nr);
	for (int idx = 0; idx < nr + 1; ++idx) {
		Room &r = _G(_rooms)[idx];
		readInts(f, 6, &r._exits[0], &r._exits[1], &r._exits[2],
				 &r._exits[3], &r._exits[4], &r._exits[5]);
		r._text = readString(f);
	}

	if (loud)
		debug("Reading %d messages.", mn);
	for (int idx = 0; idx < mn + 1; ++idx)
		_G(_messages)[idx] = readString(f);

	if (loud)
		debug("Reading %d items.", ni);
	for (int idx = 0; idx < ni + 1; ++idx) {
		Item &i = _G(_items)[idx];
		i._text = readString(f);

		const char *p = strchr(i._text.c_str(), '/');
		if (p) {
			i._autoGet = Common::String(p);

			// Some games use // to mean no auto get/drop word!
			if (!i._autoGet.hasPrefix("//") && !i._autoGet.hasPrefix("/*")) {
				i._text = Common::String(i._text.c_str(), p);
				i._autoGet.deleteChar(0);

				const char *t = strchr(i._autoGet.c_str(), '/');
				if (t) {
					i._autoGet = Common::String(i._autoGet.c_str(), t);
				}
			}
		}

		readInts(f, 1, &lo);
		i._location = (unsigned char)lo;
		i._initialLoc = i._location;
	}

	// Skip Comment Strings
	for (int idx = 0; idx < na + 1; ++idx)
		readString(f);

	readInts(f, 1, &ct);
	if (loud)
		debug("Version %d.%02d of Adventure ", ct / 100, ct % 100);
	readInts(f, 1, &ct);

	if (loud)
		debug("%d.\nLoad Complete.\n", ct);
}

void Scott::output(const Common::String &a) {
	if (_saveSlot == -1)
		display(_G(_bottomWindow), "%s", a.c_str());
}

void Scott::output(const Common::U32String &a) {
	if (_saveSlot == -1)
		display(_G(_bottomWindow), Common::U32String("%S"), a.c_str());
}

void Scott::outputNumber(int a) {
	display(_G(_bottomWindow), "%d", a);
}

void Scott::look(void) {
	drawRoomImage();

	if (_splitScreen && _G(_topWindow) == nullptr)
		return;

	char *buf = new char[1000];
	buf = static_cast<char *>(memset(buf, 0, 1000));
	_roomDescriptionStream = glk_stream_open_memory(buf, 1000, filemode_Write, 0);

	Room *r;
	int ct, f;

	if (!_splitScreen) {
		writeToRoomDescriptionStream("\n");
	} else if (_G(_transcript) && _printLookToTranscript) {
		glk_put_char_stream_uni(_G(_transcript), 10);
	}

	if ((_G(_bitFlags) & (1 << DARKBIT)) && _G(_items)[LIGHT_SOURCE]._location != CARRIED && _G(_items)[LIGHT_SOURCE]._location != MY_LOC) {
		writeToRoomDescriptionStream("%s", _G(_sys)[TOO_DARK_TO_SEE].c_str());
		flushRoomDescription(buf);
		return;
	}

	r = &_G(_rooms)[MY_LOC];

	if (r->_text.empty())
		return;

	if (r->_text.hasPrefix("*"))
		writeToRoomDescriptionStream("%s", r->_text.substr(1).c_str());
	else {
		writeToRoomDescriptionStream("%s%s", _G(_sys)[YOU_ARE].c_str(), r->_text.c_str());
	}

	if (!(_G(_options) & SPECTRUM_STYLE)) {
		listExits();
		writeToRoomDescriptionStream(".\n");
	}

	ct = 0;
	f = 0;
	while (ct <= _G(_gameHeader)->_numItems) {
		if (_G(_items)[ct]._location == MY_LOC) {
			if (_G(_items)[ct]._text[0] == 0) {
				error("Scott::look(): Invisible item in room: %d", ct);
				ct++;
				continue;
			}
			if (f == 0) {
				writeToRoomDescriptionStream("%s", _G(_sys)[YOU_SEE].c_str());
				f++;
				if (_G(_options) & SPECTRUM_STYLE)
					writeToRoomDescriptionStream("\n");
			} else if (!(_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE))) {
				writeToRoomDescriptionStream("%s", _G(_sys)[ITEM_DELIMITER].c_str());
			}
			writeToRoomDescriptionStream("%s", _G(_items)[ct]._text.c_str());
			if (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) {
				writeToRoomDescriptionStream("%s", _G(_sys)[ITEM_DELIMITER].c_str());
			}
		}
		ct++;
	}

	if ((_G(_options) & TI994A_STYLE) && f) {
		writeToRoomDescriptionStream("%s", ".");
	}

	if (_G(_options) & SPECTRUM_STYLE) {
		listExitsSpectrumStyle();
	} else if (f) {
		writeToRoomDescriptionStream("\n");
	}

	if ((_G(_autoInventory) || (_G(_options) & FORCE_INVENTORY)) && !(_G(_options) & FORCE_INVENTORY_OFF))
		listInventoryInUpperWindow();

	flushRoomDescription(buf);
}

int Scott::whichWord(const char *word, const Common::StringArray &list) {
	int n = 1;
	int ne = 1;
	const char *tp;
	while (ne <= _G(_gameHeader)->_numWords) {
		tp = list[ne].c_str();
		if (*tp == '*')
			tp++;
		else
			n = ne;
		if (scumm_strnicmp(word, tp, _G(_gameHeader)->_wordLength) == 0)
			return n;
		ne++;
	}
	return -1;
}

Common::Error Scott::writeGameData(Common::WriteStream *ws) {
	Common::String msg;

	for (int ct = 0; ct < 16; ct++) {
		msg = Common::String::format("%d %d\n", _G(_counters)[ct], _G(_roomSaved)[ct]);
		ws->write(msg.c_str(), msg.size());
		ws->writeByte(0);
	}

	msg = Common::String::format("%u %d %d %d %d %d %d\n",
								 _G(_bitFlags), (_G(_bitFlags) & (1 << DARKBIT)) ? 1 : 0,
								 MY_LOC, _G(_currentCounter), _G(_savedRoom), _G(_gameHeader)->_lightTime, _G(_autoInventory));
	ws->write(msg.c_str(), msg.size());
	ws->writeByte(0);

	for (int ct = 0; ct <= _G(_gameHeader)->_numItems; ct++) {
		msg = Common::String::format("%hd\n", (short)_G(_items)[ct]._location);
		ws->write(msg.c_str(), msg.size());
		ws->writeByte(0);
	}

	output(_("Saved.\n"));
	return Common::kNoError;
}

Common::Error Scott::readSaveData(Common::SeekableReadStream *rs) {
	Common::String line;
	int ct = 0;
	short lo;
	short darkFlag;

	int previousAutoInventory = _G(_autoInventory);

	SavedState *state = saveCurrentState();

	int result = 0;

	for (ct = 0; ct < 16; ct++) {
		line = QuetzalReader::readString(rs);
		result = sscanf(line.c_str(), "%d %d", &_G(_counters)[ct], &_G(_roomSaved)[ct]);
		if (result != 2 || _G(_roomSaved)[ct] > _G(_gameHeader)->_numRooms) {
			recoverFromBadRestore(state);
			return Common::kNoError;
		}
	}

	line = QuetzalReader::readString(rs);
	result = sscanf(line.c_str(), "%u %hd %d %d %d %d %d\n",
		   &_G(_bitFlags), &darkFlag, &MY_LOC, &_G(_currentCounter), &_G(_savedRoom),
		   &_G(_gameHeader)->_lightTime, &_G(_autoInventory));

	if (result == 6)
		_G(_autoInventory) = previousAutoInventory;
	if ((result != 7 && result != 6) || MY_LOC > _G(_gameHeader)->_numRooms || MY_LOC < 1 || _G(_savedRoom) > _G(_gameHeader)->_numRooms) {
		recoverFromBadRestore(state);
		return Common::kNoError;
	}
	// Backward compatibility
	if (darkFlag)
		_G(_bitFlags) |= (1 << 15);
	for (ct = 0; ct <= _G(_gameHeader)->_numItems; ct++) {
		line = QuetzalReader::readString(rs);
		result = sscanf(line.c_str(), "%hd\n", &lo);
		_G(_items)[ct]._location = (unsigned char)lo;
		if (result != 1 || (_G(_items)[ct]._location > _G(_gameHeader)->_numRooms && _G(_items)[ct]._location != CARRIED)) {
			recoverFromBadRestore(state);
			return Common::kNoError;
		}
	}

	saveUndo();
	_G(_justStarted) = 0;
	_G(_stopTime) = 1;
	return Common::kNoError;
}

ActionResultType Scott::performLine(int ct) {
#ifdef DEBUG_ACTIONS
	debug("Performing line %d: ", ct);
#endif
	int continuation = 0, dead = 0;
	int param[5], pptr = 0;
	int p;
	int act[4];
	int cc = 0;
	while (cc < 5) {
		int cv, dv;
		cv = _G(_actions)[ct]._condition[cc];
		dv = cv / 20;
		cv %= 20;
#ifdef DEBUG_ACTIONS
		debug("Testing condition %d: ", cv);
#endif
		switch (cv) {
		case 0:
			param[pptr++] = dv;
			break;
		case 1:
#ifdef DEBUG_ACTIONS
			debug("Does the player carry %s?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location != CARRIED)
				return ACT_FAILURE;
			break;
		case 2:
#ifdef DEBUG_ACTIONS
			debug("Is %s in location?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location != MY_LOC)
				return ACT_FAILURE;
			break;
		case 3:
#ifdef DEBUG_ACTIONS
			debug("Is %s held or in location?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location != CARRIED && _G(_items)[dv]._location != MY_LOC)
				return ACT_FAILURE;
			break;
		case 4:
#ifdef DEBUG_ACTIONS
			debug("Is location %s?\n", _G(_rooms)[dv].Text);
#endif
			if (MY_LOC != dv)
				return ACT_FAILURE;
			break;
		case 5:
#ifdef DEBUG_ACTIONS
			debug("Is %s NOT in location?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location == MY_LOC)
				return ACT_FAILURE;
			break;
		case 6:
#ifdef DEBUG_ACTIONS
			debug("Does the player NOT carry %s?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location == CARRIED)
				return ACT_FAILURE;
			break;
		case 7:
#ifdef DEBUG_ACTIONS
			debug("Is location NOT %s?\n", _G(_rooms)[dv].Text);
#endif
			if (MY_LOC == dv)
				return ACT_FAILURE;
			break;
		case 8:
#ifdef DEBUG_ACTIONS
			debug("Is bitflag %d set?\n", dv);
#endif
			if ((_G(_bitFlags) & (1 << dv)) == 0)
				return ACT_FAILURE;
			break;
		case 9:
#ifdef DEBUG_ACTIONS
			debug("Is bitflag %d NOT set?\n", dv);
#endif
			if (_G(_bitFlags) & (1 << dv))
				return ACT_FAILURE;
			break;
		case 10:
#ifdef DEBUG_ACTIONS
			debug("Does the player carry anything?\n");
#endif
			if (countCarried() == 0)
				return ACT_FAILURE;
			break;
		case 11:
#ifdef DEBUG_ACTIONS
			debug("Does the player carry nothing?\n");
#endif
			if (countCarried())
				return ACT_FAILURE;
			break;
		case 12:
#ifdef DEBUG_ACTIONS
			debug("Is %s neither carried nor in room?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location == CARRIED || _G(_items)[dv]._location == MY_LOC)
				return ACT_FAILURE;
			break;
		case 13:
#ifdef DEBUG_ACTIONS
			debug("Is %s (%d) in play?\n", _G(_items)[dv].Text, dv);
#endif
			if (_G(_items)[dv]._location == 0)
				return ACT_FAILURE;
			break;
		case 14:
#ifdef DEBUG_ACTIONS
			debug("Is %s NOT in play?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location)
				return ACT_FAILURE;
			break;
		case 15:
#ifdef DEBUG_ACTIONS
			debug("Is _G(_currentCounter) <= %d?\n", dv);
#endif
			if (_G(_currentCounter) > dv)
				return ACT_FAILURE;
			break;
		case 16:
#ifdef DEBUG_ACTIONS
			debug("Is _G(_currentCounter) > %d?\n", dv);
#endif
			if (_G(_currentCounter) <= dv)
				return ACT_FAILURE;
			break;
		case 17:
#ifdef DEBUG_ACTIONS
			debug("Is %s still in initial room?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location != _G(_items)[dv]._initialLoc)
				return ACT_FAILURE;
			break;
		case 18:
#ifdef DEBUG_ACTIONS
			debug("Has %s been moved?\n", _G(_items)[dv].Text);
#endif
			if (_G(_items)[dv]._location == _G(_items)[dv]._initialLoc)
				return ACT_FAILURE;
			break;
		case 19: /* Only seen in Brian Howarth games so far */
#ifdef DEBUG_ACTIONS
			debug("Is current counter == %d?\n", dv);
			if (_G(_currentCounter) != dv)
				debug("Nope, current counter is %d\n", _G(_currentCounter));
#endif
			if (_G(_currentCounter) != dv)
				return ACT_FAILURE;
			break;
		}
#ifdef DEBUG_ACTIONS
		debug("YES\n");
#endif
		cc++;
	}
#if defined(__clang__)
#pragma mark Subcommands
#endif

	/* Actions */
	act[0] = _G(_actions)[ct]._action[0];
	act[2] = _G(_actions)[ct]._action[1];
	act[1] = act[0] % 150;
	act[3] = act[2] % 150;
	act[0] /= 150;
	act[2] /= 150;
	cc = 0;
	pptr = 0;
	while (cc < 4) {
#ifdef DEBUG_ACTIONS
		debug("Performing action %d: ", act[cc]);
#endif
		if (act[cc] >= 1 && act[cc] < 52) {
			printMessage(act[cc]);
		} else if (act[cc] > 101) {
			printMessage(act[cc] - 50);
		} else
			switch (act[cc]) {
			case 0: /* NOP */
				break;
			case 52:
				if (countCarried() >= _G(_gameHeader)->_maxCarry) {
					output(_G(_sys)[YOURE_CARRYING_TOO_MUCH]);
					return ACT_SUCCESS;
				}
				_G(_items)[param[pptr++]]._location = CARRIED;
				break;
			case 53:
#ifdef DEBUG_ACTIONS
				debug("item %d (\"%s\") is now in location.\n", param[pptr],
					  _G(_items)[param[pptr]].Text);
#endif
				_G(_items)[param[pptr++]]._location = MY_LOC;
				_G(_shouldLookInTranscript) = 1;
				break;
			case 54:
#ifdef DEBUG_ACTIONS
				debug("player location is now room %d (%s).\n", param[pptr],
					  _G(_rooms)[param[pptr]].Text);
#endif
				MY_LOC = param[pptr++];
				_G(_shouldLookInTranscript) = 1;
				look();
				break;
			case 55:
#ifdef DEBUG_ACTIONS
				fprintf(stderr,
						"Item %d (%s) is removed from the game (put in room 0).\n",
						param[pptr], _G(_items)[param[pptr]].Text);
#endif
				_G(_items)[param[pptr++]]._location = 0;
				break;
			case 56:
				_G(_bitFlags) |= 1 << DARKBIT;
				break;
			case 57:
				_G(_bitFlags) &= ~(1 << DARKBIT);
				break;
			case 58:
#ifdef DEBUG_ACTIONS
				debug("Bitflag %d is set\n", param[pptr]);
#endif
				_G(_bitFlags) |= (1 << param[pptr++]);
				break;
			case 59:
#ifdef DEBUG_ACTIONS
				debug("Item %d (%s) is removed from play.\n", param[pptr],
					  _G(_items)[param[pptr]].Text);
#endif
				_G(_items)[param[pptr++]]._location = 0;
				break;
			case 60:
#ifdef DEBUG_ACTIONS
				debug("BitFlag %d is cleared\n", param[pptr]);
#endif
				_G(_bitFlags) &= ~(1 << param[pptr++]);
				break;
			case 61:
				playerIsDead();
				break;
			case 62:
				p = param[pptr++];
				putItemAInRoomB(p, param[pptr++]);
				break;
			case 63:
#ifdef DEBUG_ACTIONS
				debug("Game over.\n");
#endif
				doneIt();
				dead = 1;
				break;
			case 64:
				break;
			case 65:
				dead = printScore();
				_G(_stopTime) = 2;
				break;
			case 66:
				if (_G(_game)->_type == SEAS_OF_BLOOD_VARIANT)
					adventureSheet();
				else
					listInventory();
				_G(_stopTime) = 2;
				break;
			case 67:
				_G(_bitFlags) |= (1 << 0);
				break;
			case 68:
				_G(_bitFlags) &= ~(1 << 0);
				break;
			case 69:
				_G(_gameHeader)->_lightTime = _G(_lightRefill);
				_G(_items)[LIGHT_SOURCE]._location = CARRIED;
				_G(_bitFlags) &= ~(1 << LIGHTOUTBIT);
				break;
			case 70:
				clearScreen(); /* pdd. */
				break;
			case 71:
				saveGame();
				_G(_stopTime) = 2;
				break;
			case 72:
				p = param[pptr++];
				swapItemLocations(p, param[pptr++]);
				break;
			case 73:
#ifdef DEBUG_ACTIONS
				debug("Continue with next line\n");
#endif
				continuation = 1;
				break;
			case 74:
				_G(_items)[param[pptr++]]._location = CARRIED;
				break;
			case 75:
				p = param[pptr++];
				moveItemAToLocOfItemB(p, param[pptr++]);
				break;
			case 76: /* Looking at adventure .. */
#ifdef DEBUG_ACTIONS
				debug("LOOK\n");
#endif
				if (_splitScreen)
					look();
				_G(_shouldLookInTranscript) = 1;
				break;
			case 77:
				if (_G(_currentCounter) >= 1)
					_G(_currentCounter)--;
#ifdef DEBUG_ACTIONS
				fprintf(stderr,
						"decrementing current counter. Current counter is now %d.\n",
						_G(_currentCounter));
#endif
				break;
			case 78:
				outputNumber(_G(_currentCounter));
				output(" ");
				break;
			case 79:
#ifdef DEBUG_ACTIONS
				debug("_G(_currentCounter) is set to %d.\n", param[pptr]);
#endif
				_G(_currentCounter) = param[pptr++];
				break;
			case 80:
				goToStoredLoc();
				break;
			case 81:
				swapCounters(param[pptr++]);
				break;
			case 82:
				_G(_currentCounter) += param[pptr++];
				break;
			case 83:
				_G(_currentCounter) -= param[pptr++];
				if (_G(_currentCounter) < -1)
					_G(_currentCounter) = -1;
				/* Note: This seems to be needed. I don't yet
						 know if there is a maximum value to limit too */
				break;
			case 84:
				if (_G(_currentCommand))
					glk_put_string_stream_uni(
						glk_window_get_stream(_G(_bottomWindow)),
						_G(_unicodeWords)[_G(_currentCommand)->_nounWordIndex]);
				break;
			case 85:
				if (_G(_currentCommand))
					glk_put_string_stream_uni(
						glk_window_get_stream(_G(_bottomWindow)),
						_G(_unicodeWords)[_G(_currentCommand)->_nounWordIndex]);
				output("\n");
				break;
			case 86:
				if (!(_G(_options) & SPECTRUM_STYLE))
					output("\n");
				break;
			case 87:
				swapLocAndRoomFlag(param[pptr++]);
				break;
			case 88:
#ifdef DEBUG_ACTIONS
				debug("Delay\n");
#endif
				delay(1);
				break;
			case 89:
#ifdef DEBUG_ACTIONS
				debug("Action 89, parameter %d\n", param[pptr]);
#endif
				p = param[pptr++];
				switch (CURRENT_GAME) {
				case SPIDERMAN:
				case SPIDERMAN_C64:
					drawBlack();
					break;
				case SECRET_MISSION:
				case SECRET_MISSION_C64:
					// TODO
					// SecretAction(p);
					break;
				case ADVENTURELAND:
				case ADVENTURELAND_C64:
					adventurelandAction(p);
					break;
				case SEAS_OF_BLOOD:
				case SEAS_OF_BLOOD_C64:
					bloodAction(p);
					break;
				case ROBIN_OF_SHERWOOD:
				case ROBIN_OF_SHERWOOD_C64:
					sherwoodAction(p);
					break;
				case GREMLINS:
				case GREMLINS_SPANISH:
				case GREMLINS_GERMAN:
				case GREMLINS_GERMAN_C64:
					gremlinsAction(p);
					break;
				default:
					break;
				}
				break;

			case 90:
#ifdef DEBUG_ACTIONS
				debug("Draw Hulk image, parameter %d\n", param[pptr]);
#endif
				if (CURRENT_GAME != HULK && CURRENT_GAME != HULK_C64) {
					pptr++;
				} else if (!(_G(_bitFlags) & (1 << DARKBIT)))
					drawHulkImage(param[pptr++]);
				break;
			default:
				debug("Unknown action %d [Param begins %d %d]\n", act[cc],
					  param[pptr], param[pptr + 1]);
				break;
			}
		cc++;
	}

	if (dead) {
		return ACT_GAMEOVER;
	} else if (continuation) {
		return ACT_CONTINUE;
	} else {
		return ACT_SUCCESS;
	}
}

ExplicitResultType Scott::performActions(int vb, int no) {
	int dark = _G(_bitFlags) & (1 << DARKBIT);
	int ct = 0;
	ExplicitResultType flag = ER_NO_RESULT;
	int doAgain = 0;
	int foundMatch = 0;

	if (vb == GO && no == -1) {
		output(_G(_sys)[DIRECTION]);
		return ER_SUCCESS;
	}
	if (vb == 1 && no >= 1 && no <= 6) {
		int nl;
		if (_G(_items)[LIGHT_SOURCE]._location == MY_LOC || _G(_items)[LIGHT_SOURCE]._location == CARRIED)
			dark = 0;
		if (dark)
			output(_G(_sys)[DANGEROUS_TO_MOVE_IN_DARK]);
		nl = _G(_rooms)[MY_LOC]._exits[no - 1];
		if (nl != 0) {
			/* Seas of Blood needs this to be able to flee back to the last room */
			if (_G(_game)->_type == SEAS_OF_BLOOD_VARIANT)
				_G(_savedRoom) = MY_LOC;
			if (_G(_options) & (SPECTRUM_STYLE | TI994A_STYLE))
				output(_G(_sys)[OK]);
			MY_LOC = nl;
			_G(_shouldLookInTranscript) = 1;
			if (_G(_currentCommand) && _G(_currentCommand)->_next) {
				lookWithPause();
			}
			return ER_SUCCESS;
		}
		if (dark) {
			_G(_bitFlags) &= ~(1 << DARKBIT);
			MY_LOC = _G(_gameHeader)->_numRooms; /* It seems to be what the code says! */
			output(_G(_sys)[YOU_FELL_AND_BROKE_YOUR_NECK]);
			_G(_bitFlags) &= ~(1 << DARKBIT);
			MY_LOC = _G(_gameHeader)->_numRooms; /* It seems to be what the code says! */
			return ER_SUCCESS;
		}
		output(_G(_sys)[YOU_CANT_GO_THAT_WAY]);
		return ER_SUCCESS;
	}

	if ((CURRENT_GAME == HULK || CURRENT_GAME == HULK_C64) && vb == 39 && !dark) {
		hulkShowImageOnExamine(no);
	}

	if (_G(_currentCommand) && _G(_currentCommand)->_allFlag && vb == _G(_currentCommand)->_verb && !(dark && vb == TAKE)) {
		output(_G(_items)[_G(_currentCommand)->_item]._text);
		output("....");
	}
	flag = ER_RAN_ALL_LINES_NO_MATCH;
	if (CURRENT_GAME != TI994A) {
		while (ct <= _G(_gameHeader)->_numActions) {
			int verbvalue, nounvalue;
			verbvalue = _G(_actions)[ct]._vocab;
			/* Think this is now right. If a line we run has an action73
		   run all following lines with vocab of 0,0 */
			if (vb != 0 && (doAgain && verbvalue != 0))
				break;
			/* Oops.. added this minor cockup fix 1.11 */
			if (vb != 0 && !doAgain && flag == 0)
				break;
			nounvalue = verbvalue % 150;
			verbvalue /= 150;
			if ((verbvalue == vb) || (doAgain && _G(_actions)[ct]._vocab == 0)) {
				if ((verbvalue == 0 && randomPercent(nounvalue)) || doAgain || (verbvalue != 0 && (nounvalue == no || nounvalue == 0))) {
					if (verbvalue == vb && vb != 0 && nounvalue == no)
						foundMatch = 1;
					ActionResultType flag2;
					if (flag == ER_RAN_ALL_LINES_NO_MATCH)
						flag = ER_RAN_ALL_LINES;
					if ((flag2 = performLine(ct)) != ACT_FAILURE) {
						/* ahah finally figured it out ! */
						flag = ER_SUCCESS;
						if (flag2 == ACT_CONTINUE)
							doAgain = 1;
						else if (flag2 == ACT_GAMEOVER)
							return ER_SUCCESS;
						if (vb != 0 && doAgain == 0)
							return ER_SUCCESS;
					}
				}
			}

			ct++;

			/* Previously this did not check ct against
			 * GameHeader.NumActions and would read past the end of
			 * Actions.  I don't know what should happen on the last
			 * action, but doing nothing is better than reading one
			 * past the end.
			 * --Chris
			 */
			if (ct <= _G(_gameHeader)->_numActions && _G(_actions)[ct]._vocab != 0)
				doAgain = 0;
		}
	} else {
		if (vb == 0) {
			runImplicitTI99Actions();
			return ER_NO_RESULT;
		} else {
			flag = runExplicitTI99Actions(vb, no);
		}
	}

	if (foundMatch)
		return flag;

	if (flag != ER_SUCCESS) {
		int item = 0;
		if (_G(_items)[LIGHT_SOURCE]._location == MY_LOC || _G(_items)[LIGHT_SOURCE]._location == CARRIED)
			dark = 0;

		if (vb == TAKE || vb == DROP) {
			if (_G(_currentCommand)->_allFlag) {
				if (vb == TAKE && dark) {
					output(_G(_sys)[TOO_DARK_TO_SEE]);
					while (!(_G(_currentCommand)->_allFlag & LASTALL)) {
						_G(_currentCommand) = _G(_currentCommand)->_next;
					}
					return ER_SUCCESS;
				}
				item = _G(_currentCommand)->_item;
				int location = CARRIED;
				if (vb == TAKE)
					location = MY_LOC;
				while (_G(_items)[item]._location != location && !(_G(_currentCommand)->_allFlag & LASTALL)) {
					_G(_currentCommand) = _G(_currentCommand)->_next;
				}
				if (_G(_items)[item]._location != location)
					return ER_SUCCESS;
			}

			/* Yes they really _are_ hardcoded values */
			if (vb == TAKE) {
				if (no == -1) {
					output(_G(_sys)[WHAT]);
					return ER_SUCCESS;
				}
				if (countCarried() >= _G(_gameHeader)->_maxCarry) {
					output(_G(_sys)[YOURE_CARRYING_TOO_MUCH]);
					return ER_SUCCESS;
				}
				if (!item)
					item = matchUpItem(no, MY_LOC);
				if (item == -1) {
					item = matchUpItem(no, CARRIED);
					if (item == -1) {
						item = matchUpItem(no, 0);
						if (item == -1) {
							output(_G(_sys)[THATS_BEYOND_MY_POWER]);
						} else {
							output(_G(_sys)[YOU_DONT_SEE_IT]);
						}
					} else {
						output(_G(_sys)[YOU_HAVE_IT]);
					}
					return ER_SUCCESS;
				}
				_G(_items)[item]._location = CARRIED;
				printTakenOrDropped(TAKEN);
				return ER_SUCCESS;
			}

			if (vb == DROP) {
				if (no == -1) {
					output(_G(_sys)[WHAT]);
					return ER_SUCCESS;
				}
				if (!item)
					item = matchUpItem(no, CARRIED);
				if (item == -1) {
					item = matchUpItem(no, 0);
					if (item == -1) {
						output(_G(_sys)[THATS_BEYOND_MY_POWER]);
					} else {
						output(_G(_sys)[YOU_HAVENT_GOT_IT]);
					}
					return ER_SUCCESS;
				}
				_G(_items)[item]._location = MY_LOC;
				printTakenOrDropped(DROPPED);
				return ER_SUCCESS;
			}
		}
	}
	return flag;
}

void Scott::readInts(Common::SeekableReadStream *f, size_t count, ...) {
	va_list va;
	va_start(va, count);
	unsigned char c = f->readByte();

	for (size_t idx = 0; idx < count; ++idx) {
		while (f->pos() < f->size() && Common::isSpace(c))
			c = f->readByte();

		// Get the next value
		int *val = va_arg(va, int *);
		*val = 0;

		int factor = c == '-' ? -1 : 1;
		if (factor == -1)
			c = f->readByte();

		while (Common::isDigit(c)) {
			*val = (*val * 10) + (c - '0');
			c = f->readByte();
		}

		*val *= factor; // Handle negatives
	}

	va_end(va);
}

void Scott::writeToRoomDescriptionStream(const char *fmt, ...) {
	if (_roomDescriptionStream == nullptr)
		return;
	va_list ap;

	va_start(ap, fmt);
	Common::String msg = Common::String::vformat(fmt, ap);
	va_end(ap);

	glk_put_string_stream(_roomDescriptionStream, msg.c_str());
}

void Scott::flushRoomDescription(char *buf) {
	glk_stream_close(_roomDescriptionStream, 0);

	strid_t storedTranscript = _G(_transcript);
	if (!_printLookToTranscript)
		_G(_transcript) = nullptr;

	int printDelimiter = (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE | TI994A_STYLE));

	if (_splitScreen) {
		glk_window_clear(_G(_topWindow));
		glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
		int rows, length;
		char *textWithBreaks = lineBreakText(buf, _topWidth, &rows, &length);

		uint bottomheight;
		glk_window_get_size(_G(_bottomWindow), nullptr, &bottomheight);
		winid_t o2 = glk_window_get_parent(_G(_topWindow));
		if (!(bottomheight < 3 && _topHeight < rows)) {
			glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
			glk_window_set_arrangement(o2, winmethod_Above | winmethod_Fixed, rows, _G(_topWindow));
		} else {
			printDelimiter = 0;
		}

		int line = 0;
		int index = 0;
		int i;
		char *string = new char[_topWidth + 1];
		for (line = 0; line < rows && index < length; line++) {
			for (i = 0; i < _topWidth; i++) {
				string[i] = textWithBreaks[index++];
				if (string[i] == 10 || string[i] == 13 || index >= length)
					break;
			}
			if (i < _topWidth + 1) {
				string[i++] = '\n';
			}
			string[i] = 0;
			if (strlen(string) == 0)
				break;
			glk_window_move_cursor(_G(_topWindow), 0, line);
			display(_G(_topWindow), "%s", string);
		}

		if (line < rows - 1) {
			glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
			glk_window_set_arrangement(o2, winmethod_Above | winmethod_Fixed, MIN(rows - 1, _topHeight - 1), _G(_topWindow));
		}

		delete[] textWithBreaks;
	} else {
		display(_G(_bottomWindow), "%s", buf);
	}

	if (printDelimiter) {
		printWindowDelimiter();
	}

	if (_pauseNextRoomDescription) {
		delay(0.8);
		_pauseNextRoomDescription = 0;
	}

	_G(_transcript) = storedTranscript;
	if (buf != nullptr) {
		delete[] buf;
		buf = nullptr;
	}
}

void Scott::printWindowDelimiter() {
	glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
	glk_window_move_cursor(_G(_topWindow), 0, _topHeight - 1);
	glk_stream_set_current(glk_window_get_stream(_G(_topWindow)));
	if (_G(_options) & SPECTRUM_STYLE)
		for (int i = 0; i < _topWidth; i++)
			glk_put_char('*');
	else {
		glk_put_char('<');
		for (int i = 0; i < _topWidth - 2; i++)
			glk_put_char('-');
		glk_put_char('>');
	}
}

void Scott::listExits() {
	int ct = 0;
	int f = 0;

	writeToRoomDescriptionStream("\n\n%s", _G(_sys)[EXITS].c_str());

	while (ct < 6) {
		if ((&_G(_rooms)[MY_LOC])->_exits[ct] != 0) {
			if (f) {
				writeToRoomDescriptionStream("%s", _G(_sys)[EXITS_DELIMITER].c_str());
			}
			/* _G(_sys)[] begins with the exit names */
			writeToRoomDescriptionStream("%s", _G(_sys)[ct].c_str());
			f = 1;
		}
		ct++;
	}
	if (f == 0)
		writeToRoomDescriptionStream("%s", _G(_sys)[NONE].c_str());
	return;
}

void Scott::listExitsSpectrumStyle() {
	int ct = 0;
	int f = 0;

	while (ct < 6) {
		if ((&_G(_rooms)[MY_LOC])->_exits[ct] != 0) {
			if (f == 0) {
				writeToRoomDescriptionStream("\n\n%s", _G(_sys)[EXITS].c_str());
			} else {
				writeToRoomDescriptionStream("%s", _G(_sys)[EXITS_DELIMITER].c_str());
			}
			/* sys[] begins with the exit names */
			writeToRoomDescriptionStream("%s", _G(_sys)[ct].c_str());
			f = 1;
		}
		ct++;
	}
	writeToRoomDescriptionStream("\n");
	return;
}

void Scott::listInventoryInUpperWindow() {
	int i = 0;
	int lastitem = -1;
	writeToRoomDescriptionStream("\n%s", _G(_sys)[INVENTORY].c_str());
	while (i <= _G(_gameHeader)->_numItems) {
		if (_G(_items)[i]._location == CARRIED) {
			if (_G(_items)[i]._text[0] == 0) {
				error("Scott::listInventoryInUpperWindow(): Invisible item in inventory: %d", i);
				i++;
				continue;
			}
			if (lastitem > -1 && (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) == 0) {
				writeToRoomDescriptionStream("%s", _G(_sys)[ITEM_DELIMITER].c_str());
			}
			lastitem = i;
			writeToRoomDescriptionStream("%s", _G(_items)[i]._text.c_str());
			if (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) {
				writeToRoomDescriptionStream("%s", _G(_sys)[ITEM_DELIMITER].c_str());
			}
		}
		i++;
	}
	if (lastitem == -1) {
		writeToRoomDescriptionStream("%s\n", _G(_sys)[NOTHING].c_str());
	} else {
		if (_G(_options) & TI994A_STYLE && !itemEndsWithPeriod(lastitem))
			writeToRoomDescriptionStream(".");
		writeToRoomDescriptionStream("\n");
	}
}

int Scott::itemEndsWithPeriod(int item) {
	if (item < 0 || item > _G(_gameHeader)->_numItems)
		return 0;
	Common::String desc = _G(_items)[item]._text;
	if (!desc.empty() && desc[0] != 0) {
		const char lastchar = desc[desc.size() - 1];
		if (lastchar == '.' || lastchar == '!') {
			return 1;
		}
	}
	return 0;
}

void Scott::closeGraphicsWindow() {
	if (_G(_graphics) == nullptr)
		_G(_graphics) = findGlkWindowWithRock(GLK_GRAPHICS_ROCK);
	if (_G(_graphics)) {
		glk_window_close(_G(_graphics), nullptr);
		_G(_graphics) = nullptr;
		glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
	}
}

winid_t Scott::findGlkWindowWithRock(glui32 rock) {
	uint rockptr;
	winid_t win = glk_window_iterate(nullptr, &rockptr);
	while (win) {
		if (rockptr == rock)
			return win;
		win = glk_window_iterate(win, &rockptr);
	}
	return 0;
}

void Scott::openGraphicsWindow() {
	if (!glk_gestalt(gestalt_Graphics, 0))
		return;
	uint graphwidth, graphheight, optimalWidth, optimalHeight;

	if (_G(_topWindow) == nullptr)
		_G(_topWindow) = findGlkWindowWithRock(GLK_STATUS_ROCK);
	if (_G(_graphics) == nullptr)
		_G(_graphics) = findGlkWindowWithRock(GLK_GRAPHICS_ROCK);
	if (_G(_graphics) == nullptr && _G(_topWindow) != nullptr) {
		glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
		glk_window_close(_G(_topWindow), nullptr);
		_G(_graphics) = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Proportional, 60, wintype_Graphics, GLK_GRAPHICS_ROCK);
		glk_window_get_size(_G(_graphics), &graphwidth, &graphheight);
		_G(_pixelSize) = optimalPictureSize(&optimalWidth, &optimalHeight);
		_G(_xOffset) = ((int)graphwidth - (int)optimalWidth) / 2;

		if (graphheight > optimalHeight) {
			winid_t parent = glk_window_get_parent(_G(_graphics));
			glk_window_set_arrangement(parent, winmethod_Above | winmethod_Fixed, optimalHeight, nullptr);
		}

		// Set the graphics window background to match the main window background, best as we can, and clear the window.
		uint backgroundColor;
		if (glk_style_measure(_G(_bottomWindow), style_Normal, stylehint_BackColor, &backgroundColor)) {
			glk_window_set_background_color(_G(_graphics), backgroundColor);
			glk_window_clear(_G(_graphics));
		}

		_G(_topWindow) = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Fixed, _topHeight, wintype_TextGrid, GLK_STATUS_ROCK);
		glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
	} else {
		if (!_G(_graphics))
			_G(_graphics) = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Proportional, 60, wintype_Graphics, GLK_GRAPHICS_ROCK);
		glk_window_get_size(_G(_graphics), &graphwidth, &graphheight);
		_G(_pixelSize) = optimalPictureSize(&optimalWidth, &optimalHeight);
		_G(_xOffset) = (graphwidth - optimalWidth) / 2;
		winid_t parent = glk_window_get_parent(_G(_graphics));
		glk_window_set_arrangement(parent, winmethod_Above | winmethod_Fixed, optimalHeight, nullptr);
	}
}

glui32 Scott::optimalPictureSize(uint *width, uint *height) {
	*width = 255;
	*height = 96;
	int multiplier = 1;
	uint graphwidth, graphheight;
	glk_window_get_size(_G(_graphics), &graphwidth, &graphheight);
	multiplier = graphheight / 96;
	if (static_cast<glui32>(255 * multiplier) > graphwidth)
		multiplier = graphwidth / 255;

	if (multiplier == 0)
		multiplier = 1;

	*width = 255 * multiplier;
	*height = 96 * multiplier;

	return multiplier;
}

void Scott::openTopWindow() {
	_G(_topWindow) = findGlkWindowWithRock(GLK_STATUS_ROCK);
	if (_G(_topWindow) == nullptr) {
		if (_splitScreen) {
			_G(_topWindow) = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Fixed, _topHeight, wintype_TextGrid, GLK_STATUS_ROCK);
			if (_G(_topWindow) == nullptr) {
				_splitScreen = 0;
				_G(_topWindow) = _G(_bottomWindow);
			} else {
				glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, nullptr);
			}
		} else {
			_G(_topWindow) = _G(_bottomWindow);
		}
	}
}

void Scott::cleanupAndExit() {
	if (_G(_transcript))
		glk_stream_close(_G(_transcript), nullptr);
	if (drawingVector()) {
		_G(_gliSlowDraw) = 0;
		drawSomeVectorPixels(0);
	}
	glk_exit();
}

void Scott::drawBlack() {
	glk_window_fill_rect(_G(_graphics), 0, _G(_xOffset), 0, 32 * 8 * _G(_pixelSize), 12 * 8 * _G(_pixelSize));
}

void Scott::drawImage(int image) {
	if (!glk_gestalt(gestalt_Graphics, 0))
		return;
	openGraphicsWindow();
	if (_G(_graphics) == nullptr) {
		error("drawImage: Graphic window nullptr?");
		return;
	}
	if (_G(_game)->_pictureFormatVersion == 99)
		drawVectorPicture(image);
	else
		drawSagaPictureNumber(image);
}

void Scott::drawRoomImage() {
	if (CURRENT_GAME == ADVENTURELAND || CURRENT_GAME == ADVENTURELAND_C64) {
		adventurelandDarkness();
	}

	int dark = ((_G(_bitFlags) & (1 << DARKBIT)) && _G(_items)[LIGHT_SOURCE]._location != CARRIED && _G(_items)[LIGHT_SOURCE]._location != MY_LOC);

	if (dark && _G(_graphics) != nullptr && !(_G(_rooms)[MY_LOC]._image == 255)) {
		_G(_vectorImageShown) = -1;
		_G(_vectorState) = NO_VECTOR_IMAGE;
		glk_request_timer_events(0);
		drawBlack();
		return;
	}

	switch (CURRENT_GAME) {
	case SEAS_OF_BLOOD:
	case SEAS_OF_BLOOD_C64:
		seasOfBloodRoomImage();
		return;
	case ROBIN_OF_SHERWOOD:
	case ROBIN_OF_SHERWOOD_C64:
		robinOfSherwoodLook();
		return;
	case HULK:
	case HULK_C64:
		hulkLook();
		return;
	default:
		break;
	}

	if (_G(_rooms)[MY_LOC]._image == 255) {
		closeGraphicsWindow();
		return;
	}

	if (dark)
		return;

	if (_G(_game)->_pictureFormatVersion == 99) {
		drawImage(MY_LOC - 1);
		return;
	}

	if (_G(_game)->_type == GREMLINS_VARIANT) {
		gremlinsLook();
	} else {
		drawImage(_G(_rooms)[MY_LOC]._image & 127);
	}
	for (int ct = 0; ct <= _G(_gameHeader)->_numItems; ct++)
		if (_G(_items)[ct]._image && _G(_items)[ct]._location == MY_LOC) {
			if ((_G(_items)[ct]._flag & 127) == MY_LOC) {
				drawImage(_G(_items)[ct]._image);
				/* Draw the correct image of the bear on the beach */
			} else if (_G(_game)->_type == SAVAGE_ISLAND_VARIANT && ct == 20 && MY_LOC == 8) {
				drawImage(9);
			}
		}
}

void Scott::restartGame() {
	if (_G(_currentCommand))
		freeCommands();
	restoreState(_G(_initialState));
	_G(_justStarted) = 0;
	_G(_stopTime) = 0;
	glk_window_clear(_G(_bottomWindow));
	openTopWindow();
	_G(_shouldRestart) = 0;
}

void Scott::transcriptOn() {
	frefid_t ref;

	if (_G(_transcript)) {
		output(_G(_sys)[TRANSCRIPT_ALREADY]);
		return;
	}

	ref = glk_fileref_create_by_prompt(fileusage_TextMode | fileusage_Transcript, filemode_Write, 0);
	if (ref == nullptr)
		return;

	_G(_transcript) = glk_stream_open_file_uni(ref, filemode_Write, 0);
	glk_fileref_destroy(ref);

	if (_G(_transcript) == nullptr) {
		output(_G(_sys)[FAILED_TRANSCRIPT]);
		return;
	}

	glui32 *startOfTranscript = toUnicode(_G(_sys)[TRANSCRIPT_START].c_str());
	glk_put_string_stream_uni(_G(_transcript), startOfTranscript);
	delete[] startOfTranscript;
	glk_put_string_stream(glk_window_get_stream(_G(_bottomWindow)), _G(_sys)[TRANSCRIPT_ON].c_str());
}

void Scott::transcriptOff() {
	if (_G(_transcript) == nullptr) {
		output(_G(_sys)[NO_TRANSCRIPT]);
		return;
	}

	glui32 *endOfTranscript = toUnicode(_G(_sys)[TRANSCRIPT_END].c_str());
	glk_put_string_stream_uni(_G(_transcript), endOfTranscript);
	delete[] endOfTranscript;

	glk_stream_close(_G(_transcript), nullptr);
	_G(_transcript) = nullptr;
	output(_G(_sys)[TRANSCRIPT_OFF]);
}

int Scott::performExtraCommand(int extraStopTime) {
	Command command = *_G(_currentCommand);
	int verb = command._verb;
	if (verb > _G(_gameHeader)->_numWords)
		verb -= _G(_gameHeader)->_numWords;
	int noun = command._noun;
	if (noun > _G(_gameHeader)->_numWords)
		noun -= _G(_gameHeader)->_numWords;
	else if (noun) {
		const char *nounWord = _G(_charWords)[_G(_currentCommand)->_nounWordIndex];
		int newnoun = whichWord(nounWord, _G(_extraNouns));
		newnoun = _G(_extraNounsKey)[newnoun];
		if (newnoun)
			noun = newnoun;
	}

	_G(_stopTime) = 1 + extraStopTime;

	switch (verb) {
	case RESTORE:
		if (noun == 0 || noun == GAME) {
			loadGame();
			return 1;
		}
		break;
	case RESTART:
		if (noun == 0 || noun == GAME) {
			output(_G(_sys)[ARE_YOU_SURE]);
			if (yesOrNo()) {
				_G(_shouldRestart) = 1;
			}
			return 1;
		}
		break;
	case SAVE:
		if (noun == 0 || noun == GAME) {
			saveGame();
			return 1;
		}
		break;
	case UNDO:
		if (noun == 0 || noun == COMMAND) {
			restoreUndo();
			return 1;
		}
		break;
	case RAM:
		if (noun == RAMLOAD) {
			ramRestore();
			return 1;
		} else if (noun == RAMSAVE) {
			ramSave();
			return 1;
		}
		break;
	case RAMSAVE:
		if (noun == 0) {
			ramSave();
			return 1;
		}
		break;
	case RAMLOAD:
		if (noun == 0) {
			ramRestore();
			return 1;
		}
		break;
	case SCRIPT:
		if (noun == ON || noun == 0) {
			transcriptOn();
			return 1;
		} else if (noun == OFF) {
			transcriptOff();
			return 1;
		}
		break;
	case EXCEPT:
		freeCommands();
	}

	_G(_stopTime) = 0;
	return 0;
}

int Scott::yesOrNo() {
	glk_request_char_event(_G(_bottomWindow));

	event_t ev;
	int result = 0;
	const char y = tolower((unsigned char)_G(_sys)[YES][0]);
	const char n = tolower((unsigned char)_G(_sys)[NO][0]);

	do {
		glk_select(&ev);
		if (ev.type == evtype_CharInput) {
			const char reply = tolower(ev.val1);
			if (reply == y) {
				result = 1;
			} else if (reply == n) {
				result = 2;
			} else {
				output(_G(_sys)[ANSWER_YES_OR_NO]);
				glk_request_char_event(_G(_bottomWindow));
			}
		} else
			updates(ev);
	} while (result == 0);

	return (result == 1);
}

void Scott::hitEnter() {
	glk_request_char_event(_G(_bottomWindow));

	event_t ev;
	int result = 0;
	do {
		glk_select(&ev);
		if (ev.type == evtype_CharInput) {
			if (ev.val1 == keycode_Return) {
				result = 1;
			} else {
				glk_request_char_event(_G(_bottomWindow));
			}
		} else
			updates(ev);
	} while (result == 0);

	return;
}

void Scott::listInventory() {
	int i = 0;
	int lastitem = -1;
	output(_G(_sys)[INVENTORY]);
	while (i <= _G(_gameHeader)->_numItems) {
		if (_G(_items)[i]._location == CARRIED) {
			if (_G(_items)[i]._text[0] == 0) {
				warning("Invisible item in inventory: %d\n", i);
				i++;
				continue;
			}
			if (lastitem > -1 && (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) == 0) {
				output(_G(_sys)[ITEM_DELIMITER]);
			}
			lastitem = i;
			output(_G(_items)[i]._text);
			if (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) {
				output(_G(_sys)[ITEM_DELIMITER]);
			}
		}
		i++;
	}
	if (lastitem == -1)
		output(_G(_sys)[NOTHING]);
	else if (_G(_options) & TI994A_STYLE) {
		if (!itemEndsWithPeriod(lastitem))
			output(".");
		output(" ");
	}
	if (_G(_transcript)) {
		glk_put_char_stream_uni(_G(_transcript), 10);
	}
}

void Scott::lookWithPause() {
	char fc = _G(_rooms)[MY_LOC]._text[0];
	if (_G(_rooms)[MY_LOC]._text.empty() || MY_LOC == 0 || fc == 0 || fc == '.' || fc == ' ')
		return;
	_G(_shouldLookInTranscript) = 1;
	_pauseNextRoomDescription = 1;
	look();
}

void Scott::doneIt() {
	if (_splitScreen && _G(_topWindow))
		look();
	output("\n\n");
	output(_G(_sys)[PLAY_AGAIN]);
	output("\n");
	if (yesOrNo()) {
		_G(_shouldRestart) = 1;
	} else {
		cleanupAndExit();
	}
}

int Scott::printScore() {
	int i = 0;
	int n = 0;
	while (i <= _G(_gameHeader)->_numItems) {
		if (_G(_items)[i]._location == _G(_gameHeader)->_treasureRoom && _G(_items)[i]._text[0] == '*')
			n++;
		i++;
	}
	display(_G(_bottomWindow), "%s %d %s%s %d.\n", _G(_sys)[IVE_STORED].c_str(), n, _G(_sys)[TREASURES].c_str(),
			_G(_sys)[ON_A_SCALE_THAT_RATES].c_str(), (n * 100) / _G(_gameHeader)->_treasures);
	if (n == _G(_gameHeader)->_treasures) {
		output(_G(_sys)[YOUVE_SOLVED_IT].c_str());
		doneIt();
		return 1;
	}
	return 0;
}

void Scott::printNoun() {
	if (_G(_currentCommand))
		glk_put_string_stream_uni(glk_window_get_stream(_G(_bottomWindow)), _G(_unicodeWords)[_G(_currentCommand)->_nounWordIndex]);
}

void Scott::moveItemAToLocOfItemB(int itemA, int itemB) {
	_G(_items)
	[itemA]._location = _G(_items)[itemB]._location;
	if (_G(_items)[itemB]._location == MY_LOC)
		_G(_shouldLookInTranscript) = 1;
}

void Scott::goToStoredLoc() {
#ifdef DEBUG_ACTIONS
	debug("switch location to stored location (%d) (%s).\n",
		  SavedRoom, _G(_rooms)[SavedRoom].Text);
#endif
	int t = MY_LOC;
	MY_LOC = _G(_savedRoom);
	_G(_savedRoom) = t;
	_G(_shouldLookInTranscript) = 1;
}

void Scott::swapLocAndRoomFlag(int index) {
#ifdef DEBUG_ACTIONS
	debug("swap location<->roomflag[%d]\n", index);
#endif
	int temp = MY_LOC;
	MY_LOC = _G(_roomSaved)[index];
	_G(_roomSaved)[index] = temp;
	_G(_shouldLookInTranscript) = 1;
	look();
}

void Scott::swapItemLocations(int itemA, int itemB) {
	int temp = _G(_items)[itemA]._location;
	_G(_items)[itemA]._location = _G(_items)[itemB]._location;
	_G(_items)[itemB]._location = temp;
	if (_G(_items)[itemA]._location == MY_LOC || _G(_items)[itemB]._location == MY_LOC)
		_G(_shouldLookInTranscript) = 1;
}

void Scott::putItemAInRoomB(int itemA, int roomB) {
#ifdef DEBUG_ACTIONS
	debug("Item %d (%s) is put in room %d (%s). MY_LOC: %d (%s)\n",
		  itemA, _G(_items)[arg1].Text, roomB, _G(_rooms)[roomB].Text, MY_LOC,
		  _G(_rooms)[MY_LOC].Text);
#endif
	if (_G(_items)[itemA]._location == MY_LOC)
		lookWithPause();
	_G(_items)[itemA]._location = roomB;
}

void Scott::swapCounters(int index) {
#ifdef DEBUG_ACTIONS
	debug("Select a counter.Current counter is swapped with backup "
		  "counter %d\n",
		  index);
#endif
	if (index > 15) {
		error("Scott::swapCounters(int index): ERROR! parameter out of range. Max 15, got %d", index);
		index = 15;
	}
	int temp = _G(_currentCounter);

	_G(_currentCounter) = _G(_counters)[index];
	_G(_counters)[index] = temp;
#ifdef DEBUG_ACTIONS
	debug("Value of new selected counter is %d\n", _G(_currentCounter));
#endif
}

void Scott::printMessage(int index) {
#ifdef DEBUG_ACTIONS
	debug("Print message %d: \"%s\"\n", index, Messages[index]);
#endif
	Common::String message = _G(_messages)[index];
	if (!message.empty() && message[0] != 0) {
		output(message);
		const char lastchar = message[message.size() - 1];
		if (lastchar != 13 && lastchar != 10)
			output(_G(_sys)[MESSAGE_DELIMITER]);
	}
}

void Scott::playerIsDead() {
#ifdef DEBUG_ACTIONS
	debug("Player is dead\n");
#endif
	output(_G(_sys)[IM_DEAD]);
	_G(_bitFlags) &= ~(1 << DARKBIT);
	MY_LOC = _G(_gameHeader)->_numRooms; /* It seems to be what the code says! */
}

void Scott::printTakenOrDropped(int index) {
	output(_G(_sys[index]));
	int length = _G(_sys)[index].size();
	char last = _G(_sys)[index][length - 1];
	if (last == 10 || last == 13)
		return;
	output(" ");
	if ((!(_G(_currentCommand)->_allFlag & LASTALL)) || _splitScreen == 0) {
		output("\n");
	}
}

void Scott::printTitleScreenBuffer() {
	glk_stream_set_current(glk_window_get_stream(_G(_bottomWindow)));
	glk_set_style(style_User1);
	clearScreen();
	output(_G(_titleScreen));
	glk_set_style(style_Normal);
	hitEnter();
	clearScreen();
}

void Scott::printTitleScreenGrid() {
	int titleLength = _G(_titleScreen).size();
	int rows = 0;
	for (int i = 0; i < titleLength; i++)
		if (_G(_titleScreen)[i] == '\n')
			rows++;
	winid_t titlewin = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Fixed, rows + 2,
									   wintype_TextGrid, 0);
	uint width, height;
	glk_window_get_size(titlewin, &width, &height);
	if (width < 40 || height < static_cast<glui32>(rows + 2)) {
		glk_window_close(titlewin, nullptr);
		printTitleScreenBuffer();
		return;
	}
	int offset = (width - 40) / 2;
	int pos = 0;
	for (int i = 1; i <= rows; i++) {
		glk_window_move_cursor(titlewin, offset, i);
		while (_G(_titleScreen)[pos] != '\n' && pos < titleLength)
			display(titlewin, "%c", _G(_titleScreen)[pos++]);
		pos++;
	}
	hitEnter();
	glk_window_close(titlewin, nullptr);
}

void writeToRoomDescriptionStream(const char *fmt, ...) {
	if (_G(_roomDescriptionStream == nullptr))
		return;
	va_list ap;

	va_start(ap, fmt);
	Common::String msg = Common::String::vformat(fmt, ap);
	va_end(ap);

	g_scott->glk_put_string_stream(_G(_roomDescriptionStream), msg.c_str());
}

} // End of namespace Scott
} // End of namespace Glk
