/* 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "glk/scott/scott.h"
#include "common/config-manager.h"
#include "common/translation.h"

namespace Glk {
namespace Scott {

Scott::Scott(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
		_currentCounter(0), _savedRoom(0), _options(0), _width(0), _topHeight(0), _splitScreen(true),
		_bottomWindow(0), _topWindow(0), _bitFlags(0), _saveSlot(-1) {
	Common::fill(&_nounText[0], &_nounText[16], '\0');
	Common::fill(&_counters[0], &_counters[16], 0);
	Common::fill(&_roomSaved[0], &_roomSaved[16], 0);
}

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

	_bottomWindow = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
	if (_bottomWindow == nullptr) {
		glk_exit();
		return;
	}
	glk_set_window(_bottomWindow);

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

	if (_splitScreen) {
		_topWindow = glk_window_open(_bottomWindow, winmethod_Above | winmethod_Fixed, _topHeight, wintype_TextGrid, 0);
		if (_topWindow == nullptr) {
			_splitScreen = 0;
			_topWindow = _bottomWindow;
		}
	} else {
		_topWindow = _bottomWindow;
	}

	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;

	// Load the game
	loadDatabase(&_gameFile, (_options & DEBUGGING) ? 1 : 0);

	// Main game loop
	while (!shouldQuit()) {
		glk_tick();

		performActions(0, 0);
		if (shouldQuit())
			break;

		if (_saveSlot >= 0) {
			// Load any savegame during startup
			loadGameState(_saveSlot);
			_saveSlot = -1;
		}

		look();

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

		switch (performActions(vb, no)) {
		case -1:
			output(_("I don't understand your command. "));
			break;
		case -2:
			output(_("I can't do that yet. "));
			break;
		default:
			break;
		}
		if (shouldQuit())
			return;

		// Brian Howarth games seem to use -1 for forever
		if (_items[LIGHT_SOURCE]._location != DESTROYED && _gameHeader._lightTime != -1) {
			_gameHeader._lightTime--;
			if (_gameHeader._lightTime < 1) {
				_bitFlags |= (1 << LIGHTOUTBIT);
				if (_items[LIGHT_SOURCE]._location == CARRIED ||
						_items[LIGHT_SOURCE]._location == MY_LOC) {
					if (_options & SCOTTLIGHT)
						output(_("Light has run out! "));
					else
						output(_("Your light has run out. "));
				}
				if (_options & PREHISTORIC_LAMP)
					_items[LIGHT_SOURCE]._location = DESTROYED;
			} else if (_gameHeader._lightTime < 25) {
				if (_items[LIGHT_SOURCE]._location == CARRIED ||
						_items[LIGHT_SOURCE]._location == MY_LOC) {

					if (_options & SCOTTLIGHT) {
						output(_("Light runs out in "));
						outputNumber(_gameHeader._lightTime);
						output(_(" turns. "));
					} else {
						if (_gameHeader._lightTime % 5 == 0)
							output(_("Your light is growing dim. "));
					}
				}
			}
		}
	}
}

void Scott::initialize() {
	if (ConfMan.hasKey("YOUARE")) {
		if (ConfMan.getBool("YOUARE"))
			_options |= YOUARE;
		else
			_options &= ~YOUARE;
	}
	if (gDebugLevel > 0)
		_options |= DEBUGGING;
	if (ConfMan.hasKey("SCOTTLIGHT") && ConfMan.getBool("SCOTTLIGHT"))
		_options |= SCOTTLIGHT;
	if (ConfMan.hasKey("TRS80_STYLE") && ConfMan.getBool("TRS80_STYLE"))
		_options |= TRS80_STYLE;
	if (ConfMan.hasKey("PREHISTORIC_LAMP") && ConfMan.getBool("PREHISTORIC_LAMP"))
		_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());
}

void Scott::delay(int seconds) {
	event_t ev;

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

	glk_request_timer_events(1000 * seconds);

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

	glk_request_timer_events(0);
}

void Scott::fatal(const char *x) {
	error("%s", x);
}

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

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

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

const char *Scott::mapSynonym(const char *word) {
	int n = 1;
	const char *tp;
	static char lastword[16];   // Last non synonym
	while (n <= _gameHeader._numWords) {
		tp = _nouns[n].c_str();
		if (*tp == '*')
			tp++;
		else
			strcpy(lastword, tp);
		if (xstrncasecmp(word, tp, _gameHeader._wordLength) == 0)
			return lastword;
		n++;
	}
	return nullptr;
}

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

	if (word == nullptr)
		word = text;

	while (ct <= _gameHeader._numItems) {
		if (!_items[ct]._autoGet.empty() && _items[ct]._location == loc &&
				xstrncasecmp(_items[ct]._autoGet.c_str(), word, _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);

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

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

	for (int idx = 0; idx < na + 1; ++idx) {
		Action &a = _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) {
		_verbs[idx] = readString(f);
		_nouns[idx] = readString(f);
	}

	if (loud)
		debug("Reading %d rooms.", nr);
	for (int idx = 0; idx < nr + 1; ++idx) {
		Room &r = _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)
		_messages[idx] = readString(f);

	if (loud)
		debug("Reading %d items.", ni);
	for (int idx = 0; idx < ni + 1; ++idx) {
		Item &i = _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(_bottomWindow, "%s", a.c_str());
}

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

void Scott::look(void) {
	const char *const ExitNames[6] = {
		_("North"), _("South"), _("East"), _("West"), _("Up"), _("Down")
	};
	Room *r;
	int ct, f;
	int pos;

	if (_splitScreen)
		glk_window_clear(_topWindow);

	if ((_bitFlags & (1 << DARKBIT)) && _items[LIGHT_SOURCE]._location != CARRIED
			&& _items[LIGHT_SOURCE]._location != MY_LOC) {
		if (_options & YOUARE)
			display(_topWindow, _("You can't see. It is too dark!\n"));
		else
			display(_topWindow, _("I can't see. It is too dark!\n"));
		if (_options & TRS80_STYLE)
			display(_topWindow, TRS80_LINE);
		return;
	}
	r = &_rooms[MY_LOC];
	if (r->_text.hasPrefix("*"))
		display(_topWindow, "%s\n", r->_text.c_str() + 1);
	else {
		if (_options & YOUARE)
			display(_topWindow, _("You are in a %s\n"), r->_text.c_str());
		else
			display(_topWindow, _("I'm in a %s\n"), r->_text.c_str());
	}

	ct = 0;
	f = 0;
	display(_topWindow, _("\nObvious exits: "));
	while (ct < 6) {
		if (r->_exits[ct] != 0) {
			if (f == 0)
				f = 1;
			else
				display(_topWindow, ", ");
			display(_topWindow, "%s", ExitNames[ct]);
		}
		ct++;
	}

	if (f == 0)
		display(_topWindow, _("none"));
	display(_topWindow, ".\n");
	ct = 0;
	f = 0;
	pos = 0;
	while (ct <= _gameHeader._numItems) {
		if (_items[ct]._location == MY_LOC) {
			if (f == 0) {
				if (_options & YOUARE) {
					display(_topWindow, _("\nYou can also see: "));
					pos = 18;
				} else {
					display(_topWindow, _("\nI can also see: "));
					pos = 16;
				}
				f++;
			} else if (!(_options & TRS80_STYLE)) {
				display(_topWindow, " - ");
				pos += 3;
			}
			if (pos + (int)_items[ct]._text.size() > (_width - 10)) {
				pos = 0;
				display(_topWindow, "\n");
			}
			display(_topWindow, "%s", _items[ct]._text.c_str());
			pos += _items[ct]._text.size();
			if (_options & TRS80_STYLE) {
				display(_topWindow, ". ");
				pos += 2;
			}
		}
		ct++;
	}

	display(_topWindow, "\n");
	if (_options & TRS80_STYLE)
		display(_topWindow, TRS80_LINE);
}

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

void Scott::lineInput(char *buf, size_t n) {
	event_t ev;

	glk_request_line_event(_bottomWindow, buf, n - 1, 0);

	do {
		glk_select(&ev);
		if (ev.type == evtype_Quit)
			return;
		else if (ev.type == evtype_LineInput)
			break;
		else if (ev.type == evtype_Arrange && _splitScreen)
			look();
	} while (ev.type != evtype_Quit);

	buf[ev.val1] = 0;
}

Common::Error Scott::saveGameData(strid_t file, const Common::String &desc) {
	Common::String msg;

	for (int ct = 0; ct < 16; ct++) {
		msg = Common::String::format("%d %d\n", _counters[ct], _roomSaved[ct]);
		glk_put_string_stream(file, msg.c_str());
	}

	msg = Common::String::format("%u %d %d %d %d %d\n",
								 _bitFlags, (_bitFlags & (1 << DARKBIT)) ? 1 : 0,
								 MY_LOC, _currentCounter, _savedRoom, _gameHeader._lightTime);
	glk_put_string_stream(file, msg.c_str());

	for (int ct = 0; ct <= _gameHeader._numItems; ct++) {
		msg = Common::String::format("%hd\n", (short)_items[ct]._location);
		glk_put_string_stream(file, msg.c_str());
	}

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

Common::Error Scott::loadGameData(strid_t file) {
	char buf[128];
	int ct = 0;
	short lo;
	short darkFlag;

	for (ct = 0; ct < 16; ct++) {
		glk_get_line_stream(file, buf, sizeof buf);
		sscanf(buf, "%d %d", &_counters[ct], &_roomSaved[ct]);
	}

	glk_get_line_stream(file, buf, sizeof buf);
	sscanf(buf, "%u %hd %d %d %d %d\n",
		   &_bitFlags, &darkFlag, &MY_LOC, &_currentCounter, &_savedRoom,
		   &_gameHeader._lightTime);

	// Backward compatibility
	if (darkFlag)
		_bitFlags |= (1 << 15);
	for (ct = 0; ct <= _gameHeader._numItems; ct++) {
		glk_get_line_stream(file, buf, sizeof buf);
		sscanf(buf, "%hd\n", &lo);
		_items[ct]._location = (unsigned char)lo;
	}

	return Common::kNoError;
}

int Scott::getInput(int *vb, int *no) {
	char buf[256];
	char verb[10], noun[10];
	int vc, nc;
	int num;

	do {
		do {
			output("\nTell me what to do ? ");
			lineInput(buf, sizeof buf);
			if (g_vm->shouldQuit())
				return 0;

			num = sscanf(buf, "%9s %9s", verb, noun);
		} while (num == 0 || *buf == '\n');

		if (xstrcasecmp(verb, "restore") == 0) {
			loadGame();
			return -1;
		}
		if (num == 1)
			*noun = 0;
		if (*noun == 0 && strlen(verb) == 1) {
			switch (Common::isUpper((unsigned char)*verb) ? tolower((unsigned char)*verb) : *verb) {
			case 'n':
				strcpy(verb, "NORTH");
				break;
			case 'e':
				strcpy(verb, "EAST");
				break;
			case 's':
				strcpy(verb, "SOUTH");
				break;
			case 'w':
				strcpy(verb, "WEST");
				break;
			case 'u':
				strcpy(verb, "UP");
				break;
			case 'd':
				strcpy(verb, "DOWN");
				break;
			// Brian Howarth interpreter also supports this
			case 'i':
				strcpy(verb, "INVENTORY");
				break;
			}
		}
		nc = whichWord(verb, _nouns);
		// The Scott Adams system has a hack to avoid typing 'go'
		if (nc >= 1 && nc <= 6) {
			vc = 1;
		} else {
			vc = whichWord(verb, _verbs);
			nc = whichWord(noun, _nouns);
		}
		*vb = vc;
		*no = nc;
		if (vc == -1) {
			output(_("You use word(s) I don't know! "));
		}
	} while (vc == -1);

	strcpy(_nounText, noun); // Needed by GET/DROP hack
	return 0;
}

int Scott::performLine(int ct) {
	int continuation = 0;
	int param[5], pptr = 0;
	int act[4];
	int cc = 0;

	while (cc < 5) {
		int cv, dv;
		cv = _actions[ct]._condition[cc];
		dv = cv / 20;
		cv %= 20;
		switch (cv) {
		case 0:
			param[pptr++] = dv;
			break;
		case 1:
			if (_items[dv]._location != CARRIED)
				return 0;
			break;
		case 2:
			if (_items[dv]._location != MY_LOC)
				return 0;
			break;
		case 3:
			if (_items[dv]._location != CARRIED &&
					_items[dv]._location != MY_LOC)
				return 0;
			break;
		case 4:
			if (MY_LOC != dv)
				return 0;
			break;
		case 5:
			if (_items[dv]._location == MY_LOC)
				return 0;
			break;
		case 6:
			if (_items[dv]._location == CARRIED)
				return 0;
			break;
		case 7:
			if (MY_LOC == dv)
				return 0;
			break;
		case 8:
			if ((_bitFlags & (1 << dv)) == 0)
				return 0;
			break;
		case 9:
			if (_bitFlags & (1 << dv))
				return 0;
			break;
		case 10:
			if (countCarried() == 0)
				return 0;
			break;
		case 11:
			if (countCarried())
				return 0;
			break;
		case 12:
			if (_items[dv]._location == CARRIED || _items[dv]._location == MY_LOC)
				return 0;
			break;
		case 13:
			if (_items[dv]._location == 0)
				return 0;
			break;
		case 14:
			if (_items[dv]._location)
				return 0;
			break;
		case 15:
			if (_currentCounter > dv)
				return 0;
			break;
		case 16:
			if (_currentCounter <= dv)
				return 0;
			break;
		case 17:
			if (_items[dv]._location != _items[dv]._initialLoc)
				return 0;
			break;
		case 18:
			if (_items[dv]._location == _items[dv]._initialLoc)
				return 0;
			break;
		case 19:
			// Only seen in Brian Howarth games so far
			if (_currentCounter != dv)
				return 0;
			break;
		}
		cc++;
	}

	// _actions
	act[0] = _actions[ct]._action[0];
	act[2] = _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) {
		if (act[cc] >= 1 && act[cc] < 52) {
			output(_messages[act[cc]]);
			output("\n");
		} else if (act[cc] > 101) {
			output(_messages[act[cc] - 50]);
			output("\n");
		} else {
			switch (act[cc]) {
			case 0:// NOP
				break;
			case 52:
				if (countCarried() == _gameHeader._maxCarry) {
					if (_options & YOUARE)
						output(_("You are carrying too much. "));
					else
						output(_("I've too much to carry! "));
					break;
				}
				_items[param[pptr++]]._location = CARRIED;
				break;
			case 53:
				_items[param[pptr++]]._location = MY_LOC;
				break;
			case 54:
				MY_LOC = param[pptr++];
				break;
			case 55:
				_items[param[pptr++]]._location = 0;
				break;
			case 56:
				_bitFlags |= 1 << DARKBIT;
				break;
			case 57:
				_bitFlags &= ~(1 << DARKBIT);
				break;
			case 58:
				_bitFlags |= (1 << param[pptr++]);
				break;
			case 59:
				_items[param[pptr++]]._location = 0;
				break;
			case 60:
				_bitFlags &= ~(1 << param[pptr++]);
				break;
			case 61:
				if (_options & YOUARE)
					output(_("You are dead.\n"));
				else
					output(_("I am dead.\n"));
				_bitFlags &= ~(1 << DARKBIT);
				MY_LOC = _gameHeader._numRooms;// It seems to be what the code says!
				break;
			case 62: {
				// Bug fix for some systems - before it could get parameters wrong */
				int i = param[pptr++];
				_items[i]._location = param[pptr++];
				break;
			}
			case 63:
doneit:
				output(_("The game is now over.\n"));
				glk_exit();
				return 0;
			case 64:
				break;
			case 65: {
				int i = 0;
				int n = 0;
				while (i <= _gameHeader._numItems) {
					if (_items[i]._location == _gameHeader._treasureRoom &&
							_items[i]._text.hasPrefix("*"))
						n++;
					i++;
				}
				if (_options & YOUARE)
					output(_("You have stored "));
				else
					output(_("I've stored "));
				outputNumber(n);
				output(_(" treasures.  On a scale of 0 to 100, that rates "));
				outputNumber((n * 100) / _gameHeader._treasures);
				output(".\n");
				if (n == _gameHeader._treasures) {
					output(_("Well done.\n"));
					goto doneit;
				}
				break;
			}
			case 66: {
				int i = 0;
				int f = 0;
				if (_options & YOUARE)
					output(_("You are carrying:\n"));
				else
					output(_("I'm carrying:\n"));
				while (i <= _gameHeader._numItems) {
					if (_items[i]._location == CARRIED) {
						if (f == 1) {
							if (_options & TRS80_STYLE)
								output(". ");
							else
								output(" - ");
						}
						f = 1;
						output(_items[i]._text);
					}
					i++;
				}
				if (f == 0)
					output(_("Nothing"));
				output(".\n");
				break;
			}
			case 67:
				_bitFlags |= (1 << 0);
				break;
			case 68:
				_bitFlags &= ~(1 << 0);
				break;
			case 69:
				_gameHeader._lightTime = _lightRefill;
				_items[LIGHT_SOURCE]._location = CARRIED;
				_bitFlags &= ~(1 << LIGHTOUTBIT);
				break;
			case 70:
				clearScreen(); // pdd.
				break;
			case 71:
				saveGame();
				break;
			case 72: {
				int i1 = param[pptr++];
				int i2 = param[pptr++];
				int t = _items[i1]._location;
				_items[i1]._location = _items[i2]._location;
				_items[i2]._location = t;
				break;
			}
			case 73:
				continuation = 1;
				break;
			case 74:
				_items[param[pptr++]]._location = CARRIED;
				break;
			case 75: {
				int i1, i2;
				i1 = param[pptr++];
				i2 = param[pptr++];
				_items[i1]._location = _items[i2]._location;
				break;
			}
			case 76:
				// Looking at adventure ..
				break;
			case 77:
				if (_currentCounter >= 0)
					_currentCounter--;
				break;
			case 78:
				outputNumber(_currentCounter);
				break;
			case 79:
				_currentCounter = param[pptr++];
				break;
			case 80: {
				int t = MY_LOC;
				MY_LOC = _savedRoom;
				_savedRoom = t;
				break;
			}
			case 81: {
				// This is somewhat guessed. Claymorgue always seems to do
				// select counter n, thing, select counter n, but uses one value that always
				// seems to exist. Trying a few options I found this gave sane results on ageing
				int t = param[pptr++];
				int c1 = _currentCounter;
				_currentCounter = _counters[t];
				_counters[t] = c1;
				break;
			}
			case 82:
				_currentCounter += param[pptr++];
				break;
			case 83:
				_currentCounter -= param[pptr++];
				if (_currentCounter < -1)
					_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:
				output(_nounText);
				break;
			case 85:
				output(_nounText);
				output("\n");
				break;
			case 86:
				output("\n");
				break;
			case 87: {
				// Changed this to swap location<->roomflag[x] not roomflag 0 and x
				int p = param[pptr++];
				int sr = MY_LOC;
				MY_LOC = _roomSaved[p];
				_roomSaved[p] = sr;
				break;
			}
			case 88:
				delay(2);
				break;
			case 89:
				pptr++;
				// SAGA draw picture n
				// Spectrum Seas of Blood - start combat ?
				// Poking this into older spectrum games causes a crash
				break;
			default:
				error("Unknown action %d [Param begins %d %d]\n",
					  act[cc], param[pptr], param[pptr + 1]);
				break;
			}
		}

		cc++;
	}

	return 1 + continuation;
}

int Scott::performActions(int vb, int no) {
	static bool disableSysFunc = false; // Recursion lock
	int d = _bitFlags & (1 << DARKBIT);

	int ct = 0;
	int fl;
	int doagain = 0;
	if (vb == 1 && no == -1) {
		output(_("Give me a direction too."));
		return 0;
	}
	if (vb == 1 && no >= 1 && no <= 6) {
		int nl;
		if (_items[LIGHT_SOURCE]._location == MY_LOC ||
				_items[LIGHT_SOURCE]._location == CARRIED)
			d = 0;
		if (d)
			output(_("Dangerous to move in the dark! "));
		nl = _rooms[MY_LOC]._exits[no - 1];
		if (nl != 0) {
			MY_LOC = nl;
			return 0;
		}
		if (d) {
			if (_options & YOUARE)
				output(_("You fell down and broke your neck. "));
			else
				output(_("I fell down and broke my neck. "));
			glk_exit();
			return 0;
		}
		if (_options & YOUARE)
			output(_("You can't go in that direction. "));
		else
			output(_("I can't go in that direction. "));
		return 0;
	}

	fl = -1;
	while (ct <= _gameHeader._numActions) {
		int vv, nv;
		vv = _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 && vv != 0))
			break;
		// Oops.. added this minor cockup fix 1.11
		if (vb != 0 && !doagain && fl == 0)
			break;
		nv = vv % 150;
		vv /= 150;
		if ((vv == vb) || (doagain && _actions[ct]._vocab == 0)) {
			if ((vv == 0 && randomPercent(nv)) || doagain ||
					(vv != 0 && (nv == no || nv == 0))) {
				int f2;
				if (fl == -1)
					fl = -2;
				if ((f2 = performLine(ct)) > 0) {
					// ahah finally figured it out !
					fl = 0;
					if (f2 == 2)
						doagain = 1;
					if (vb != 0 && doagain == 0)
						return 0;
				}

				if (shouldQuit())
					return 0;
			}
		}
		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 <= _gameHeader._numActions && _actions[ct]._vocab != 0)
			doagain = 0;
	}
	if (fl != 0 && disableSysFunc == 0) {
		int item;
		if (_items[LIGHT_SOURCE]._location == MY_LOC ||
				_items[LIGHT_SOURCE]._location == CARRIED)
			d = 0;
		if (vb == 10 || vb == 18) {
			// Yes they really _are_ hardcoded values
			if (vb == 10) {
				if (xstrcasecmp(_nounText, "ALL") == 0) {
					int i = 0;
					int f = 0;

					if (d) {
						output(_("It is dark.\n"));
						return 0;
					}
					while (i <= _gameHeader._numItems) {
						if (_items[i]._location == MY_LOC && _items[i]._autoGet != nullptr && _items[i]._autoGet[0] != '*') {
							no = whichWord(_items[i]._autoGet.c_str(), _nouns);
							disableSysFunc = true;    // Don't recurse into auto get !
							performActions(vb, no);   // Recursively check each items table code
							disableSysFunc = false;
							if (shouldQuit())
								return 0;

							if (countCarried() == _gameHeader._maxCarry) {
								if (_options & YOUARE)
									output(_("You are carrying too much. "));
								else
									output(_("I've too much to carry. "));
								return 0;
							}
							_items[i]._location = CARRIED;
							output(_items[i]._text);
							output(_(": O.K.\n"));
							f = 1;
						}
						i++;
					}
					if (f == 0)
						output(_("Nothing taken."));
					return 0;
				}
				if (no == -1) {
					output(_("What ? "));
					return 0;
				}
				if (countCarried() == _gameHeader._maxCarry) {
					if (_options & YOUARE)
						output(_("You are carrying too much. "));
					else
						output(_("I've too much to carry. "));
					return 0;
				}
				item = matchUpItem(_nounText, MY_LOC);
				if (item == -1) {
					if (_options & YOUARE)
						output(_("It is beyond your power to do that. "));
					else
						output(_("It's beyond my power to do that. "));
					return 0;
				}
				_items[item]._location = CARRIED;
				output(_("O.K. "));
				return 0;
			}
			if (vb == 18) {
				if (xstrcasecmp(_nounText, "ALL") == 0) {
					int i = 0;
					int f = 0;
					while (i <= _gameHeader._numItems) {
						if (_items[i]._location == CARRIED && !_items[i]._autoGet.empty()
								&& !_items[i]._autoGet.hasPrefix("*")) {
							no = whichWord(_items[i]._autoGet.c_str(), _nouns);
							disableSysFunc = true;
							performActions(vb, no);
							disableSysFunc = false;
							if (shouldQuit())
								return 0;

							_items[i]._location = MY_LOC;
							output(_items[i]._text);
							output(_(": O.K.\n"));
							f = 1;
						}
						i++;
					}
					if (f == 0)
						output(_("Nothing dropped.\n"));
					return 0;
				}
				if (no == -1) {
					output(_("What ? "));
					return 0;
				}
				item = matchUpItem(_nounText, CARRIED);
				if (item == -1) {
					if (_options & YOUARE)
						output(_("It's beyond your power to do that.\n"));
					else
						output(_("It's beyond my power to do that.\n"));
					return 0;
				}
				_items[item]._location = MY_LOC;
				output("O.K. ");
				return 0;
			}
		}
	}

	return fl;
}

int Scott::xstrcasecmp(const char *s1, const char *s2) {
	const unsigned char
	*us1 = (const unsigned char *)s1,
	 *us2 = (const unsigned char *)s2;

	while (tolower(*us1) == tolower(*us2++))
		if (*us1++ == '\0')
			return (0);
	return (tolower(*us1) - tolower(*--us2));
}

int Scott::xstrncasecmp(const char *s1, const char *s2, size_t n) {
	if (n != 0) {
		const unsigned char
		*us1 = (const unsigned char *)s1,
		 *us2 = (const unsigned char *)s2;

		do {
			if (tolower(*us1) != tolower(*us2++))
				return (tolower(*us1) - tolower(*--us2));
			if (*us1++ == '\0')
				break;
		} while (--n != 0);
	}

	return 0;
}

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);
}

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