/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "lure/game.h"
#include "lure/animseq.h"
#include "lure/fights.h"
#include "lure/lure.h"
#include "lure/res_struct.h"
#include "lure/room.h"
#include "lure/scripts.h"
#include "lure/sound.h"
#include "lure/strings.h"

#include "backends/keymapper/keymapper.h"

#include "common/config-manager.h"
#include "common/system.h"

namespace Lure {

static Game *int_game = nullptr;

bool Game::isCreated() {
	return int_game != nullptr;
}

Game &Game::getReference() {
	return *int_game;
}

Game::Game() {
	int_game = this;
	g_engine->setDebugger(new Debugger());
	_fastTextFlag = false;
	_preloadFlag = false;
	_debugFlag = gDebugLevel >= ERROR_BASIC;
	_soundFlag = true;
}

Game::~Game() {
}

void Game::tick() {
	// Call the tick method for each hotspot - this is somewaht complicated
	// by the fact that a tick proc can unload both itself and/or others,
	// so we first get a list of the Ids, and call the tick proc for each
	// id in sequence if it's still active
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	HotspotList::iterator i;

	uint16 *idList = new uint16[res.activeHotspots().size()];
	int idSize = 0;
	for (i = res.activeHotspots().begin(); i != res.activeHotspots().end(); ++i) {
		Hotspot const &hotspot = **i;

		if (!_preloadFlag || ((hotspot.layer() != 0xff) &&
			(hotspot.hotspotId() < FIRST_NONCHARACTER_ID)))
			// Add hotspot to list to execute
			idList[idSize++] = hotspot.hotspotId();
	}

	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot ticks begin");
	for (int idCtr = 0; idCtr < idSize; ++idCtr) {
		Hotspot *hotspot = res.getActiveHotspot(idList[idCtr]);
		if (hotspot) {
			fields.setField(CHARACTER_HOTSPOT_ID, hotspot->hotspotId());
			hotspot->tick();
		}
	}
	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot ticks end");

	delete[] idList;
}

void Game::tickCheck() {
	Resources &res = Resources::getReference();
	Room &room = Room::getReference();
	bool remoteFlag = res.fieldList().getField(OLD_ROOM_NUMBER) != 0;

	_state |= GS_TICK;
	if ((room.roomNumber() == ROOMNUM_VILLAGE_SHOP) && !remoteFlag && ((_state & GS_TICK) != 0)) {
		// In the village shop,
		bool tockFlag = (_state & GS_TOCK) != 0;
		Sound.addSound(tockFlag ? 16 : 50);

		_state = _state ^ (GS_TICK | GS_TOCK);
	}
}

void Game::nextFrame() {
	Resources &res = Resources::getReference();
	Room &room = Room::getReference();

	if (Fights.isFighting())
		Fights.fightLoop();

	res.pausedList().countdown();
	room.update();
	room.checkCursor();
	tick();

	Screen::getReference().update();
}

void Game::execute() {
	OSystem &system = *g_system;
	LureEngine &engine = LureEngine::getReference();
	Room &room = Room::getReference();
	Resources &res = Resources::getReference();
	Events &events = Events::getReference();
	Mouse &mouse = Mouse::getReference();
	Screen &screen = Screen::getReference();
	ValueTableData &fields = res.fieldList();

	uint32 timerVal = system.getMillis();
	uint32 timerVal2 = system.getMillis();

	screen.empty();
	screen.setPaletteEmpty();

	bool _loadSavegame = false;

	if (engine.gameToLoad() != -1)
		_loadSavegame = engine.loadGame(engine.gameToLoad());

	if (!_loadSavegame) {
		// Flag for starting game
		setState(GS_RESTART);
	}

	bool initialRestart = true;

	while (!engine.shouldQuit()) {

		if ((_state & GS_RESTART) != 0) {
			res.reset();
			Fights.reset();
			if (!initialRestart) room.reset();

			setState(0);
			Script::execute(STARTUP_SCRIPT);

			int bootParam = initialRestart ? ConfMan.getInt("boot_param") : 0;
			handleBootParam(bootParam);
			initialRestart = false;
		}

		room.update();
		mouse.setCursorNum(CURSOR_ARROW);
		mouse.cursorOn();

		// Main game loop
		while (!engine.shouldQuit() && ((_state & GS_RESTART) == 0)) {
			// If time for next frame, allow everything to update
			if (system.getMillis() > timerVal + GAME_FRAME_DELAY) {
				timerVal = system.getMillis();
				nextFrame();
				res.delayList().tick();

				Sound.musicInterface_ContinuePlaying();
			}

			// Also check if time to do another village shop tick check
			if (system.getMillis() > timerVal2 + GAME_TICK_DELAY) {
				timerVal2 = system.getMillis();
				tickCheck();
			}

			while (events.pollEvent()) {
				// Handle special keys
				if (events.type() == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
					bool handled = true;
					switch (events.customType()) {
					case kActionSaveGame:
						if (isMenuAvailable())
							SaveRestoreDialog::show(true);
						break;

					case kActionRestoreGame:
						SaveRestoreDialog::show(false);
						break;

					case kActionRestartGame:
						doRestart();
						break;

					case kActionEscape:
						doQuit();
						break;

					default:
						handled = false;
					}
					if (handled)
						continue;
				}

				if (events.type() == Common::EVENT_KEYDOWN) {
					uint16 roomNum = room.roomNumber();

					// Handle special keys
					bool handled = true;
					switch (events.event().kbd.keycode) {
					case Common::KEYCODE_KP_PLUS:
						if (_debugFlag) {
							while (++roomNum <= 51)
								if (res.getRoom(roomNum) != nullptr) break;
							if (roomNum == 52) roomNum = 1;
							room.setRoomNumber(roomNum);
						}
						break;

					case Common::KEYCODE_KP_MINUS:
						if (_debugFlag) {
							if (roomNum == 1) roomNum = 55;
							while (res.getRoom(--roomNum) == nullptr)
								;
							room.setRoomNumber(roomNum);
						}
						break;

					case Common::KEYCODE_KP_MULTIPLY:
						if (_debugFlag)
							res.getActiveHotspot(PLAYER_ID)->setRoomNumber(
								room.roomNumber());
						break;

					case Common::KEYCODE_KP_DIVIDE:
					case Common::KEYCODE_SLASH:
						if (_debugFlag)
							room.setShowInfo(!room.showInfo());
						break;

					default:
						handled = false;
					}
					if (handled)
						continue;
				}

				if ((events.type() == Common::EVENT_LBUTTONDOWN) ||
					(events.type() == Common::EVENT_RBUTTONDOWN))
					handleClick();
			}

			uint16 destRoom;
			destRoom = fields.getField(NEW_ROOM_NUMBER);
			if (destRoom != 0) {
				// Need to change the current room
				room.statusLine()[0] = '\0';
				bool remoteFlag = fields.getField(OLD_ROOM_NUMBER) != 0;
				room.setRoomNumber(destRoom, remoteFlag);
				fields.setField(NEW_ROOM_NUMBER, 0);
			}

			destRoom = fields.playerNewPos().roomNumber;
			if (destRoom != 0) {
				playerChangeRoom();
			}

			system.updateScreen();
			system.delayMillis(10);
		}

		room.leaveRoom();

		// If Skorl catches player, show the catching animation
		if ((_state & GS_CAUGHT) != 0) {
			if (!engine.isEGA())
				screen.paletteFadeOut();

			Palette palette(SKORL_CATCH_PALETTE_ID);
			mouse.cursorOff();

			static const AnimSoundSequence catchSound[] = { { 12, 0xFF, 0xFF, 1, false }, { 1, 41, 41, 1, false }, {0, 0, 0, 0, false} };
			AnimationSequence *anim = new AnimationSequence(SKORL_CATCH_ANIM_ID, palette, true, 5, catchSound);
			anim->show();
			delete anim;

			if (!engine.isEGA())
				screen.paletteFadeOut();
		}

		// If the Restart/Restore dialog is needed, show it
		if ((_state & GS_RESTORE) != 0) {
			// Show the Restore/Restart dialog
			bool restartFlag = RestartRestoreDialog::show();

			if (restartFlag)
				setState(GS_RESTART);
		}
	}
}

void Game::handleMenuResponse(uint8 selection) {
	Common::String filename;

	switch (selection) {
	case MENUITEM_CREDITS:
		doShowCredits();
		break;

	case MENUITEM_RESTART_GAME:
		doRestart();
		break;

	case MENUITEM_SAVE_GAME:
		SaveRestoreDialog::show(true);
		break;

	case MENUITEM_RESTORE_GAME:
		SaveRestoreDialog::show(false);
		break;

	case MENUITEM_QUIT:
		doQuit();
		break;

	case MENUITEM_TEXT_SPEED:
		doTextSpeed();
		break;

	case MENUITEM_SOUND:
		doSound();
		break;

	default:
		break;
	}
}

void Game::playerChangeRoom() {
	Resources &res = Resources::getReference();
	Room &room = Room::getReference();
	ValueTableData &fields = res.fieldList();
	SequenceDelayList &delayList = Resources::getReference().delayList();

	uint16 roomNum = fields.playerNewPos().roomNumber;
	fields.playerNewPos().roomNumber = 0;
	Common::Point &newPos = fields.playerNewPos().position;

	delayList.clear();

	RoomData *roomData = res.getRoom(roomNum);
	assert(roomData);
	roomData->flags |= HOTSPOTFLAG_FOUND;

	// Check for any room change animation

	int animFlag = fields.getField(ROOM_EXIT_ANIMATION);
	if (animFlag == 1)
		displayChuteAnimation();
	else if (animFlag != 0)
		displayBarrelAnimation();

	fields.setField(ROOM_EXIT_ANIMATION, 0);
	roomData->exitTime = g_system->getMillis();

	// Change to the new room
	Hotspot *player = res.getActiveHotspot(PLAYER_ID);
	player->currentActions().clear();
	player->setRoomNumber(roomNum);
	player->setPosition((newPos.x & 0xfff8) | 5, newPos.y & 0xfff8);
	player->setOccupied(true);
	room.setRoomNumber(roomNum, false);

	// Special check for change back from Selena
	if ((roomNum != 31) && (roomNum != 14) && (fields.getField(74) != 0)) {
		uint16 v = fields.getField(29);
		if (v != 0) {
			--v;
			fields.setField(29, v);
			if (v == 0)
				res.delayList().add(2, 0xCB7, false);
		}
	}
}

void Game::displayChuteAnimation() {
	Resources &res = Resources::getReference();
	Mouse &mouse = Mouse::getReference();

	ValueTableData &fields = res.fieldList();
	Palette palette(CHUTE_PALETTE_ID);

	mouse.setCursorNum(CURSOR_DISK);
	if (!LureEngine::getReference().isEGA())
		Screen::getReference().paletteFadeOut();

	debugC(ERROR_INTERMEDIATE, kLureDebugAnimations, "Starting chute animation");
	mouse.cursorOff();

	Sound.killSounds();

	AnimationSequence *anim = new AnimationSequence(CHUTE_ANIM_ID, palette, true);
	Sound.musicInterface_Play(0x40, true);
	AnimAbortType result = anim->show();
	delete anim;

	if (result != ABORT_END_INTRO) {
		anim = new AnimationSequence(CHUTE2_ANIM_ID, palette, true, 5, nullptr, 4);
		result = anim->show();
		delete anim;
	}

	if (result != ABORT_END_INTRO) {
		anim = new AnimationSequence(CHUTE3_ANIM_ID, palette, false);
		anim->show();
		delete anim;
	}

	Sound.killSounds();
	mouse.cursorOn();
	fields.setField(AREA_FLAG, 1);

	// WORKAROUND When outside in the town, the game plays an ambient sound
	// of twittering birds. When first entering town after falling through
	// the chute, this sound does not play; it starts playing after you
	// enter and exit a building. Calling removeSounds here triggers the
	// function which manages the ambient sounds in town, so the bird
	// sounds start playing. Because all other sounds have already been
	// removed, this has no side effects.
	Sound.removeSounds();
}

void Game::displayBarrelAnimation() {
	Mouse &mouse = Mouse::getReference();
	Resources &res = Resources::getReference();
	LureEngine &engine = LureEngine::getReference();
	Screen &screen = Screen::getReference();

	mouse.setCursorNum(CURSOR_DISK);
	if (!engine.isEGA())
		screen.paletteFadeOut();

	debugC(ERROR_INTERMEDIATE, kLureDebugAnimations, "Starting barrel animation");
	Palette palette(BARREL_PALETTE_ID);
	mouse.cursorOff();

	Sound.killSounds();
	Sound.musicInterface_Play(0x3B, true);

	AnimationSequence *anim = new AnimationSequence(BARREL_ANIM_ID, palette, true);
	anim->show();

	delete anim;

	if (!engine.shouldQuit() && !engine.isEGA())
		screen.paletteFadeOut();

	Sound.killSounds();
	mouse.cursorOn();

	// Disable town NPCs that are no longer needed
	res.deactivateHotspot(SKORL_ID);
	res.deactivateHotspot(BLACKSMITH_ID);
	res.deactivateHotspot(GWEN_ID);
	res.deactivateHotspot(MALLIN_ID);
	res.deactivateHotspot(MONK1_ID);
	res.deactivateHotspot(GOEWIN_ID);
	res.deactivateHotspot(MONK2_ID);
	res.deactivateHotspot(WAYNE_ID);
}

void Game::handleClick() {
	Resources &res = Resources::getReference();
	Room &room = Room::getReference();
	ValueTableData &fields = res.fieldList();
	Mouse &mouse = Mouse::getReference();
	uint16 oldRoomNumber = fields.getField(OLD_ROOM_NUMBER);

	if (room.checkInTalkDialog()) {
		// Close the active talk dialog
		room.setTalkDialog(0, 0, 0, 0);
	} else if (oldRoomNumber != 0) {
		// Viewing a room remotely - handle returning to prior room
		if ((room.roomNumber() != 35) || (fields.getField(87) == 0)) {
			// Reset player tick proc and signal to change back to the old room
			res.getActiveHotspot(PLAYER_ID)->setTickProc(PLAYER_TICK_PROC_ID);
			fields.setField(NEW_ROOM_NUMBER, oldRoomNumber);
			fields.setField(OLD_ROOM_NUMBER, 0);
		}
	} else if ((room.cursorState() == CS_TALKING) ||
			   (res.getTalkState() != TALK_NONE)) {
		// Currently talking, so let its tick proc handle it
	} else if (mouse.y() < MENUBAR_Y_SIZE) {
		uint8 response = Menu::getReference().execute();
		if (response != MENUITEM_NONE)
			handleMenuResponse(response);
	} else if ((room.cursorState() == CS_SEQUENCE) ||
			   (room.cursorState() == CS_BUMPED)) {
		// No action necessary
	} else {
		if (mouse.lButton())
			handleLeftClick();
		else
			handleRightClickMenu();
	}
}

void Game::handleRightClickMenu() {
	Room &room = Room::getReference();
	Resources &res = Resources::getReference();
	Screen &screen = Screen::getReference();
	ValueTableData &fields = res.fieldList();
	StringList &stringList = res.stringList();
	StringData &strings = StringData::getReference();
	Mouse &mouse = Mouse::getReference();
	char *statusLine = room.statusLine();
	Hotspot *player = res.getActiveHotspot(PLAYER_ID);
	HotspotData *hotspot, *useHotspot;
	Action action;
	uint32 actions;
	uint16 itemId = 0xffff;
	bool hasItems;

	if (room.hotspotId() != 0) {
		// Get hotspot actions
		actions = room.hotspotActions();
	} else {
		// Standard actions - drink, examine, look, status
		actions = 0x1184000;
	}

	// If no inventory items remove entries that require them
	if (res.numInventoryItems() == 0)
		actions &= 0xFEF3F9FD;

	// If the player hasn't any money, remove any bribe entry
	if (res.fieldList().numGroats() == 0)
		actions &= 0xFF7FFFFF;

	action = NONE;
	hotspot = nullptr;

	bool breakFlag = false;
	while (!breakFlag) {
		statusLine = room.statusLine();
		statusLine[0] = '\0';
		room.update();
		screen.update();

		action = PopupMenu::Show(actions);

		if (action != NONE) {
			Common::sprintf_s(statusLine, MAX_DESC_SIZE, "%s ", stringList.getString(action));
			statusLine += strlen(statusLine);
		}

		switch (action) {
		case LOOK:
		case STATUS:
			breakFlag = true;
			break;

		case ASK:
			hotspot = res.getHotspot(room.hotspotId());
			assert(hotspot);
			strings.getString(hotspot->nameId, statusLine);
			Common::strlcat(statusLine, stringList.getString(S_FOR), MAX_DESC_SIZE);
			statusLine += strlen(statusLine);

			itemId = PopupMenu::ShowItems(GET, player->roomNumber());
			breakFlag = ((itemId != 0xffff) && (itemId != 0xfffe));
			break;

		case TELL:
			hotspot = res.getHotspot(room.hotspotId());
			assert(hotspot);
			strings.getString(hotspot->nameId, statusLine);
			Common::strlcat(statusLine, stringList.getString(S_TO), MAX_DESC_SIZE);
			breakFlag = GetTellActions();
			break;

		case GIVE:
		case USE:
		case EXAMINE:
		case DRINK:
			hasItems = (res.numInventoryItems() != 0);
			if (!hasItems)
				Common::strlcat(statusLine, stringList.getString(S_ACTION_NOTHING), MAX_DESC_SIZE);
			statusLine += strlen(statusLine);

			room.update();
			screen.update();
			mouse.waitForRelease();

			if (hasItems) {
				if (action != DRINK)
					hotspot = res.getHotspot(room.hotspotId());
				itemId = PopupMenu::ShowInventory();
				breakFlag = (itemId != 0xffff);
				if (breakFlag) {
					fields.setField(USE_HOTSPOT_ID, itemId);
					if ((action == GIVE) || (action == USE)) {
						// Add in the "X to " or "X on " section of give/use action
						useHotspot = res.getHotspot(itemId);
						assert(useHotspot);
						strings.getString(useHotspot->nameId, statusLine);
						if (action == GIVE)
							Common::strlcat(statusLine, stringList.getString(S_TO), MAX_DESC_SIZE);
						else
							Common::strlcat(statusLine, stringList.getString(S_ON), MAX_DESC_SIZE);
						statusLine += strlen(statusLine);
					}
					else if ((action == DRINK) || (action == EXAMINE))
						hotspot = res.getHotspot(itemId);
				}
			}
			break;

		default:
			hotspot = res.getHotspot(room.hotspotId());
			breakFlag = true;
			break;
		}
	}

	if (action != NONE) {
		player->stopWalking();

		if (hotspot == nullptr) {
			doAction(action, 0, itemId);
		} else {
			if (action != TELL) {
				// Add the hotspot name to the status line and then go do the action
				if ((itemId != 0xffff) && (action != GIVE) && (action != USE)) {
					HotspotData *itemHotspot = res.getHotspot(itemId);
					if (itemHotspot != nullptr)
						strings.getString(itemHotspot->nameId, statusLine);
				}
				else
					strings.getString(hotspot->nameId, statusLine);
			}

			doAction(action, hotspot->hotspotId, itemId);
		}
	} else {
		// Clear the status line
		room.statusLine()[0] = '\0';
	}
}

void Game::handleLeftClick() {
	Room &room = Room::getReference();
	Mouse &mouse = Mouse::getReference();
	Resources &res = Resources::getReference();
	StringData &strings = StringData::getReference();
	StringList &stringList = res.stringList();
	Hotspot *player = res.getActiveHotspot(PLAYER_ID);

	room.setCursorState(CS_NONE);
	player->stopWalking();
	player->setDestHotspot(0);
	player->setActionCtr(0);
	room.statusLine()[0] = '\0';

	if ((room.destRoomNumber() == 0) && (room.hotspotId() != 0)) {
		// Handle look at hotspot
		Common::sprintf_s(room.statusLine(), MAX_DESC_SIZE, "%s ", stringList.getString(LOOK_AT));
		HotspotData *hotspot = res.getHotspot(room.hotspotId());
		assert(hotspot);
		strings.getString(hotspot->nameId, room.statusLine() + strlen(room.statusLine()));
		doAction(LOOK_AT, room.hotspotId(), 0xffff);

	} else if (room.destRoomNumber() != 0) {
		// Walk to another room
		RoomExitCoordinateData &exitData =
			res.coordinateList().getEntry(room.roomNumber()).getData(room.destRoomNumber());

		player->walkTo((exitData.x & 0xfff8) | 5, (exitData.y & 0xfff8),
			room.hotspotId() == 0 ? 0xffff : room.hotspotId());
	} else {
		// Walking within room
		player->walkTo(mouse.x(), mouse.y(), 0);
	}
}

bool Game::GetTellActions() {
	Resources &res = Resources::getReference();
	Screen &screen = Screen::getReference();
	Room &room = Room::getReference();
	StringData &strings = StringData::getReference();
	StringList &stringList = res.stringList();
	char *origStatusLine = room.statusLine();
	char *statusLine = origStatusLine;
	uint16 *commands = &_tellCommands[1];
	char *statusLinePos[MAX_TELL_COMMANDS][4];
	int paramIndex = 0;
	uint16 selectionId;
	char selectionName[MAX_DESC_SIZE];
	HotspotData *hotspot;
	Action action;
	const char *continueStrsList[2] = {stringList.getString(S_AND_THEN), stringList.getString(S_FINISH)};

	// First word will be the destination character
	_tellCommands[0] = room.hotspotId();
	_numTellCommands = 0;

	// Set up a room transfer list
	Common::List<uint16> roomList;
	roomList.push_front(room.roomNumber());

	// Loop for getting tell commands

	while ((_numTellCommands >= 0) && (_numTellCommands < MAX_TELL_COMMANDS)) {

		// Loop for each sub-part of commands: Action, up to two params, and
		// a "and then" selection to allow for more commands

		while ((paramIndex >= 0) && (paramIndex <= 4)) {
			// Update status line
			statusLine += strlen(statusLine);
			statusLinePos[_numTellCommands][paramIndex] = statusLine;
			room.update();
			screen.update();

			switch (paramIndex) {
			case 0: {
				// Prompt for selection of action to perform
				action = PopupMenu::Show(0x6A07FD);
				if (action == NONE) {
					// Move backwards to prior specified action
					--_numTellCommands;
					if (_numTellCommands < 0)
						paramIndex = -1;
					else {
						paramIndex = 3;
						statusLine = statusLinePos[_numTellCommands][paramIndex];
						*statusLine = '\0';
					}
					break;
				}

				// Add the action to the status line
				size_t pos = strlen(statusLine);
				Common::sprintf_s(statusLine + pos, MAX_DESC_SIZE - (statusLine - origStatusLine) - pos, "%s ", stringList.getString(action));

				// Handle any processing for the action
				commands[_numTellCommands * 3] = (uint16) action;
				commands[_numTellCommands * 3 + 1] = 0;
				commands[_numTellCommands * 3 + 2] = 0;
				++paramIndex;
				break;
			}
			case 1:
				// First parameter
				action = (Action) commands[_numTellCommands * 3];
				if (action != RETURN) {
					// Prompt for selection
					if ((action != USE) && (action != DRINK) && (action != GIVE))
						selectionId = PopupMenu::ShowItems(action, *roomList.begin());
					else
						selectionId = PopupMenu::ShowItems(GET, *roomList.begin());

					if ((selectionId == 0xffff) || (selectionId == 0xfffe)) {
						// Move back to prompting for action
						--paramIndex;
						statusLine = statusLinePos[_numTellCommands][paramIndex];
						*statusLine = '\0';
						break;
					}

					if (selectionId < NOONE_ID) {
						// Must be a room selection
						strings.getString(selectionId, selectionName);
						roomList.push_front(selectionId);
					} else {
						hotspot = res.getHotspot(selectionId);
						assert(hotspot);
						strings.getString(hotspot->nameId, selectionName);
					}

					// Store selected entry
					commands[_numTellCommands * 3 + 1] = selectionId;
					Common::strcat_s(statusLine, MAX_DESC_SIZE - (statusLine - origStatusLine), selectionName);
				}

				++paramIndex;
				break;

			case 2:
				// Second parameter
				action = (Action) commands[_numTellCommands * 3];
				if (action == ASK)
					Common::strlcat(statusLine, stringList.getString(S_FOR), MAX_DESC_SIZE);
				else if (action == GIVE)
					Common::strlcat(statusLine, stringList.getString(S_TO), MAX_DESC_SIZE);
				else if (action == USE)
					Common::strlcat(statusLine, stringList.getString(S_ON), MAX_DESC_SIZE);
				else {
					// All other commads don't need a second parameter
					++paramIndex;
					break;
				}

				// Get the second parameter
				selectionId = PopupMenu::ShowItems(GET, *roomList.begin());
				if ((selectionId == 0xfffe) || (selectionId == 0xffff)) {
					--paramIndex;
					statusLine = statusLinePos[_numTellCommands][paramIndex];
					*statusLine = '\0';
				} else {
					// Display the second parameter
					hotspot = res.getHotspot(selectionId);
					assert(hotspot);
					strings.getString(hotspot->nameId, selectionName);
					Common::strcat_s(statusLine, MAX_DESC_SIZE - (statusLine - origStatusLine), selectionName);

					commands[_numTellCommands * 3 + 2] = selectionId;
					++paramIndex;
				}
				break;

			case 3:
				// Prompting for "and then" for more commands
				if (_numTellCommands == MAX_TELL_COMMANDS - 1) {
					// No more commands allowed
					++_numTellCommands;
					paramIndex = -1;
				} else {
					// Only prompt if less than 8 commands entered
					selectionId = PopupMenu::Show(2, continueStrsList);

					switch (selectionId) {
					case 0: {
						// Get ready for next command
						size_t pos = strlen(statusLine);
						Common::sprintf_s(statusLine + pos, MAX_DESC_SIZE - (statusLine - origStatusLine) - pos, " %s ", continueStrsList[0]);
						++_numTellCommands;
						paramIndex = 0;
						break;
					}
					case 1:
						// Increment for just selected command, and add a large amount
						// to signal that the command sequence is complete
						_numTellCommands += 1 + 0x100;
						paramIndex = -1;
						break;

					default:
						// Move to end of just completed command
						action = (Action) commands[_numTellCommands * 3];
						if (action == RETURN)
							paramIndex = 0;
						else if ((action == ASK) || (action == GIVE) || (action == USE))
							paramIndex = 2;
						else {
							paramIndex = 1;
							if (action == GO_TO)
								// Remove top of the cached room change list
								roomList.erase(roomList.begin());
						}

						statusLine = statusLinePos[_numTellCommands][paramIndex];
						*statusLine = '\0';
					}
				}
				break;

			default:
				break;
			}
		}
	}

	bool result = (_numTellCommands != -1);
	if (result) {
		_numTellCommands &= 0xff;
		assert((_numTellCommands > 0) && (_numTellCommands <= MAX_TELL_COMMANDS));
		Common::strcpy_s(statusLinePos[0][0], MAX_DESC_SIZE - (statusLinePos[0][0] - origStatusLine), "..");
		room.update();
		screen.update();
	}

	return result;
}

void Game::doAction(Action action, uint16 hotspotId, uint16 usedId) {
	Resources &res = Resources::getReference();
	Room &room = Room::getReference();
	ValueTableData &fields = res.fieldList();
	Hotspot *player = res.getActiveHotspot(PLAYER_ID);

	fields.setField(CHARACTER_HOTSPOT_ID, PLAYER_ID);
	fields.setField(ACTIVE_HOTSPOT_ID, hotspotId);

	res.setCurrentAction(action);
	room.setCursorState(CS_ACTION);

	// Set the action
	if (action == TELL) {
		// Tell action needs special handling because of the variable length parameter list - add in a
		// placeholder entry, and then replace it's details with the TELL command data
		player->currentActions().addFront(NONE, player->roomNumber(), 0, 0);
		player->currentActions().top().supportData().setDetails2(TELL, _numTellCommands * 3 + 1, &_tellCommands[0]);
	} else if (action == USE)
		// Use action parameters are, for some reason, in reverse order from other 2 item actions
		player->currentActions().addFront(action, player->roomNumber(), usedId, hotspotId);
	else
		// All other action types
		player->currentActions().addFront(action, player->roomNumber(), hotspotId, usedId);
}

void Game::doShowCredits() {
	Events &events = Events::getReference();
	Mouse &mouse = Mouse::getReference();
	Screen &screen = Screen::getReference();
	Room &room = Room::getReference();
	bool isEGA = LureEngine::getReference().isEGA();

	Sound.pause();
	mouse.cursorOff();

	Surface *s = Surface::getScreen(CREDITS_RESOURCE_ID);

	if (isEGA) {
		s->copyToScreen(0, 0);
	} else {
		Palette p(CREDITS_RESOURCE_ID - 1);
		screen.setPaletteEmpty();
		s->copyToScreen(0, 0);
		screen.setPalette(&p);
	}

	delete s;
	events.waitForPress();

	room.setRoomNumber(room.roomNumber());
	mouse.cursorOn();
	Sound.resume();
}

void Game::doQuit() {
	Sound.pause();
	if (getYN())
		LureEngine::getReference().quitGame();
	Sound.resume();
}

void Game::doRestart() {
	Sound.pause();
	if (getYN())
		setState(GS_RESTART);
	Sound.resume();
}

void Game::doTextSpeed() {
	Menu &menu = Menu::getReference();
	StringList &sl = Resources::getReference().stringList();

	_fastTextFlag = !_fastTextFlag;
	menu.getMenu(2).entries()[1] = sl.getString(_fastTextFlag ? S_FAST_TEXT : S_SLOW_TEXT);
}

void Game::doSound() {
	Menu &menu = Menu::getReference();
	StringList &sl = Resources::getReference().stringList();

	_soundFlag = !_soundFlag;
	menu.getMenu(2).entries()[2] = sl.getString(_soundFlag ? S_SOUND_ON : S_SOUND_OFF);

	if (!_soundFlag)
		// Stop all currently playing sounds
		Sound.killSounds();
}

void Game::handleBootParam(int value) {
	Resources &res = Resources::getReference();
	ValueTableData &fields = res.fieldList();
	Room &room = Room::getReference();
	Hotspot *h;

	switch (value) {
	case 0:
		// No parameter - load the first room
		room.setRoomNumber(1);
		break;

	case 1:
		// Set player to be in rack room with a few items
		// Setup Skorl in cell room
		h = res.getActiveHotspot(SKORL_ID);
		h->setRoomNumber(1);
		h->setPosition(140, 120);
		h->currentActions().top().setSupportData(0x1400);
		fields.setField(11, 1);

		// Set up player
		h = res.getActiveHotspot(PLAYER_ID);
		h->setRoomNumber(4);
		h->setPosition(150, 110);
		res.getHotspot(0x2710)->roomNumber = PLAYER_ID;  // Bottle
		res.getHotspot(0x2713)->roomNumber = PLAYER_ID;  // Knife

		room.setRoomNumber(4);
		break;

	case 2:
		// Set the player up in the outer cell with a full bottle & knife
		h = res.getActiveHotspot(PLAYER_ID);
		h->setRoomNumber(2);
		h->setPosition(100, 110);
		res.getHotspot(0x2710)->roomNumber = PLAYER_ID;  // Bottle
		fields.setField(BOTTLE_FILLED, 1);
		res.getHotspot(0x2713)->roomNumber = PLAYER_ID;  // Knife

		room.setRoomNumber(2);
		break;

	default:
		room.setRoomNumber(value);
		break;
	}
}

bool Game::getYN() {
	Mouse &mouse = Mouse::getReference();
	Events &events = Events::getReference();
	Screen &screen = Screen::getReference();
	Resources &res = Resources::getReference();
	LureEngine &engine = LureEngine::getReference();

	bool vKbdFlag = g_system->hasFeature(OSystem::kFeatureVirtualKeyboard);
	if (!vKbdFlag)
		mouse.cursorOff();
	else
		g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);

	Surface *s = Surface::newDialog(190, res.stringList().getString(S_CONFIRM_YN));
	s->centerOnScreen();
	delete s;

	bool breakFlag = false;
	bool result = false;

	Common::Keymapper *keymapper = LureEngine::getReference().getEventManager()->getKeymapper();
	keymapper->getKeymap("yesno-shortcut")->setEnabled(true);

	do {
		while (events.pollEvent()) {
			if (events.event().type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
				Common::CustomEventType key = events.event().customType;
				if ((key == kActionYes) || (key == kActionNo) ||
					(key == kActionEscape)) {
					breakFlag = true;
					result = key == kActionYes;
				}
			}
			if (events.event().type == Common::EVENT_LBUTTONUP) {
				breakFlag = true;
				result = true;
			}
			if (events.event().type == Common::EVENT_RBUTTONUP) {
				breakFlag = true;
				result = false;
			}
		}

		g_system->delayMillis(10);
	} while (!engine.shouldQuit() && !breakFlag);

	keymapper->getKeymap("yesno-shortcut")->setEnabled(false);

	screen.update();
	if (!vKbdFlag)
		mouse.cursorOn();
	else
		g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);

	return result;
}

bool Game::isMenuAvailable() {
	Resources &res = Resources::getReference();
	Room &room = Room::getReference();
	uint16 oldRoomNumber = res.fieldList().getField(OLD_ROOM_NUMBER);

	if (oldRoomNumber != 0)
		// Viewing a room remotely - so the menu isn't available
		return false;

	else if ((room.cursorState() == CS_TALKING) || (res.getTalkState() != TALK_NONE))
		return false;

	return true;
}

void Game::saveToStream(Common::WriteStream *stream) {
	stream->writeByte(_fastTextFlag);
	stream->writeByte(_soundFlag);
}

void Game::loadFromStream(Common::ReadStream *stream) {
	Menu &menu = Menu::getReference();
	StringList &sl = Resources::getReference().stringList();

	_fastTextFlag = stream->readByte() != 0;
	menu.getMenu(2).entries()[1] = sl.getString(_fastTextFlag ? S_FAST_TEXT : S_SLOW_TEXT);

	_soundFlag = stream->readByte() != 0;
	menu.getMenu(2).entries()[2] = sl.getString(_soundFlag ? S_SOUND_ON : S_SOUND_OFF);

	// Reset game state flags
	setState(0);
}


} // End of namespace Lure
