/* 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 "common/file.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "common/translation.h"

#include "gui/message.h"

#include "agos/agos.h"
#include "agos/intern.h"

namespace AGOS {


// FIXME: This code counts savegames, but callers in many cases assume
// that the return value + 1 indicates an empty slot.
int AGOSEngine::countSaveGames() {
	Common::StringArray filenames;
	uint s, numSaveGames = 1;
	int slotNum;
	bool marks[256];

	// Get the name of (possibly non-existent) savegame slot 998, and replace
	// the extension by * to get a pattern.
	Common::String tmp = genSaveName(998);
	assert(tmp.size() >= 4 && tmp[tmp.size()-4] == '.');
	Common::String prefix = Common::String(tmp.c_str(), tmp.size()-3) + "*";

	memset(marks, false, 256 * sizeof(bool));	//assume no savegames for this title
	filenames = _saveFileMan->listSavefiles(prefix);

	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file){
		//Obtain the last 3 digits of the filename, since they correspond to the save slot
		assert(file->size() >= 4);
		slotNum = atoi(file->c_str() + file->size() - 3);
		if (slotNum >= 0 && slotNum < 256)
			marks[slotNum] = true;	//mark this slot as valid
	}

	// locate first empty slot
	for (s = 1; s < 256; s++) {
		if (marks[s])
			numSaveGames++;
	}

	return numSaveGames;
}

#ifdef ENABLE_AGOS2
Common::String AGOSEngine_PuzzlePack::genSaveName(int slot) const {
	if (getGameId() == GID_DIMP)
		return "dimp.sav";
	else
		return "swampy.sav";
}

Common::String AGOSEngine_Feeble::genSaveName(int slot) const {
	return Common::String::format("feeble.%.3d", slot);
}
#endif

Common::String AGOSEngine_Simon2::genSaveName(int slot) const {
	return Common::String::format("simon2.%.3d", slot);
}

Common::String AGOSEngine_Simon1::genSaveName(int slot) const {
	return Common::String::format("simon1.%.3d", slot);
}

Common::String AGOSEngine_Waxworks::genSaveName(int slot) const {
	if (getPlatform() == Common::kPlatformDOS)
		return Common::String::format("waxworks-pc.%.3d", slot);
	else
		return Common::String::format("waxworks.%.3d", slot);
}

Common::String AGOSEngine_Elvira2::genSaveName(int slot) const {
	if (getPlatform() == Common::kPlatformDOS)
		return Common::String::format("elvira2-pc.%.3d", slot);
	else
		return Common::String::format("elvira2.%.3d", slot);
}

Common::String AGOSEngine_Elvira1::genSaveName(int slot) const {
	return Common::String::format("elvira1.%.3d", slot);
}

Common::String AGOSEngine::genSaveName(int slot) const {
	return Common::String::format("pn.%.3d", slot);
}

#ifdef ENABLE_AGOS2
void AGOSEngine_Feeble::quickLoadOrSave() {
	// Quick loading and saving isn't possible in The Feeble Files or Puzzle Pack.
}
#endif

// The function uses segments of code from the original game scripts
// to allow quick loading and saving, but isn't perfect.
//
// Unfortuntely this allows loading and saving in locations,
// which aren't supported, and will not restore correctly:
// Various locations in Elvira 1/2 and Waxworks where saving
// was disabled
void AGOSEngine::quickLoadOrSave() {
	bool success;
	Common::String buf;

	// Disable loading and saving when it was not possible in the original:
	// In overhead maps areas in Simon the Sorcerer 2
	// In the floppy disk demo of Simon the Sorcerer 1
	// In copy protection, conversations and cut scenes
	if ((getGameType() == GType_SIMON2 && _boxStarHeight == 200) ||
		(getGameType() == GType_SIMON1 && (getFeatures() & GF_DEMO)) ||
		_mouseHideCount || _showPreposition) {
		buf = Common::String::format("Quick load or save game isn't supported in this location");
		GUI::MessageDialog dialog(buf, "OK");
		dialog.runModal();
		return;
	}

	// Check if Simon is walking, and stop when required
	if (getGameType() == GType_SIMON1 && getBitFlag(11)) {
		vcStopAnimation(11, 1122);
		animate(4, 11, 1122, 0, 0, 2);
		waitForSync(1122);
	} else if (getGameType() == GType_SIMON2 && getBitFlag(11)) {
		vcStopAnimation(11, 232);
		animate(4, 11, 232, 0, 0, 2);
		waitForSync(1122);
	}

	Common::String filename = genSaveName(_saveLoadSlot);
	if (_saveLoadType == 2) {
		Subroutine *sub;
		success = loadGame(genSaveName(_saveLoadSlot));
		if (!success) {
			buf = Common::String::format(_("Failed to load saved game from file:\n\n%s"), filename.c_str());
		} else if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) {
			drawIconArray(2, me(), 0, 0);
			setBitFlag(97, true);
			sub = getSubroutineByID(100);
			startSubroutine(sub);
		} else if (getGameType() == GType_WW) {
			sub = getSubroutineByID(66);
			startSubroutine(sub);
		} else if (getGameType() == GType_ELVIRA2) {
			sub = getSubroutineByID(87);
			startSubroutine(sub);
			setBitFlag(7, false);
			sub = getSubroutineByID(19);
			startSubroutine(sub);
			printStats();
			sub = getSubroutineByID(28);
			startSubroutine(sub);
			setBitFlag(17, false);
			sub = getSubroutineByID(207);
			startSubroutine(sub);
			sub = getSubroutineByID(71);
			startSubroutine(sub);
		} else if (getGameType() == GType_ELVIRA1) {
			drawIconArray(2, me(), 0, 0);
			sub = getSubroutineByID(265);
			startSubroutine(sub);
			sub = getSubroutineByID(129);
			startSubroutine(sub);
			sub = getSubroutineByID(131);
			startSubroutine(sub);
		}
	} else {
		success = saveGame(_saveLoadSlot, _saveLoadName);
		if (!success)
			buf = Common::String::format(_("Failed to save game to file:\n\n%s"), filename.c_str());
	}

	if (!success) {
		GUI::MessageDialog dialog(buf, "OK");
		dialog.runModal();

	} else if (_saveLoadType == 1) {
		buf = Common::String::format(_("Successfully saved game in file:\n\n%s"), filename.c_str());
		GUI::TimedMessageDialog dialog(buf, 1500);
		dialog.runModal();

	}

	_saveLoadType = 0;
}

bool AGOSEngine_Waxworks::confirmOverWrite(WindowBlock *window) {
	Subroutine *sub = getSubroutineByID(80);
	if (sub != NULL)
		startSubroutineEx(sub);

	if (_variableArray[253] == 0)
		return true;

	return false;
}

bool AGOSEngine_Elvira2::confirmOverWrite(WindowBlock *window) {
	// Original verison never confirmed
	return true;
}

bool AGOSEngine::confirmOverWrite(WindowBlock *window) {
	const char *message1, *message2, *message3;

	switch (_language) {
	case Common::FR_FRA:
		message1 = "\rFichier d/j; existant.\r\r";
		message2 = "  Ecrire pardessus ?\r\r";
		message3 = "     Oui      Non";
		break;
	case Common::DE_DEU:
		message1 = "\rDatei existiert bereits.\r\r";
		message2 = "   Ueberschreiben ?\r\r";
		message3 = "     Ja        Nein";
		break;
	default:
		message1 = "\r File already exists.\r\r";
		message2 = "    Overwrite it ?\r\r";
		message3 = "     Yes       No";
		break;
	}

	printScroll();
	window->textColumn = 0;
	window->textRow = 0;
	window->textColumnOffset = 0;
	window->textLength = 0;		// Difference

	for (; *message1; message1++)
		windowPutChar(window, *message1);
	for (; *message2; message2++)
		windowPutChar(window, *message2);
	for (; *message3; message3++)
		windowPutChar(window, *message3);

	if (confirmYesOrNo(120, 78) == 0x7FFF)
		return true;

	return false;
}

int16 AGOSEngine::matchSaveGame(const char *name, uint16 max) {
	Common::InSaveFile *in;
	char dst[10];
	uint16 slot;

	memset(dst, 0, sizeof(dst));
	for (slot = 0; slot < max; slot++) {
		if ((in = _saveFileMan->openForLoading(genSaveName(slot)))) {
			in->read(dst, 8);
			delete in;

			if (!scumm_stricmp(name, dst)) {
				return slot;
			}
		}
	}

	return -1;
}

void AGOSEngine::userGame(bool load) {
	WindowBlock *window = _windowArray[4];
	const char *message1;
	int i = 0, numSaveGames;
	char *name;
	memset(_saveBuf, 0, sizeof(_saveBuf));

	numSaveGames = countSaveGames();

	uint32 saveTime = getTime();
	haltAnimation();

restart:
	printScroll();
	window->textColumn = 0;
	window->textRow = 0;
	window->textColumnOffset = 0;
	window->textLength = 0;		// Difference

	switch (_language) {
	case Common::FR_FRA:
		message1 = "\rIns/rez disquette de\rsauvegarde de jeux &\rentrez nom de fichier:\r\r   ";
		break;
	case Common::DE_DEU:
		message1 = "\rLege Spielstandsdiskette ein. Dateinamen eingeben:\r\r   ";
		break;
	default:
		message1 = "\r Insert savegame data disk & enter filename:\r\r   ";
		break;
	}

	for (; *message1; message1++)
		windowPutChar(window, *message1);

	memset(_saveBuf, 0, 10);
	name = _saveBuf;
	_saveGameNameLen = 0;

	while (!shouldQuit()) {
		windowPutChar(window, 128);
		_keyPressed.reset();

		while (!shouldQuit()) {
			delay(10);
			if (_keyPressed.ascii && _keyPressed.ascii < 128) {
				i = _keyPressed.ascii;
				break;
			}
		}

		userGameBackSpace(_windowArray[4], 8);
		if (i == 10 || i == 13) {
			break;
		} else if (i == 8) {
			// do_backspace
			if (_saveGameNameLen) {
				_saveGameNameLen--;
				name[_saveGameNameLen] = 0;
				userGameBackSpace(_windowArray[4], 8);
			}
		} else if (i >= 32 && _saveGameNameLen != 8) {
			name[_saveGameNameLen++] = i;
			windowPutChar(_windowArray[4], i);
		}
	}

	if (_saveGameNameLen != 0) {
		int16 slot = matchSaveGame(name, numSaveGames);
		if (!load) {
			if (slot >= 0 && !confirmOverWrite(window))
				goto restart;

			if (slot < 0)
				slot = numSaveGames;

			if (!saveGame(slot, name))
				fileError(_windowArray[4], true);
		} else {
			if (slot < 0) {
				fileError(_windowArray[4], false);
			} else {
				if (!loadGame(genSaveName(slot)))
					fileError(_windowArray[4], false);
			}
		}

		printStats();
	}

	restartAnimation();
	_gameStoppedClock = getTime() - saveTime + _gameStoppedClock;
}

void AGOSEngine_Elvira2::listSaveGames() {
	Common::InSaveFile *in;
	uint y, slot;
	char *dst = _saveBuf;

	const uint8 num = (getGameType() == GType_WW) ? 3 : 4;

	disableFileBoxes();

	WindowBlock *window = _windowArray[num];
	window->textRow = 0;
	window->textColumn = 0;
	window->textColumnOffset = 4;

	windowPutChar(window, 12);

	memset(dst, 0, 200);

	slot = _saveLoadRowCurPos;
	for (y = 0; y < 8; y++) {
		window->textColumn = 0;
		window->textColumnOffset = (getGameType() == GType_ELVIRA2) ? 4 : 0;
		window->textLength = 0;
		if ((in = _saveFileMan->openForLoading(genSaveName(slot++)))) {
			in->read(dst, 8);
			delete in;

			const char *name = dst;
			for (; *name; name++)
				windowPutChar(window, *name);

			enableBox(200 + y * 3 + 0);
		}
		dst+= 8;

		if (getGameType() == GType_WW) {
			window->textColumn = 7;
			window->textColumnOffset = 4;
		} else if (getGameType() == GType_ELVIRA2) {
			window->textColumn = 8;
			window->textColumnOffset = 0;
		}
		window->textLength = 0;
		if ((in = _saveFileMan->openForLoading(genSaveName(slot++)))) {
			in->read(dst, 8);
			delete in;

			const char *name = dst;
			for (; *name; name++)
				windowPutChar(window, *name);

			enableBox(200 + y * 3 + 1);
		}
		dst+= 8;

		window->textColumn = 15;
		window->textColumnOffset = (getGameType() == GType_ELVIRA2) ? 4 : 0;
		window->textLength = 0;
		if ((in = _saveFileMan->openForLoading(genSaveName(slot++)))) {
			in->read(dst, 8);
			delete in;

			const char *name = dst;
			for (; *name; name++)
				windowPutChar(window, *name);

			enableBox(200 + y * 3 + 2);
		}
		dst+= 8;

		windowPutChar(window, 13);
	}

	window->textRow = 9;
	window->textColumn = 0;
	window->textColumnOffset = 4;
	window->textLength = 0;

	_saveGameNameLen = 0;
}

void AGOSEngine_Elvira2::userGame(bool load) {
	uint32 saveTime;
	int i, numSaveGames;
	char *name;
	bool b;
	memset(_saveBuf, 0, sizeof(_saveBuf));

	_saveOrLoad = load;

	saveTime = getTime();

	if (getGameType() == GType_ELVIRA2)
		haltAnimation();

	numSaveGames = countSaveGames();
	_numSaveGameRows = numSaveGames;
	_saveLoadRowCurPos = 1;
	_saveLoadEdit = false;

	const uint8 num = (getGameType() == GType_WW) ? 3 : 4;

	listSaveGames();

	if (!load) {
		WindowBlock *window = _windowArray[num];
		int16 slot = -1;

		name = _saveBuf + 192;

		while (!shouldQuit()) {
			windowPutChar(window, 128);

			_saveLoadEdit = true;

			i = userGameGetKey(&b, 128);
			if (b) {
				if (i <= 23) {
					if (!confirmOverWrite(window)) {
						listSaveGames();
						continue;
					}

					if (!saveGame(_saveLoadRowCurPos + i, _saveBuf + i * 8))
						fileError(_windowArray[num], true);
				}

				goto get_out;
			}

			userGameBackSpace(_windowArray[num], 8);
			if (i == 10 || i == 13) {
				slot = matchSaveGame(name, numSaveGames);
				if (slot >= 0) {
					if (!confirmOverWrite(window)) {
						listSaveGames();
						continue;
					}
				}
				break;
			} else if (i == 8) {
				// do_backspace
				if (_saveGameNameLen) {
					_saveGameNameLen--;
					name[_saveGameNameLen] = 0;
					userGameBackSpace(_windowArray[num], 8);
				}
			} else if (i >= 32 && _saveGameNameLen != 8) {
				name[_saveGameNameLen++] = i;
				windowPutChar(_windowArray[num], i);
			}
		}

		if (_saveGameNameLen != 0) {
			if (slot < 0)
				slot = numSaveGames;

			if (!saveGame(slot, _saveBuf + 192))
				fileError(_windowArray[num], true);
		}
	} else {
		i = userGameGetKey(&b, 128);
		if (i != 225) {
			if (!loadGame(genSaveName(_saveLoadRowCurPos + i)))
				fileError(_windowArray[num], false);
		}
	}

get_out:;
	disableFileBoxes();

	_gameStoppedClock = getTime() - saveTime + _gameStoppedClock;

	if (getGameType() == GType_ELVIRA2)
		restartAnimation();
}

int AGOSEngine_Elvira2::userGameGetKey(bool *b, uint maxChar) {
	HitArea *ha;
	*b = true;

	_keyPressed.reset();

	while (!shouldQuit()) {
		_lastHitArea = NULL;
		_lastHitArea3 = NULL;

		do {
			if (_saveLoadEdit && _keyPressed.ascii && _keyPressed.ascii < maxChar) {
				*b = false;
				return _keyPressed.ascii;
			}
			delay(10);
		} while (_lastHitArea3 == 0 && !shouldQuit());

		ha = _lastHitArea;
		if (ha == NULL || ha->id < 200) {
		} else if (ha->id == 225) {
			return ha->id;
		} else if (ha->id == 224) {
			_saveGameNameLen = 0;
			_saveLoadRowCurPos += 24;
			if (_saveLoadRowCurPos >= _numSaveGameRows)
				_saveLoadRowCurPos = 1;

			listSaveGames();
		} else if (ha->id < 224) {
			return ha->id - 200;
		}
	}

	return 225;
}

void AGOSEngine_Simon1::listSaveGames() {
	Common::InSaveFile *in;
	uint16 i, slot, lastSlot;
	char *dst = _saveBuf;

	disableFileBoxes();

	showMessageFormat("\xC");

	memset(dst, 0, 108);

	slot = _saveLoadRowCurPos;
	while (_saveLoadRowCurPos + 6 > slot) {
		if (!(in = _saveFileMan->openForLoading(genSaveName(slot))))
			break;

		in->read(dst, 18);
		delete in;

		lastSlot = slot;
		if (slot < 10) {
			showMessageFormat(" ");
		} else if (_language == Common::HE_ISR) {
			lastSlot = (slot % 10) * 10;
			lastSlot += slot / 10;
		}

		showMessageFormat("%d", lastSlot);
		if (_language == Common::HE_ISR && !(slot % 10))
			showMessageFormat("0");
		showMessageFormat(".%s\n", dst);
		dst += 18;
		slot++;
	}

	if (!_saveOrLoad) {
		if (_saveLoadRowCurPos + 6 == slot) {
			slot++;
		} else {
			if (slot < 10)
				showMessageFormat(" ");
			showMessageFormat("%d.\n", slot);
		}
	} else {
		if (_saveLoadRowCurPos + 6 == slot) {
			if ((in = _saveFileMan->openForLoading(genSaveName(slot)))) {
				slot++;
				delete in;
			}
		}
	}

	_saveDialogFlag = true;

	i = slot - _saveLoadRowCurPos;
	if (i != 7) {
		i++;
		if (!_saveOrLoad)
			i++;
		_saveDialogFlag = false;
	}

	if (!--i)
		return;

	do {
		enableBox(208 + i - 1);
	} while (--i);
}

const byte hebrewKeyTable[96] = {
	32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 90, 45, 85, 47, 48, 49, 50,
	51, 52, 53, 54, 55, 56, 57, 83, 83, 90, 61, 85, 63, 35, 89, 80, 65, 66, 87,
	75, 82, 73, 79, 71, 76, 74, 86, 78, 77, 84, 47, 88, 67, 64, 69, 68, 44, 81,
	72, 70, 91, 92, 93, 94, 95, 96, 89, 80, 65, 66, 87, 75, 82, 73, 79, 71, 76,
	74, 86, 78, 77, 84, 47, 88, 67, 64, 69, 68, 44, 81, 72, 70,
	123, 124, 125, 126, 127,
};

void AGOSEngine_Simon1::userGame(bool load) {
	uint32 saveTime;
	int i, numSaveGames, result;
	WindowBlock *window;
	char *name;
	bool b;
	memset(_saveBuf, 0, sizeof(_saveBuf));
	int maxChar = (_language == Common::HE_ISR) ? 155: 128;

	_saveOrLoad = load;

	saveTime = getTime();

	numSaveGames = countSaveGames();
	if (!load)
		numSaveGames++;
	numSaveGames -= 6;
	if (numSaveGames < 0)
		numSaveGames = 0;
	numSaveGames++;
	_numSaveGameRows = numSaveGames;

	_saveLoadRowCurPos = 1;
	if (!load)
		_saveLoadRowCurPos = numSaveGames;

	_saveLoadEdit = false;

restart:;
	i = userGameGetKey(&b, maxChar);

	if (i == 205)
		goto get_out;
	if (!load) {
		// if_1
	if_1:;
		result = i;

		disableBox(208 + i);
		leaveHitAreaById(208 + i);

		window = _windowArray[5];

		window->textRow = result;

		// init x offset with a 2 character savegame number + a period (18 pix)
		if (_language == Common::HE_ISR) {
			window->textColumn = 3;
			window->textColumnOffset = 6;
		} else {
			window->textColumn = 2;
			window->textColumnOffset = 2;
		}
		window->textLength = 3;

		name = _saveBuf + i * 18;

		// now process entire savegame name to get correct x offset for cursor
		_saveGameNameLen = 0;
		while (name[_saveGameNameLen]) {
			if (_language == Common::HE_ISR) {
				byte width = 6;
				if (name[_saveGameNameLen] >= 64 && name[_saveGameNameLen] < 91)
					width = _hebrewCharWidths [name[_saveGameNameLen] - 64];
				window->textLength++;
				window->textColumnOffset -= width;
				if (window->textColumnOffset < width) {
					window->textColumnOffset += 8;
					window->textColumn++;
				}
			} else {
				window->textLength++;
				window->textColumnOffset += 6;
				if (name[_saveGameNameLen] == 'i' || name[_saveGameNameLen] == 'l')
					window->textColumnOffset -= 2;
				if (window->textColumnOffset >= 8) {
					window->textColumnOffset -= 8;
					window->textColumn++;
				}
			}
			_saveGameNameLen++;
		}

		while (!shouldQuit()) {
			windowPutChar(window, 127);

			_saveLoadEdit = true;

			i = userGameGetKey(&b, maxChar);

			if (b) {
				if (i == 205)
					goto get_out;
				enableBox(208 + result);
				if (_saveLoadEdit) {
					userGameBackSpace(_windowArray[5], 8);
				}
				goto if_1;
			}

			if (!_saveLoadEdit) {
				enableBox(208 + result);
				goto restart;
			}

			if (_language == Common::HE_ISR) {
				if (i >= 128)
					i -= 64;
				else if (i >= 32)
					i = hebrewKeyTable[i - 32];
			}

			userGameBackSpace(_windowArray[5], 8);
			if (i == 10 || i == 13) {
				break;
			} else if (i == 8) {
				// do_backspace
				if (_saveGameNameLen) {
					byte m, x;

					_saveGameNameLen--;
					m = name[_saveGameNameLen];

					if (_language == Common::HE_ISR)
						x = 8;
					else
						x = (name[_saveGameNameLen] == 'i' || name[_saveGameNameLen] == 'l') ? 1 : 8;

					name[_saveGameNameLen] = 0;

					userGameBackSpace(_windowArray[5], x, m);
				}
			} else if (i >= 32 && _saveGameNameLen != 17) {
				name[_saveGameNameLen++] = i;

				windowPutChar(_windowArray[5], i);
			}
		}

		if (!saveGame(_saveLoadRowCurPos + result, _saveBuf + result * 18))
			fileError(_windowArray[5], true);
	} else {
		if (!loadGame(genSaveName(_saveLoadRowCurPos + i)))
			fileError(_windowArray[5], false);
	}

get_out:;
	disableFileBoxes();

	_gameStoppedClock = getTime() - saveTime + _gameStoppedClock;
}

int AGOSEngine_Simon1::userGameGetKey(bool *b, uint maxChar) {
	HitArea *ha;
	*b = true;

	if (!_saveLoadEdit) {
		listSaveGames();
	}

	_keyPressed.reset();

	while (!shouldQuit()) {
		_lastHitArea = NULL;
		_lastHitArea3 = NULL;

		do {
			if (_saveLoadEdit && _keyPressed.ascii && _keyPressed.ascii < maxChar) {
				*b = false;
				return _keyPressed.ascii;
			}
			delay(10);
		} while (_lastHitArea3 == 0 && !shouldQuit());

		ha = _lastHitArea;
		if (ha == NULL || ha->id < 205) {
		} else if (ha->id == 205) {
			return ha->id;
		} else if (ha->id == 206) {
			if (_saveLoadRowCurPos != 1) {
				if (_saveLoadRowCurPos < 7)
					_saveLoadRowCurPos = 1;
				else
					_saveLoadRowCurPos -= 6;

				_saveLoadEdit = false;
				listSaveGames();
			}
		} else if (ha->id == 207) {
			if (_saveDialogFlag) {
				_saveLoadRowCurPos += 6;
				if (_saveLoadRowCurPos >= _numSaveGameRows)
					_saveLoadRowCurPos = _numSaveGameRows;

				_saveLoadEdit = false;
				listSaveGames();
			}
		} else if (ha->id < 214) {
			return ha->id - 208;
		}
	}

	return 205;
}

void AGOSEngine::disableFileBoxes() {
	int i;

	if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) {
		for (i = 208; i != 214; i++)
			disableBox(i);
	} else {
		for (i = 200; i != 224; i++)
			disableBox(i);
	}
}

void AGOSEngine::userGameBackSpace(WindowBlock *window, int x, byte b) {
	byte oldTextColor;

	windowPutChar(window, x, b);
	oldTextColor = window->textColor;
	window->textColor = window->fillColor;

	if (_language == Common::HE_ISR) {
		x = 128;
	} else {
		x += 120;
		if (x != 128)
			x = 129;
	}

	windowPutChar(window, x);

	window->textColor = oldTextColor;
	windowPutChar(window, 8);
}

void AGOSEngine::fileError(WindowBlock *window, bool saveError) {
	const char *message1, *message2;

	if (saveError) {
		switch (_language) {
		case Common::RU_RUS:
			if (getGameType() == GType_SIMON2) {
				message1 = "\r   Mf sowrap+fts+.";
				message2 = "\r  Nzjb#a ejs#a.";
			} else {
				message1 = "\r   Mf sowrap]fts].";
				message2 = "\r   Nzjb_a ejs_a.";
			}
			break;
		case Common::PL_POL:
			message1 = "\r      Blad zapisu.    ";
			message2 = "\rBlad dysku.                       ";
			break;
		case Common::ES_ESP:
			message1 = "\r     Error al salvar";
			message2 = "\r  Intenta con otro disco";
			break;
		case Common::IT_ITA:
			message1 = "\r  Salvataggio non riuscito";
			message2 = "\r    Prova un""\x27""altro disco";
			break;
		case Common::FR_FRA:
			message1 = "\r    Echec sauvegarde";
			message2 = "\rEssayez une autre disquette";
			break;
		case Common::DE_DEU:
			message1 = "\r  Sicherung erfolglos.";
			message2 = "\rVersuche eine andere     Diskette.";
			break;
		default:
			message1 = "\r       Save failed.";
			message2 = "\r       Disk error.";
			break;
		}
	} else {
		switch (_language) {
		case Common::RU_RUS:
			if (getGameType() == GType_SIMON2) {
				message1 = "\r  Mf ^adruhafts+.";
				message2 = "\r   Takm pf pakefp.";
			} else {
				message1 = "\r   Mf ^adruhafts].";
				message2 = "\r   Takm pf pakefp.";
			}
			break;
		case Common::PL_POL:
			message1 = "\r   Blad odczytu.    ";
			message2 = "\r  Nie znaleziono pliku.";
			break;
		case Common::ES_ESP:
			message1 = "\r     Error al cargar";
			message2 = "\r  Archivo no encontrado";
			break;
		case Common::IT_ITA:
			message1 = "\r  Caricamento non riuscito";
			message2 = "\r      File non trovato";
			break;
		case Common::FR_FRA:
			message1 = "\r    Echec chargement";
			message2 = "\r  Fichier introuvable";
			break;
		case Common::DE_DEU:
			message1 = "\r    Laden erfolglos.";
			message2 = "\r  Datei nicht gefunden.";
			break;
		default:
			message1 = "\r       Load failed.";
			message2 = "\r     File not found.";
			break;
		}
	}

	if (getGameType() == GType_ELVIRA1) {
		printScroll();
		window->textColumn = 0;
		window->textRow = 0;
		window->textColumnOffset = 0;
		window->textLength = 0;		// Difference
	} else {
		windowPutChar(window, 12);
	}

	for (; *message1; message1++)
		windowPutChar(window, *message1);
	for (; *message2; message2++)
		windowPutChar(window, *message2);

	waitWindow(window);
}

uint16 readItemID(Common::SeekableReadStream *f) {
	uint32 val = f->readUint32BE();
	if (val == 0xFFFFFFFF)
		return 0;
	return val + 1;
}

void writeItemID(Common::WriteStream *f, uint16 val) {
	if (val == 0)
		f->writeUint32BE(0xFFFFFFFF);
	else
		f->writeUint32BE(val - 1);
}

bool AGOSEngine::loadGame(const Common::String &filename, bool restartMode) {
	char ident[100];
	Common::SeekableReadStream *f = NULL;
	uint num, item_index, i;

	_videoLockOut |= 0x100;

	if (restartMode) {
		// Load restart state
		Common::File *file = new Common::File();
		if (!file->open(filename)) {
			delete file;
			file = nullptr;
		}
		f = file;
	} else {
		f = _saveFileMan->openForLoading(filename);
	}

	if (f == NULL) {
		_videoLockOut &= ~0x100;
		return false;
	}

	if (!restartMode) {
		f->read(ident, 8);
	}

	num = f->readUint32BE();

	if (f->readUint32BE() != 0xFFFFFFFF || num != _itemArrayInited - 1) {
		delete f;
		_videoLockOut &= ~0x100;
		return false;
	}

	f->readUint32BE();
	f->readUint32BE();
	_noParentNotify = true;

	// add all timers
	killAllTimers();
	for (num = f->readUint32BE(); num; num--) {
		uint32 timeout = f->readUint32BE();
		uint16 subroutine_id = f->readUint16BE();
		addTimeEvent(timeout, subroutine_id);
	}

	item_index = 1;
	for (num = _itemArrayInited - 1; num; num--) {
		Item *item = _itemArrayPtr[item_index++], *parent_item;

		parent_item = derefItem(readItemID(f));
		setItemParent(item, parent_item);

		item->state = f->readUint16BE();
		item->classFlags = f->readUint16BE();

		SubObject *o = (SubObject *)findChildOfType(item, kObjectType);
		if (o) {
			o->objectSize = f->readUint16BE();
			o->objectWeight = f->readUint16BE();
		}

		SubPlayer *p = (SubPlayer *)findChildOfType(item, kPlayerType);
		if (p) {
			p->score = f->readUint32BE();
			p->level = f->readUint16BE();
			p->size = f->readUint16BE();
			p->weight = f->readUint16BE();
			p->strength = f->readUint16BE();
		}

		SubUserFlag *u = (SubUserFlag *)findChildOfType(item, kUserFlagType);
		if (u) {
			for (i = 0; i != 8; i++) {
				u->userFlags[i] = f->readUint16BE();
			}
			u->userItems[0] = readItemID(f);
		}
	}

	// read the variables
	for (i = 0; i != _numVars; i++) {
		writeVariable(i, f->readUint16BE());
	}

	if (f->err()) {
		error("load failed");
	}

	delete f;

	_noParentNotify = false;

	_videoLockOut &= ~0x100;

	return true;
}

bool AGOSEngine::saveGame(uint slot, const char *caption) {
	Common::OutSaveFile *f;
	uint item_index, num_item, i;
	TimeEvent *te;
	uint32 curTime = getTime();
	uint32 gsc = _gameStoppedClock;

	_videoLockOut |= 0x100;

	f = _saveFileMan->openForSaving(genSaveName(slot));
	if (f == NULL) {
		_videoLockOut &= ~0x100;
		return false;
	}

	f->write(caption, 8);

	f->writeUint32BE(_itemArrayInited - 1);
	f->writeUint32BE(0xFFFFFFFF);
	f->writeUint32BE(curTime);
	f->writeUint32BE(0);

	i = 0;
	for (te = _firstTimeStruct; te; te = te->next)
		i++;
	f->writeUint32BE(i);

	for (te = _firstTimeStruct; te; te = te->next) {
		f->writeUint32BE(te->time - curTime + gsc);
		f->writeUint16BE(te->subroutine_id);
	}

	item_index = 1;
	for (num_item = _itemArrayInited - 1; num_item; num_item--) {
		Item *item = _itemArrayPtr[item_index++];

		writeItemID(f, item->parent);

		f->writeUint16BE(item->state);
		f->writeUint16BE(item->classFlags);

		SubObject *o = (SubObject *)findChildOfType(item, kObjectType);
		if (o) {
			f->writeUint16BE(o->objectSize);
			f->writeUint16BE(o->objectWeight);
		}

		SubPlayer *p = (SubPlayer *)findChildOfType(item, kPlayerType);
		if (p) {
			f->writeUint32BE(p->score);
			f->writeUint16BE(p->level);
			f->writeUint16BE(p->size);
			f->writeUint16BE(p->weight);
			f->writeUint16BE(p->strength);
		}

		SubUserFlag *u = (SubUserFlag *)findChildOfType(item, kUserFlagType);
		if (u) {
			for (i = 0; i != 8; i++) {
				f->writeUint16BE(u->userFlags[i]);
			}
			writeItemID(f, u->userItems[0]);
		}
	}

	// write the variables
	for (i = 0; i != _numVars; i++) {
		f->writeUint16BE(readVariable(i));
	}

	f->finalize();
	bool result = !f->err();

	delete f;
	_videoLockOut &= ~0x100;

	return result;
}

bool AGOSEngine_Elvira2::loadGame(const Common::String &filename, bool restartMode) {
	char ident[100];
	Common::SeekableReadStream *f = NULL;
	uint num, item_index, i, j;

	_videoLockOut |= 0x100;

	if (restartMode) {
		// Load restart state
		Common::File *file = new Common::File();
		if (!file->open(filename)) {
			delete file;
			file = nullptr;
		}
		f = file;
	} else {
		f = _saveFileMan->openForLoading(filename);
	}

	if (f == NULL) {
		_videoLockOut &= ~0x100;
		return false;
	}

	if (getGameType() == GType_PP) {
		// No caption
	} else if (getGameType() == GType_FF) {
		f->read(ident, 100);
	} else if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) {
		f->read(ident, 18);
	} else if (!restartMode) {
		f->read(ident, 8);
	}

	num = f->readUint32BE();

	if (f->readUint32BE() != 0xFFFFFFFF || num != _itemArrayInited - 1) {
		delete f;
		_videoLockOut &= ~0x100;
		return false;
	}

	f->readUint32BE();
	f->readUint32BE();
	_noParentNotify = true;

	// add all timers
	killAllTimers();
	for (num = f->readUint32BE(); num; num--) {
		uint32 timeout = f->readUint32BE();
		uint16 subroutine_id = f->readUint16BE();
		addTimeEvent(timeout, subroutine_id);
	}

	if (getGameType() == GType_WW && getPlatform() == Common::kPlatformDOS) {
		for (uint s = 0; s < _numRoomStates; s++) {
			_roomStates[s].state = f->readUint16BE();
			_roomStates[s].classFlags = f->readUint16BE();
			_roomStates[s].roomExitStates = f->readUint16BE();
		}
		f->readUint16BE();

		uint16 room = _currentRoom;
		_currentRoom = f->readUint16BE();
		if (_roomsListPtr) {
			byte *p = _roomsListPtr;
			if (room == _currentRoom) {
				for (;;) {
					uint16 minNum = READ_BE_UINT16(p); p += 2;
					if (minNum == 0)
						break;

					uint16 maxNum = READ_BE_UINT16(p); p += 2;

					 for (uint16 z = minNum; z <= maxNum; z++) {
						uint16 itemNum = z + 2;
						Item *item = derefItem(itemNum);

						num = (itemNum - _itemArrayInited);
						item->state = _roomStates[num].state;
						item->classFlags = _roomStates[num].classFlags;
						SubRoom *subRoom = (SubRoom *)findChildOfType(item, kRoomType);
						subRoom->roomExitStates = _roomStates[num].roomExitStates;
					}
				}
			} else {
				for (;;) {
					uint16 minNum = READ_BE_UINT16(p); p += 2;
					if (minNum == 0)
						break;

					uint16 maxNum = READ_BE_UINT16(p); p += 2;

					 for (uint16 z = minNum; z <= maxNum; z++) {
						uint16 itemNum = z + 2;
						_itemArrayPtr[itemNum] = 0;
					}
				}
			}
		}

		if (room != _currentRoom) {
			_roomsListPtr = 0;
			loadRoomItems(_currentRoom);
		}
	}

	item_index = 1;
	for (num = _itemArrayInited - 1; num; num--) {
		Item *item = _itemArrayPtr[item_index++], *parent_item;

		if ((getGameType() == GType_WW && getPlatform() == Common::kPlatformAmiga) ||
			getGameType() == GType_ELVIRA2) {
			parent_item = derefItem(readItemID(f));
			setItemParent(item, parent_item);
		} else {
			uint parent = f->readUint16BE();
			uint next = f->readUint16BE();

			if (getGameType() == GType_WW && getPlatform() == Common::kPlatformDOS && derefItem(item->parent) == NULL)
				item->parent = 0;

			parent_item = derefItem(parent);
			setItemParent(item, parent_item);

			if (parent_item == NULL) {
				item->parent = parent;
				item->next = next;
			}
		}

		item->state = f->readUint16BE();
		item->classFlags = f->readUint16BE();

		SubRoom *r = (SubRoom *)findChildOfType(item, kRoomType);
		if (r) {
			r->roomExitStates = f->readUint16BE();
		}

		SubSuperRoom *sr = (SubSuperRoom *)findChildOfType(item, kSuperRoomType);
		if (sr) {
			uint16 n = sr->roomX * sr->roomY * sr->roomZ;
			for (i = j = 0; i != n; i++)
				sr->roomExitStates[j++] = f->readUint16BE();
		}

		SubObject *o = (SubObject *)findChildOfType(item, kObjectType);
		if (o) {
			o->objectFlags = f->readUint32BE();
			i = o->objectFlags & 1;

			for (j = 1; j < 16; j++) {
				if (o->objectFlags & (1 << j)) {
					o->objectFlagValue[i++] = f->readUint16BE();
				}
			}
		}

		SubUserFlag *u = (SubUserFlag *)findChildOfType(item, kUserFlagType);
		if (u) {
			for (i = 0; i != 4; i++) {
				u->userFlags[i] = f->readUint16BE();
			}
		}
	}

	// read the variables
	for (i = 0; i != _numVars; i++) {
		writeVariable(i, f->readUint16BE());
	}

	// read the items in item store
	for (i = 0; i != _numItemStore; i++) {
		if (getGameType() == GType_WW && getPlatform() == Common::kPlatformAmiga) {
			_itemStore[i] = derefItem(f->readUint16BE() / 16);
		} else if (getGameType() == GType_ELVIRA2) {
			if (getPlatform() == Common::kPlatformDOS) {
				_itemStore[i] = derefItem(readItemID(f));
			} else {
				_itemStore[i] = derefItem(f->readUint16BE() / 18);
			}
		} else {
			_itemStore[i] = derefItem(f->readUint16BE());
		}
	}

	// Read the bits in array 1
	for (i = 0; i != _numBitArray1; i++)
		_bitArray[i] = f->readUint16BE();

	// Read the bits in array 2
	for (i = 0; i != _numBitArray2; i++)
		_bitArrayTwo[i] = f->readUint16BE();

	// Read the bits in array 3
	for (i = 0; i != _numBitArray3; i++)
		_bitArrayThree[i] = f->readUint16BE();

	if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
		_superRoomNumber = f->readUint16BE();
	}

	if (f->err()) {
		error("load failed");
	}

	delete f;

	_noParentNotify = false;

	_videoLockOut &= ~0x100;

	// The floppy disk versions of Simon the Sorcerer 2 block changing
	// to scrolling rooms, if the copy protection fails. But the copy
	// protection flags are never set in the CD version.
	// Setting this copy protection flag, allows saved games to be shared
	// between all versions of Simon the Sorcerer 2.
	if (getGameType() == GType_SIMON2) {
		setBitFlag(135, 1);
	}

	return true;
}

bool AGOSEngine_Elvira2::saveGame(uint slot, const char *caption) {
	Common::OutSaveFile *f;
	uint item_index, num_item, i, j;
	TimeEvent *te;
	uint32 curTime = getTime();
	uint32 gsc = _gameStoppedClock;

	_videoLockOut |= 0x100;

	f = _saveFileMan->openForSaving(genSaveName(slot));
	if (f == NULL) {
		_videoLockOut &= ~0x100;
		return false;
	}

	if (getGameType() == GType_PP) {
		// No caption
	} else if (getGameType() == GType_FF) {
		f->write(caption, 100);
	} else if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) {
		f->write(caption, 18);
	} else {
		f->write(caption, 8);
	}

	f->writeUint32BE(_itemArrayInited - 1);
	f->writeUint32BE(0xFFFFFFFF);
	f->writeUint32BE(curTime);
	f->writeUint32BE(0);

	i = 0;
	for (te = _firstTimeStruct; te; te = te->next)
		i++;
	f->writeUint32BE(i);

	if (getGameType() == GType_FF && _clockStopped)
		gsc += (getTime() - _clockStopped);
	for (te = _firstTimeStruct; te; te = te->next) {
		f->writeUint32BE(te->time - curTime + gsc);
		f->writeUint16BE(te->subroutine_id);
	}

	if (getGameType() == GType_WW && getPlatform() == Common::kPlatformDOS) {
		if (_roomsListPtr) {
			byte *p = _roomsListPtr;
			for (;;) {
				uint16 minNum = READ_BE_UINT16(p); p += 2;
				if (minNum == 0)
					break;

				uint16 maxNum = READ_BE_UINT16(p); p += 2;

				 for (uint16 z = minNum; z <= maxNum; z++) {
					uint16 itemNum = z + 2;
					Item *item = derefItem(itemNum);

					uint16 num = (itemNum - _itemArrayInited);
					_roomStates[num].state = item->state;
					_roomStates[num].classFlags = item->classFlags;
					SubRoom *subRoom = (SubRoom *)findChildOfType(item, kRoomType);
					_roomStates[num].roomExitStates = subRoom->roomExitStates;
				}
			}
		}

		for (uint s = 0; s < _numRoomStates; s++) {
			f->writeUint16BE(_roomStates[s].state);
			f->writeUint16BE(_roomStates[s].classFlags);
			f->writeUint16BE(_roomStates[s].roomExitStates);
		}
		f->writeUint16BE(0);
		f->writeUint16BE(_currentRoom);
	}

	item_index = 1;
	for (num_item = _itemArrayInited - 1; num_item; num_item--) {
		Item *item = _itemArrayPtr[item_index++];

		if ((getGameType() == GType_WW && getPlatform() == Common::kPlatformAmiga) ||
			getGameType() == GType_ELVIRA2) {
			writeItemID(f, item->parent);
		} else {
			f->writeUint16BE(item->parent);
			f->writeUint16BE(item->next);
		}

		f->writeUint16BE(item->state);
		f->writeUint16BE(item->classFlags);

		SubRoom *r = (SubRoom *)findChildOfType(item, kRoomType);
		if (r) {
			f->writeUint16BE(r->roomExitStates);
		}

		SubSuperRoom *sr = (SubSuperRoom *)findChildOfType(item, kSuperRoomType);
		if (sr) {
			uint16 n = sr->roomX * sr->roomY * sr->roomZ;
			for (i = j = 0; i != n; i++)
				f->writeUint16BE(sr->roomExitStates[j++]);
		}

		SubObject *o = (SubObject *)findChildOfType(item, kObjectType);
		if (o) {
			f->writeUint32BE(o->objectFlags);
			i = o->objectFlags & 1;

			for (j = 1; j < 16; j++) {
				if (o->objectFlags & (1 << j)) {
					f->writeUint16BE(o->objectFlagValue[i++]);
				}
			}
		}

		SubUserFlag *u = (SubUserFlag *)findChildOfType(item, kUserFlagType);
		if (u) {
			for (i = 0; i != 4; i++) {
				f->writeUint16BE(u->userFlags[i]);
			}
		}
	}

	// write the variables
	for (i = 0; i != _numVars; i++) {
		f->writeUint16BE(readVariable(i));
	}

	// write the items in item store
	for (i = 0; i != _numItemStore; i++) {
		if (getGameType() == GType_WW && getPlatform() == Common::kPlatformAmiga) {
			f->writeUint16BE(itemPtrToID(_itemStore[i]) * 16);
		} else if (getGameType() == GType_ELVIRA2) {
			if (getPlatform() == Common::kPlatformDOS) {
				writeItemID(f, itemPtrToID(_itemStore[i]));
			} else {
				f->writeUint16BE(itemPtrToID(_itemStore[i]) * 18);
			}
		} else {
			f->writeUint16BE(itemPtrToID(_itemStore[i]));
		}
	}

	// Write the bits in array 1
	for (i = 0; i != _numBitArray1; i++)
		f->writeUint16BE(_bitArray[i]);

	// Write the bits in array 2
	for (i = 0; i != _numBitArray2; i++)
		f->writeUint16BE(_bitArrayTwo[i]);

	// Write the bits in array 3
	for (i = 0; i != _numBitArray3; i++)
		f->writeUint16BE(_bitArrayThree[i]);

	if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
		f->writeUint16BE(_superRoomNumber);
	}

	f->finalize();
	bool result = !f->err();

	delete f;
	_videoLockOut &= ~0x100;

	return result;
}

// Personal Nightmare specific
bool AGOSEngine_PN::badload(int8 errorNum) {
	if (errorNum == -2)
		return 0;
	// Load error recovery routine

	// Clear any stack
	while (_stackbase != NULL) {
		dumpstack();
	}

	// Restart from process 1
	_tagOfActiveDoline = 1;
	_dolineReturnVal = 3;
	return 1;
}

void AGOSEngine_PN::getFilename() {
	_noScanFlag = 1;
	clearInputLine();

	memset(_saveFile, 0, sizeof(_saveFile));
	while (!shouldQuit() && !strlen(_saveFile)) {
		const char *msg = "File name : ";
		pcf((unsigned char)'\n');
		while (*msg)
			pcf((unsigned char)*msg++);

		interact(_saveFile, 8);
		pcf((unsigned char)'\n');
		_noScanFlag = 0;
	}
}

int AGOSEngine_PN::loadFile(const Common::String &name) {
	Common::InSaveFile *f;
	haltAnimation();

	f = _saveFileMan->openForLoading(name);
	if (f == NULL) {
		restartAnimation();
		return -2;
	}
	f->read(_saveFile, 8);

	if (f->readByte() != 41) {
		restartAnimation();
		delete f;
		return -2;
	}
	if (f->readByte() != 33) {
		restartAnimation();
		delete f;
		return -2;
	}
	// TODO: Make endian safe
	if (!f->read(_dataBase + _quickptr[2], (int)(_quickptr[6] - _quickptr[2]))) {
		restartAnimation();
		delete f;
		return -1;
	}
	delete f;
	restartAnimation();
	dbtosysf();
	return 0;
}

int AGOSEngine_PN::saveFile(const Common::String &name) {
	Common::OutSaveFile *f;
	sysftodb();
	haltAnimation();

	f = _saveFileMan->openForSaving(name);
	if (f == NULL) {
		restartAnimation();

		const char *msg = "Couldn't save. ";
		pcf((unsigned char)'\n');
		while (*msg)
			pcf((unsigned char)*msg++);

		return 0;
	}
	f->write(_saveFile, 8);

	f->writeByte(41);
	f->writeByte(33);
	// TODO: Make endian safe
	if (!f->write(_dataBase + _quickptr[2], (int)(_quickptr[6] - _quickptr[2]))) {
		delete f;
		restartAnimation();
		error("Couldn't save ");
		return 0;	// for compilers that don't support NORETURN
	}
	f->finalize();
	delete f;

	restartAnimation();
	return 1;
}

void AGOSEngine_PN::sysftodb() {
	uint32 pos = _quickptr[2];
	int ct = 0;

	while (ct < (getptr(49L) / 2)) {
		_dataBase[pos] = (uint8)(_variableArray[ct] % 256);
		_dataBase[pos + 1] = (uint8)(_variableArray[ct] / 256);
		pos+=2;
		ct++;
	}
}

void AGOSEngine_PN::dbtosysf() {
	uint32 pos = _quickptr[2];
	int ct = 0;

	while (ct < (getptr(49L) / 2)) {
		_variableArray[ct] = _dataBase[pos] + 256 * _dataBase[pos + 1];
		pos += 2;
		ct++;
	}
}

} // End of namespace AGOS
