/* 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 "lastexpress/menu/menu.h"

// Data
#include "lastexpress/data/animation.h"
#include "lastexpress/data/cursor.h"
#include "lastexpress/data/snd.h"
#include "lastexpress/data/scene.h"

#include "lastexpress/fight/fight.h"

#include "lastexpress/game/entities.h"
#include "lastexpress/game/inventory.h"
#include "lastexpress/game/logic.h"
#include "lastexpress/game/savegame.h"
#include "lastexpress/game/savepoint.h"
#include "lastexpress/game/scenes.h"
#include "lastexpress/game/state.h"

#include "lastexpress/menu/clock.h"
#include "lastexpress/menu/trainline.h"

#include "lastexpress/sound/queue.h"

#include "lastexpress/graphics.h"
#include "lastexpress/lastexpress.h"
#include "lastexpress/resource.h"

#include "common/rational.h"

#define getNextGameId() (GameId)((_gameId + 1) % SaveLoad::kMaximumSaveSlots)

namespace LastExpress {

// Bottom-left buttons (quit.seq)
enum StartMenuButtons {
	kButtonVolumeDownPushed,
	kButtonVolumeDown,
	kButtonVolume,
	kButtonVolumeUp,
	kButtonVolumeUpPushed,
	kButtonBrightnessDownPushed,    // 5
	kButtonBrightnessDown,
	kButtonBrightness,
	kButtonBrightnessUp,
	kButtonBrightnessUpPushed,
	kButtonQuit,                    // 10
	kButtonQuitPushed
};

// Egg buttons (buttns.seq)
enum StartMenuEggButtons {
	kButtonShield,
	kButtonRewind,
	kButtonRewindPushed,
	kButtonForward,
	kButtonForwardPushed,
	kButtonCredits,                // 5
	kButtonCreditsPushed,
	kButtonContinue
};

// Tooltips sequence (helpnewr.seq)
enum StartMenuTooltips {
	kTooltipInsertCd1,
	kTooltipInsertCd2,
	kTooltipInsertCd3,
	kTooltipContinueGame,
	kTooltipReplayGame,
	kTooltipContinueRewoundGame,    // 5
	kTooltipViewGameEnding,
	kTooltipStartAnotherGame,
	kTooltipVolumeUp,
	kTooltipVolumeDown,
	kTooltipBrightnessUp,           // 10
	kTooltipBrightnessDown,
	kTooltipQuit,
	kTooltipRewindParis,
	kTooltipForwardStrasbourg,
	kTooltipRewindStrasbourg,      // 15
	kTooltipRewindMunich,
	kTooltipForwardMunich,
	kTooltipForwardVienna,
	kTooltipRewindVienna,
	kTooltipRewindBudapest,        // 20
	kTooltipForwardBudapest,
	kTooltipForwardBelgrade,
	kTooltipRewindBelgrade,
	kTooltipForwardConstantinople,
	kTooltipSwitchBlueGame,        // 25
	kTooltipSwitchRedGame,
	kTooltipSwitchGoldGame,
	kTooltipSwitchGreenGame,
	kTooltipSwitchTealGame,
	kTooltipSwitchPurpleGame,      // 30
	kTooltipPlayNewGame,
	kTooltipCredits,
	kTooltipFastForward,
	kTooltipRewind
};

//////////////////////////////////////////////////////////////////////////
// DATA
//////////////////////////////////////////////////////////////////////////
static const struct {
	TimeValue time;
	uint index;
	StartMenuTooltips rewind;
	StartMenuTooltips forward;
} _cityButtonsInfo[7] = {
	{kTimeCityParis, 64, kTooltipRewindParis, kTooltipRewindParis},
	{kTimeCityStrasbourg, 128, kTooltipRewindStrasbourg, kTooltipForwardStrasbourg},
	{kTimeCityMunich, 129, kTooltipRewindMunich, kTooltipForwardMunich},
	{kTimeCityVienna, 130, kTooltipRewindVienna, kTooltipForwardVienna},
	{kTimeCityBudapest, 131, kTooltipRewindBudapest, kTooltipForwardBudapest},
	{kTimeCityBelgrade, 132, kTooltipRewindBelgrade, kTooltipForwardBelgrade},
	{kTimeCityConstantinople, 192, kTooltipForwardConstantinople, kTooltipForwardConstantinople}
};

//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////
Menu::Menu(LastExpressEngine *engine) : _engine(engine),
	_seqTooltips(nullptr), _seqEggButtons(nullptr), _seqButtons(nullptr), _seqAcorn(nullptr), _seqCity1(nullptr), _seqCity2(nullptr), _seqCity3(nullptr), _seqCredits(nullptr),
	_gameId(kGameBlue), _hasShownStartScreen(false), _hasShownIntro(false),
	_isShowingCredits(false), _isGameStarted(false), _isShowingMenu(false),
	_creditsSequenceIndex(0), _checkHotspotsTicks(15),  _mouseFlags(Common::EVENT_INVALID), _lastHotspot(nullptr),
	_currentTime(kTimeNone), _lowerTime(kTimeNone), _time(kTimeNone), _currentIndex(0), _index(0), _lastIndex(0), _delta(0), _handleTimeDelta(false) {

	_clock = new Clock(_engine);
	if (!_engine->isDemo()) {
		_trainLine = new TrainLine(_engine);
	}
}

Menu::~Menu() {
	SAFE_DELETE(_clock);
	if (!_engine->isDemo()) {
		SAFE_DELETE(_trainLine);
	}

	SAFE_DELETE(_seqTooltips);
	SAFE_DELETE(_seqEggButtons);
	SAFE_DELETE(_seqButtons);
	SAFE_DELETE(_seqAcorn);
	SAFE_DELETE(_seqCity1);
	SAFE_DELETE(_seqCity2);
	SAFE_DELETE(_seqCity3);
	SAFE_DELETE(_seqCredits);

	_lastHotspot = nullptr;

	// Cleanup frames
	for (MenuFrames::iterator it = _frames.begin(); it != _frames.end(); it++)
		SAFE_DELETE(it->_value);

	_frames.clear();

	// Zero passed pointers
	_engine = nullptr;
}

//////////////////////////////////////////////////////////////////////////
// Setup
void Menu::setup() {

	// Clear drawing queue
	getScenes()->removeAndRedraw(&_frames[kOverlayAcorn], false);
	SAFE_DELETE(_seqAcorn);

	// Load Menu scene
	// + 1 = normal menu with open egg / clock
	// + 2 = shield menu, when no savegame exists (no game has been started)
	_isGameStarted = _lowerTime >= kTimeStartGame;
	getScenes()->loadScene((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2));
	getFlags()->shouldRedraw = true;
	getLogic()->updateCursor();

	//////////////////////////////////////////////////////////////////////////
	// Load Acorn sequence
	_seqAcorn = loadSequence(getAcornSequenceName(_isGameStarted ? getNextGameId() : kGameBlue));

	//////////////////////////////////////////////////////////////////////////
	// Check if we loaded sequences before
	if (_seqTooltips && _seqTooltips->count() > 0)
		return;

	// Load all static data
	_seqTooltips = loadSequence("helpnewr.seq");
	_seqEggButtons = loadSequence("buttns.seq");
	_seqButtons = loadSequence("quit.seq");
	_seqCity1 = loadSequence("jlinetl.seq");
	_seqCity2 = loadSequence("jlinecen.seq");
	_seqCity3 = loadSequence("jlinebr.seq");
	_seqCredits = loadSequence("credits.seq");

	_frames[kOverlayTooltip] = new SequenceFrame(_seqTooltips);
	_frames[kOverlayEggButtons] = new SequenceFrame(_seqEggButtons);
	_frames[kOverlayButtons] = new SequenceFrame(_seqButtons);
	_frames[kOverlayAcorn] = new SequenceFrame(_seqAcorn);
	_frames[kOverlayCity1] = new SequenceFrame(_seqCity1);
	_frames[kOverlayCity2] = new SequenceFrame(_seqCity2);
	_frames[kOverlayCity3] = new SequenceFrame(_seqCity3);
	_frames[kOverlayCredits] = new SequenceFrame(_seqCredits);
}

//////////////////////////////////////////////////////////////////////////
// Handle events
void Menu::eventMouse(const Common::Event &ev) {
	if (!getFlags()->shouldRedraw)
		return;

	bool redraw = true;
	getFlags()->shouldRedraw = false;

	// Update coordinates
	setCoords(ev.mouse);
	//_mouseFlags = (Common::EventType)(ev.type & Common::EVENT_LBUTTONUP);

	if (_isShowingCredits) {
		if (ev.type == Common::EVENT_RBUTTONUP) {
			showFrame(kOverlayCredits, -1, true);
			_isShowingCredits = false;
		}

		if (ev.type == Common::EVENT_LBUTTONUP) {
			// Last frame of the credits
			if (_seqCredits && _creditsSequenceIndex == _seqCredits->count() - 1) {
				showFrame(kOverlayCredits, -1, true);
				_isShowingCredits = false;
			} else {
				++_creditsSequenceIndex;
				showFrame(kOverlayCredits, _creditsSequenceIndex, true);
			}
		}
	} else {
		// Check for hotspots
		SceneHotspot *hotspot = nullptr;
		getScenes()->get(getState()->scene)->checkHotSpot(ev.mouse, &hotspot);

		if (_lastHotspot != hotspot || ev.type == Common::EVENT_LBUTTONUP) {
			_lastHotspot = hotspot;

			if (ev.type == Common::EVENT_MOUSEMOVE) { /* todo check event type */
				if (!_handleTimeDelta && hasTimeDelta())
					setTime();
			}

			if (hotspot) {
				redraw = handleEvent((StartMenuAction)hotspot->action, ev.type);
				getFlags()->mouseRightClick = false;
				getFlags()->mouseLeftClick = false;
			} else {
				hideOverlays();
			}
		}
	}

	if (redraw) {
		getFlags()->shouldRedraw = true;
		askForRedraw();
	}
}

void Menu::eventTick(const Common::Event&) {
	if (hasTimeDelta())
		adjustTime();
	else if (_handleTimeDelta)
		_handleTimeDelta = false;

	// Check hotspots
	if (!--_checkHotspotsTicks) {
		checkHotspots();
		_checkHotspotsTicks = 15;
	}
}

//////////////////////////////////////////////////////////////////////////
// Show the intro and load the main menu scene
void Menu::show(bool doSavegame, SavegameType type, uint32 value) {

	if (_isShowingMenu)
		return;

	_isShowingMenu = true;
	getEntities()->reset();

	// If no blue savegame exists, this might be the first time we start the game, so we show the full intro
	if (!getFlags()->mouseRightClick) {
		if (!SaveLoad::isSavegameValid(_engine->getTargetName(), kGameBlue) && _engine->getResourceManager()->loadArchive(kArchiveCd1) && !_engine->isDemo()) {

			if (!_hasShownIntro) {
				// Show Broderbrund logo
				Animation animation;
				if (animation.load(getArchiveMember("1930.nis")))
					animation.play();

				getFlags()->mouseRightClick = false;

				// Play intro music
				getSound()->playSoundWithSubtitles("MUS001.SND", kSoundTypeIntro | kVolumeFull, kEntityPlayer);

				// Show The Smoking Car logo
				if (animation.load(getArchiveMember("1931.nis")))
					animation.play();

				_hasShownIntro = true;
			}
		} else {
			// Only show the quick intro
			if (!_hasShownStartScreen) {
				getSound()->playSoundWithSubtitles("MUS018.SND", kSoundTypeIntro | kVolumeFull, kEntityPlayer);
				getScenes()->loadScene(kSceneStartScreen);

				// Original game waits 60 frames and loops Sound::unknownFunction1 unless the right button is pressed
				uint32 nextFrameCount = getFrameCount() + 60;
				while (getFrameCount() < nextFrameCount) {
					_engine->pollEvents();

					if (getFlags()->mouseRightClick)
						break;

					getSoundQueue()->updateQueue();
				}
			}
		}
	}

	_hasShownStartScreen = true;

	// Init Menu
	init(doSavegame, type, value);

	// Setup sound
	getSoundQueue()->stopAmbient();
	getSoundQueue()->stopAllExcept(kSoundTagIntro, kSoundTagMenu);
	if (getSoundQueue()->isBuffered("TIMER"))
		getSoundQueue()->stop("TIMER");

	// Init flags & misc
	_isShowingCredits = false;
	_handleTimeDelta = hasTimeDelta();
	getInventory()->unselectItem();

	// Set Cursor type
	_engine->getCursor()->setStyle(kCursorNormal);
	_engine->getCursor()->show(true);

	setup();
	checkHotspots();

	// Set event handlers
	SET_EVENT_HANDLERS(Menu, this);
}

bool Menu::handleEvent(StartMenuAction action, Common::EventType type) {
	bool clicked = (type == Common::EVENT_LBUTTONUP);

	switch(action) {
	default:
		hideOverlays();
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuCredits:
		if (hasTimeDelta()) {
			hideOverlays();
			break;
		}

		if (clicked) {
			showFrame(kOverlayEggButtons, kButtonCreditsPushed, true);
			showFrame(kOverlayTooltip, -1, true);

			getSound()->playSound(kEntityPlayer, "LIB046");

			hideOverlays();

			_isShowingCredits = true;
			_creditsSequenceIndex = 0;

			showFrame(kOverlayCredits, 0, true);
		} else {
			// TODO check flags ?

			showFrame(kOverlayEggButtons, kButtonCredits, true);
			showFrame(kOverlayTooltip, kTooltipCredits, true);
		}
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuQuitGame:
		showFrame(kOverlayTooltip, kTooltipQuit, true);

		if (clicked) {
			showFrame(kOverlayButtons, kButtonQuitPushed, true);

			getSoundQueue()->stopAll();
			getSoundQueue()->updateQueue();
			getSound()->playSound(kEntityPlayer, "LIB046");

			// FIXME uncomment when sound queue is properly implemented
			/*while (getSoundQueue()->isBuffered("LIB046"))
				getSoundQueue()->updateQueue();*/

			getFlags()->shouldRedraw = false;

			Engine::quitGame();

			return false;
		} else {
			showFrame(kOverlayButtons, kButtonQuit, true);
		}
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuCase4:
		if (clicked)
			_index = 0;
		// fall through

	//////////////////////////////////////////////////////////////////////////
	case kMenuContinue: {
		if (hasTimeDelta()) {
			hideOverlays();
			break;
		}

		// Determine the proper CD archive
		ArchiveIndex cd = kArchiveCd1;
		if (getProgress().chapter > kChapter1)
			cd = (getProgress().chapter > kChapter3) ? kArchiveCd3 : kArchiveCd2;

		// Show tooltips & buttons to start a game, continue a game or load the proper cd
		if (_engine->getResourceManager()->isArchivePresent(cd)) {
			if (_isGameStarted) {
				showFrame(kOverlayEggButtons, kButtonContinue, true);

				if (_lastIndex == _index) {
					showFrame(kOverlayTooltip, getSaveLoad()->isGameFinished(_index, _lastIndex) ? kTooltipViewGameEnding : kTooltipContinueGame, true);
				} else {
					showFrame(kOverlayTooltip, kTooltipContinueRewoundGame, true);
				}

			} else {
				showFrame(kOverlayEggButtons, kButtonShield, true);
				showFrame(kOverlayTooltip, kTooltipPlayNewGame, true);
			}
		} else {
			showFrame(kOverlayEggButtons, -1, true);
			showFrame(kOverlayTooltip, cd - 1, true);
		}

		if (!clicked)
			break;

		// Try loading the archive file
		if (!_engine->getResourceManager()->loadArchive(cd))
			break;

		// Load the train data file and setup game
		getScenes()->loadSceneDataFile(cd);
		showFrame(kOverlayTooltip, -1, true);
		getSound()->playSound(kEntityPlayer, "LIB046");

		// Setup new game
		getSavePoints()->reset();
		setLogicEventHandlers();

		if (_index) {
			getSoundQueue()->fade(kSoundTagIntro);
		} else {
			if (!getFlags()->mouseRightClick) {
				getScenes()->loadScene((SceneIndex)(5 * _gameId + 3));

				if (!getFlags()->mouseRightClick) {
					getScenes()->loadScene((SceneIndex)(5 * _gameId + 4));

					if (!getFlags()->mouseRightClick) {
						getScenes()->loadScene((SceneIndex)(5 * _gameId + 5));

						if (!getFlags()->mouseRightClick) {
							getSoundQueue()->fade(kSoundTagIntro);

							// Show intro
							Animation animation;
							if (animation.load(getArchiveMember("1601.nis")))
								animation.play();

							getEvent(kEventIntro) = 1;
						}
					}
				}
			}

			if (!getEvent(kEventIntro))	{
				getEvent(kEventIntro) = 1;

				getSoundQueue()->fade(kSoundTagIntro);
			}
		}

		// Setup game
		getFlags()->isGameRunning = true;
		startGame();

		if (!_isShowingMenu)
			getInventory()->show();

		return false;
	}

	//////////////////////////////////////////////////////////////////////////
	case kMenuSwitchSaveGame:
		if (hasTimeDelta()) {
			hideOverlays();
			break;
		}

		if (clicked) {
			if (!_engine->isDemo()) {
				showFrame(kOverlayAcorn, 1, true);
				showFrame(kOverlayTooltip, -1, true);
				getSound()->playSound(kEntityPlayer, "LIB047");

				// Setup new menu screen
				switchGame();
				setup();

				// Set fight state to 0
				getFight()->resetState();
			}
			return true;
		}

		// TODO Check for flag

		showFrame(kOverlayAcorn, 0, true);

		if (_isGameStarted && !_engine->isDemo()) {
			showFrame(kOverlayTooltip, kTooltipSwitchBlueGame, true);
			break;
		}

		if (_gameId == kGameGold) {
			showFrame(kOverlayTooltip, kTooltipSwitchBlueGame, true);
			break;
		}

		if (_engine->isDemo() || !SaveLoad::isSavegameValid(_engine->getTargetName(), getNextGameId())) {
			showFrame(kOverlayTooltip, kTooltipStartAnotherGame, true);
			break;
		}

		// Stupid tooltips ids are not in order, so we can't just increment them...
		switch(_gameId) {
		default:
			break;

		case kGameBlue:
			showFrame(kOverlayTooltip, kTooltipSwitchRedGame, true);
			break;

		case kGameRed:
			showFrame(kOverlayTooltip, kTooltipSwitchGreenGame, true);
			break;

		case kGameGreen:
			showFrame(kOverlayTooltip, kTooltipSwitchPurpleGame, true);
			break;

		case kGamePurple:
			showFrame(kOverlayTooltip, kTooltipSwitchTealGame, true);
			break;

		case kGameTeal:
			showFrame(kOverlayTooltip, kTooltipSwitchGoldGame, true);
			break;
		}
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuRewindGame:
		if (!_index || _currentTime < _time) {
			hideOverlays();
			break;
		}

		if (clicked) {
			if (hasTimeDelta())
				_handleTimeDelta = false;

			showFrame(kOverlayEggButtons, kButtonRewindPushed, true);
			showFrame(kOverlayTooltip, -1, true);

			getSound()->playSound(kEntityPlayer, "LIB046");

			rewindTime();

			_handleTimeDelta = false;
		} else {
			showFrame(kOverlayEggButtons, kButtonRewind, true);
			showFrame(kOverlayTooltip, kTooltipRewind, true);
		}
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuForwardGame:
		if (_lastIndex <= _index || _currentTime > _time) {
			hideOverlays();
			break;
		}

		if (clicked) {
			if (hasTimeDelta())
				_handleTimeDelta = false;

			showFrame(kOverlayEggButtons, kButtonForwardPushed, true);
			showFrame(kOverlayTooltip, -1, true);

			getSound()->playSound(kEntityPlayer, "LIB046");

			forwardTime();

			_handleTimeDelta = false;
		} else {
			showFrame(kOverlayEggButtons, kButtonForward, true);
			showFrame(kOverlayTooltip, kTooltipFastForward, true);
		}
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuParis:
		moveToCity(kParis, clicked);
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuStrasBourg:
		moveToCity(kStrasbourg, clicked);
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuMunich:
		moveToCity(kMunich, clicked);
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuVienna:
		moveToCity(kVienna, clicked);
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuBudapest:
		moveToCity(kBudapest, clicked);
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuBelgrade:
		moveToCity(kBelgrade, clicked);
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuConstantinople:
		moveToCity(kConstantinople, clicked);
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuDecreaseVolume:
		if (hasTimeDelta()) {
			hideOverlays();
			break;
		}

		// Cannot decrease volume further
		if (getVolume() == 0) {
			showFrame(kOverlayButtons, kButtonVolume, true);
			showFrame(kOverlayTooltip, -1, true);
			break;
		}

		showFrame(kOverlayTooltip, kTooltipVolumeDown, true);

		// Show highlight on button & adjust volume if needed
		if (clicked) {
			showFrame(kOverlayButtons, kButtonVolumeDownPushed, true);
			getSound()->playSound(kEntityPlayer, "LIB046");
			setVolume(getVolume() - 1);

			getSaveLoad()->saveVolumeBrightness();

			uint32 nextFrameCount = getFrameCount() + 15;
			while (nextFrameCount > getFrameCount()) {
				_engine->pollEvents();

				getSoundQueue()->updateQueue();
			}
		} else {
			showFrame(kOverlayButtons, kButtonVolumeDown, true);
		}
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuIncreaseVolume:
		if (hasTimeDelta()) {
			hideOverlays();
			break;
		}

		// Cannot increase volume further
		if (getVolume() >= 7) {
			showFrame(kOverlayButtons, kButtonVolume, true);
			showFrame(kOverlayTooltip, -1, true);
			break;
		}

		showFrame(kOverlayTooltip, kTooltipVolumeUp, true);

		// Show highlight on button & adjust volume if needed
		if (clicked) {
			showFrame(kOverlayButtons, kButtonVolumeUpPushed, true);
			getSound()->playSound(kEntityPlayer, "LIB046");
			setVolume(getVolume() + 1);

			getSaveLoad()->saveVolumeBrightness();

			uint32 nextFrameCount = getFrameCount() + 15;
			while (nextFrameCount > getFrameCount()) {
				_engine->pollEvents();

				getSoundQueue()->updateQueue();
			}
		} else {
			showFrame(kOverlayButtons, kButtonVolumeUp, true);
		}
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuDecreaseBrightness:
		if (hasTimeDelta()) {
			hideOverlays();
			break;
		}

		// Cannot increase brightness further
		if (getBrightness() == 0) {
			showFrame(kOverlayButtons, kButtonBrightness, true);
			showFrame(kOverlayTooltip, -1, true);
			break;
		}

		showFrame(kOverlayTooltip, kTooltipBrightnessDown, true);

		// Show highlight on button & adjust brightness if needed
		if (clicked) {
			showFrame(kOverlayButtons, kButtonBrightnessDownPushed, true);
			getSound()->playSound(kEntityPlayer, "LIB046");
			setBrightness(getBrightness() - 1);

			getSaveLoad()->saveVolumeBrightness();

			// Reshow the background and frames (they will pick up the new brightness through the GraphicsManager)
			_engine->getGraphicsManager()->draw(getScenes()->get((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2)), GraphicsManager::kBackgroundC, true);
			showFrame(kOverlayTooltip, kTooltipBrightnessDown, false);
			showFrame(kOverlayButtons, kButtonBrightnessDownPushed, false);
		} else {
			showFrame(kOverlayButtons, kButtonBrightnessDown, true);
		}
		break;

	//////////////////////////////////////////////////////////////////////////
	case kMenuIncreaseBrightness:
		if (hasTimeDelta()) {
			hideOverlays();
			break;
		}

		// Cannot increase brightness further
		if (getBrightness() >= 6) {
			showFrame(kOverlayButtons, kButtonBrightness, true);
			showFrame(kOverlayTooltip, -1, true);
			break;
		}

		showFrame(kOverlayTooltip, kTooltipBrightnessUp, true);

		// Show highlight on button & adjust brightness if needed
		if (clicked) {
			showFrame(kOverlayButtons, kButtonBrightnessUpPushed, true);
			getSound()->playSound(kEntityPlayer, "LIB046");
			setBrightness(getBrightness() + 1);

			getSaveLoad()->saveVolumeBrightness();

			// Reshow the background and frames (they will pick up the new brightness through the GraphicsManager)
			_engine->getGraphicsManager()->draw(getScenes()->get((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2)), GraphicsManager::kBackgroundC, true);
			showFrame(kOverlayTooltip, kTooltipBrightnessUp, false);
			showFrame(kOverlayButtons, kButtonBrightnessUpPushed, false);
		} else {
			showFrame(kOverlayButtons, kButtonBrightnessUp, true);
		}
		break;
	}

	return true;
}

void Menu::setLogicEventHandlers() {
	SET_EVENT_HANDLERS(Logic, getLogic());
	clear();
	_isShowingMenu = false;
}

//////////////////////////////////////////////////////////////////////////
// Game-related
//////////////////////////////////////////////////////////////////////////
void Menu::init(bool doSavegame, SavegameType type, uint32 value) {
	bool useSameIndex = true;

	if (getGlobalTimer()) {
		value = 0;

		// Check if the CD file is present
		ArchiveIndex index = kArchiveCd1;
		switch (getProgress().chapter) {
		default:
		case kChapter1:
			break;

		case kChapter2:
		case kChapter3:
			index = kArchiveCd2;
			break;

		case kChapter4:
		case kChapter5:
			index = kArchiveCd3;
			break;
		}

		if (_engine->getResourceManager()->isArchivePresent(index)) {
			setGlobalTimer(0);
			useSameIndex = false;

			// TODO remove existing savegame and reset index & savegame name
			warning("[Menu::initGame] Not implemented");
		}

		doSavegame = false;
	} else {
		warning("[Menu::initGame] Renaming saves not implemented");
	}

	// Create a new savegame if needed
	if (!SaveLoad::isSavegamePresent(_engine->getTargetName(), _gameId))
		getSaveLoad()->create(_engine->getTargetName(), _gameId);

	if (doSavegame)
		getSaveLoad()->saveGame(kSavegameTypeEvent2, kEntityPlayer, kEventNone);

	if (!getGlobalTimer()) {
		warning("[Menu::initGame] Removing temporary saves not implemented");
	}

	// Init savegame & menu values
	_lastIndex = getSaveLoad()->init(_engine->getTargetName(), _gameId, true);
	_lowerTime = getSaveLoad()->getTime(_lastIndex);

	if (useSameIndex)
		_index = _lastIndex;

	//if (!getGlobalTimer())
	//	_index3 = 0;

	if (!getProgress().chapter) {
		if (_engine->isDemo()) {
			getProgress().chapter = kChapter3;
		} else {
			getProgress().chapter = kChapter1;
		}
	}

	getState()->time = (TimeValue)getSaveLoad()->getTime(_index);
	getProgress().chapter = getSaveLoad()->getChapter(_index);

	if (_lowerTime >= kTimeStartGame) {
		_currentTime = (uint32)getState()->time;
		_time = (uint32)getState()->time;
		_clock->draw(_time);
		if (!_engine->isDemo()) {
			_trainLine->draw(_time);
		}

		initTime(type, value);
	}
}

// Start a game (or load an existing savegame)
void Menu::startGame() {
	// Clear savegame headers
	getSaveLoad()->clear();

	// Hide menu elements
	_clock->clear();
	if (!_engine->isDemo()) {
		_trainLine->clear();
	}

	if (_lastIndex == _index) {
		setGlobalTimer(0);
		if (_index) {
			getSaveLoad()->loadLastGame();
		} else {
			getLogic()->resetState();
			// TODO This check and code (for demo case) may be removed in the future
			if (_engine->isDemo()) {
				getState()->time = kTime2241000;
				getProgress().chapter = kChapter3;
			}
			getEntities()->setup(true, kEntityPlayer);
		}
	} else {
		getSaveLoad()->loadGame(_index);
	}
}

// Switch to the next savegame
void Menu::switchGame() {

	// Switch back to blue game if the current game is not started
	_gameId = SaveLoad::isSavegameValid(_engine->getTargetName(), _gameId) ? getNextGameId() : kGameBlue;

	// Initialize savegame if needed
	if (!SaveLoad::isSavegamePresent(_engine->getTargetName(), _gameId))
		getSaveLoad()->create(_engine->getTargetName(), _gameId);

	getState()->time = kTimeNone;

	// Clear menu elements
	_clock->clear();
	if (!_engine->isDemo()) {
		_trainLine->clear();
	}

	// Clear loaded savegame data
	getSaveLoad()->clear(true);

	init(false, kSavegameTypeIndex, 0);
}

//////////////////////////////////////////////////////////////////////////
// Overlays & elements
//////////////////////////////////////////////////////////////////////////
void Menu::checkHotspots() {
	if (!_isShowingMenu)
		return;

	if (!getFlags()->shouldRedraw)
		return;

	if (_isShowingCredits)
		return;

	SceneHotspot *hotspot = nullptr;
	getScenes()->get(getState()->scene)->checkHotSpot(getCoords(), &hotspot);

	if (hotspot)
		handleEvent((StartMenuAction)hotspot->action, _mouseFlags);
	else
		hideOverlays();
}

void Menu::hideOverlays() {
	_lastHotspot = nullptr;

	// Hide all menu overlays
	for (MenuFrames::iterator it = _frames.begin(); it != _frames.end(); it++)
		showFrame(it->_key, -1, false);

	getScenes()->drawFrames(true);
}

void Menu::showFrame(StartMenuOverlay overlayType, int index, bool redraw) {
	if (index == -1) {
		getScenes()->removeFromQueue(_frames[overlayType]);
	} else {
		// Check that the overlay is valid
		if (!_frames[overlayType])
			return;

		// Remove the frame and add a new one with the proper index
		getScenes()->removeFromQueue(_frames[overlayType]);
		_frames[overlayType]->setFrame((uint16)index);
		getScenes()->addToQueue(_frames[overlayType]);
	}

	if (redraw)
		getScenes()->drawFrames(true);
}

// Remove all frames from the queue
void Menu::clear() {
	for (MenuFrames::iterator it = _frames.begin(); it != _frames.end(); it++)
		getScenes()->removeAndRedraw(&it->_value, false);

	clearBg(GraphicsManager::kBackgroundOverlay);
}

// Get the sequence name to use for the acorn highlight, depending of the currently loaded savegame
Common::String Menu::getAcornSequenceName(GameId id) const {
	if (_engine->isDemo()) {
		return "aconred.seq";
	}

	Common::String name = "";
	switch (id) {
	default:
	case kGameBlue:
		name = "aconblu3.seq";
		break;

	case kGameRed:
		name = "aconred.seq";
		break;

	case kGameGreen:
		name = "acongren.seq";
		break;

	case kGamePurple:
		name = "aconpurp.seq";
		break;

	case kGameTeal:
		name = "aconteal.seq";
		break;

	case kGameGold:
		name = "acongold.seq";
		break;
	}

	return name;
}

//////////////////////////////////////////////////////////////////////////
// Time
//////////////////////////////////////////////////////////////////////////
void Menu::initTime(SavegameType type, uint32 value) {
	if (!value)
		return;

	// The savegame entry index
	uint32 entryIndex = 0;

	switch (type) {
	default:
		break;

	case kSavegameTypeIndex:
		entryIndex = (_index <= value) ? 1 : _index - value;
		break;

	case kSavegameTypeTime:
		if (value < kTimeStartGame)
			break;

		entryIndex = _index;
		if (!entryIndex)
			break;

		// Iterate through existing entries
		do {
			if (getSaveLoad()->getTime(entryIndex) <= value)
				break;

			entryIndex--;
		} while (entryIndex);
		break;

	case kSavegameTypeEvent:
		entryIndex = _index;
		if (!entryIndex)
			break;

		do {
			if (getSaveLoad()->getValue(entryIndex) == value)
				break;

			entryIndex--;
		} while (entryIndex);
		break;

	case kSavegameTypeEvent2:
		// TODO rewrite in a more legible way
		if (_index > 1) {
			uint32 index = _index;
			do {
				if (getSaveLoad()->getValue(index) == value)
					break;

				index--;
			} while (index > 1);

			entryIndex = index - 1;
		} else {
			entryIndex = _index - 1;
		}
		break;
	}

	if (entryIndex) {
		_currentIndex = entryIndex;
		updateTime(getSaveLoad()->getTime(entryIndex));
	}
}

void Menu::updateTime(uint32 time) {
	if (_currentTime == _time)
		_delta = 0;

	_currentTime = time;

	if (_time != time) {
		if (getSoundQueue()->isBuffered(kEntityChapters))
			getSoundQueue()->stop(kEntityChapters);

		getSound()->playSoundWithSubtitles((_currentTime >= _time) ? "LIB042" : "LIB041", kSoundTypeMenu | kSoundFlagFixedVolume | kVolumeFull, kEntityChapters);
		adjustIndex(_currentTime, _time, false);
	}
}

void Menu::adjustIndex(uint32 time1, uint32 time2, bool searchEntry) {
	uint32 index = 0;
	int32 timeDelta = -1;

	if (time1 != time2) {

		index = _index;

		if (time2 >= time1) {
			if (searchEntry) {
				uint32 currentIndex = _index;

				if ((int32)_index >= 0) {
					do {
						// Calculate new delta
						int32 newDelta = time1 - (uint32)getSaveLoad()->getTime(currentIndex);

						if (newDelta >= 0 && timeDelta >= newDelta) {
							timeDelta = newDelta;
							index = currentIndex;
						}

						--currentIndex;
					} while ((int32)currentIndex >= 0);
				}
			} else {
				index = _index - 1;
			}
		} else {
			if (searchEntry) {
				uint32 currentIndex = _index;

				if (_lastIndex >= _index) {
					do {
						// Calculate new delta
						int32 newDelta = (uint32)getSaveLoad()->getTime(currentIndex) - time1;

						if (newDelta >= 0 && timeDelta > newDelta) {
							timeDelta = newDelta;
							index = currentIndex;
						}

						++currentIndex;
					} while (currentIndex <= _lastIndex);
				}
			} else {
				index = _index + 1;
			}
		}

		_index = index;
		checkHotspots();
	}

	if (_index == _currentIndex) {
		if (getProgress().chapter != getSaveLoad()->getChapter(index))
			getProgress().chapter = getSaveLoad()->getChapter(_index);
	}
}

void Menu::goToTime(uint32 time) {

	uint32 entryIndex = 0;
	uint32 deltaTime = (uint32)ABS((int32)(getSaveLoad()->getTime(0) - time));
	uint32 index = 0;

	do {
		uint32 deltaTime2 = (uint32)ABS((int32)(getSaveLoad()->getTime(index) - time));
		if (deltaTime2 < deltaTime) {
			deltaTime = deltaTime2;
			entryIndex = index;
		}

		++index;
	} while (_lastIndex >= index);

	_currentIndex = entryIndex;
	updateTime(getSaveLoad()->getTime(entryIndex));
}

void Menu::setTime() {
	_currentIndex = _index;
	_currentTime = getSaveLoad()->getTime(_currentIndex);

	if (_time == _currentTime)
		adjustTime();
}

void Menu::forwardTime() {
	if (_lastIndex <= _index)
		return;

	_currentIndex = _lastIndex;
	updateTime(getSaveLoad()->getTime(_currentIndex));
}

void Menu::rewindTime() {
	if (!_index)
		return;

	_currentIndex = 0;
	updateTime(getSaveLoad()->getTime(_currentIndex));
}

void Menu::adjustTime() {
	uint32 originalTime = _time;

	// Adjust time delta
	Common::Rational timeDelta(_delta >= 90 ? 9 : (9 * _delta + 89), _delta >= 90 ? 1 : 90);

	if (_currentTime < _time) {
		timeDelta *= 900;
		_time -= (uint)timeDelta.toInt();

		if (_currentTime > _time)
			_time = _currentTime;
	} else {
		timeDelta *= 900;
		_time += (uint)timeDelta.toInt();

		if (_currentTime < _time)
			_time = _currentTime;
	}

	if (_currentTime == _time && getSoundQueue()->isBuffered(kEntityChapters))
		getSoundQueue()->stop(kEntityChapters);

	_clock->draw(_time);
	if (!_engine->isDemo()) {
		_trainLine->draw(_time);
	}
	getScenes()->drawFrames(true);

	adjustIndex(_time, originalTime, true);

	++_delta;
}

void Menu::moveToCity(CityButton city, bool clicked) {
	uint32 time = (uint32)_cityButtonsInfo[city].time;

	// TODO Check if we have access (there seems to be more checks on some internal times) - probably : current_time (menu only) / game time / some other?
	if (_lowerTime < time || _time == time || _currentTime == time) {
		hideOverlays();
		return;
	}

	// Show city overlay
	showFrame((StartMenuOverlay)((_cityButtonsInfo[city].index >> 6) + 3), _cityButtonsInfo[city].index & 63, true);

	if (clicked) {
		showFrame(kOverlayTooltip, -1, true);
		getSound()->playSound(kEntityPlayer, "LIB046");
		goToTime(time);

		_handleTimeDelta = true;

		return;
	}

	// Special case of first and last cities
	if (city == kParis || city == kConstantinople) {
		showFrame(kOverlayTooltip, (city == kParis) ? kTooltipRewindParis : kTooltipForwardConstantinople, true);
		return;
	}

	showFrame(kOverlayTooltip, (_time <= time) ? _cityButtonsInfo[city].forward : _cityButtonsInfo[city].rewind, true);
}

//////////////////////////////////////////////////////////////////////////
// Sound / Brightness
//////////////////////////////////////////////////////////////////////////

// Get current volume (converted internal ScummVM value)
uint32 Menu::getVolume() const {
	return getState()->volume;
}

// Set the volume (converts to ScummVM values)
void Menu::setVolume(uint32 volume) const {
	getState()->volume = volume;

	// Clamp volume
	uint32 value = volume * Audio::Mixer::kMaxMixerVolume / 7;

	if (value > Audio::Mixer::kMaxMixerVolume)
		value = Audio::Mixer::kMaxMixerVolume;

	_engine->_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, (int32)value);
}

uint32 Menu::getBrightness() const {
	return getState()->brightness;
}

void Menu::setBrightness(uint32 brightness) const {
	getState()->brightness = brightness;

	// TODO reload cursor & font with adjusted brightness
}

} // End of namespace LastExpress
