/* 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 "common/system.h"

#include "audio/mixer.h"
#include "graphics/cursorman.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "graphics/managed_surface.h"
#include "graphics/surface.h"

#include "dgds/includes.h"
#include "dgds/font.h"
#include "dgds/globals.h"
#include "dgds/menu.h"
#include "dgds/request.h"
#include "dgds/scene.h"
#include "dgds/sound.h"
#include "dgds/minigames/china_train.h"

namespace Dgds {


// TODO: These are the IDs for Dragon, this code needs updates for China/Beamish/etc
enum MenuButtonIds {
	kMainMenuWillySave = 110,
	kMainMenuWillyLoad = 111,
	kMainMenuWillyRestart = 112,
	kMainMenuWillyQuit = 113,
	kMainMenuWillyVCRHelp = 114,
	kMainMenuWillyHelp = 120,
	kMainMenuWillySoundsOnOff = 115,
	kMainMenuWillyMusicOnOff = 116,

	kMenuWillyCreditsDone = 176,
	kMenuWillyHelpDone = 174,

	kMenuMainPlay = 120,
	kMenuMainControls = 20,
	kMenuMainOptions = 121,
	kMenuMainCalibrate = 118, // or Credits in Willy Beamish
	kMenuMainFiles = 119, // or Play in Willy Beamish
	kMenuMainQuit = 122,

	kMenuControlsVCR = 127,
	kMenuControlsPlay = 128,

	kMenuSliderControlsDifficulty = 123,
	kMenuSliderControlsTextSpeed = 125,
	kMenuSliderControlsDetailLevel = 131,

	kMenuOptionsJoystickOnOff = 139,
	kMenuOptionsJoystickOnOffHoC = 174,
	kMenuOptionsMouseOnOff = 138,
	kMenuOptionsMouseOnOffHoC = 173,
	kMenuOptionsSoundsOnOff = 137,
	kMenuOptionsMusicOnOff = 140,
	kMenuOptionsSoundsOnOffHoC = 175,
	kMenuOptionsSoundsOnOffDE = 172, // German version
	kMenuOptionsMusicOnOffHoC = 171,
	kMenuOptionsVCR = 135,
	kMenuOptionsPlay = 136,

	//kMenuCalibrateJoystickBtn = 145,
	//kMenuCalibrateMouseBtn = 146,
	//kMenuCalibrateVCR = 144,
	//kMenuCalibratePlay = 147,
	//kMenuCalibrateVCRHoC = 159,
	//kMenuCalibratePlayHoC = 158,

	kMenuFilesSave = 107,
	kMenuFilesRestore = 106,
	kMenuFilesRestart = 105,
	kMenuFilesVCR = 103,
	kMenuFilesPlay = 130,

	//kMenuSavePrevious = 58,
	//kMenuSaveNext = 59,
	//kMenuSaveSave = 53,
	//kMenuSaveCancel = 54,
	//kMenuSaveChangeDirectory = 55,
	//kMenuChangeDirectoryOK = 95,
	//kMenuChangeDirectoryCancel = 96,

	//kMenuMouseCalibrationCalibrate = 157,
	//kMenuMouseCalibrationPlay = 155,

	//kMenuJoystickCalibrationOK = 132,

	kMenuQuitYes = 134,
	kMenuQuitNo = 133,

	kMenuMaybeBetterSaveYes = 137,
	kMenuMaybeBetterSaveNo = 138,

	// Intro menu in Rise of the Dragon
	kMenuIntroSkip = 143,
	kMenuIntroPlay = 144,

	// Intro menu in Heart of China / Willy Beamish
	kMenuIntroJumpToIntroduction = 156,
	kMenuIntroJumpToGame = 157,
	kMenuIntroRestore = 150,

	kMenuRestartYes = 163,
	kMenuRestartNo = 164,

	// For Dragon/HOC arcade
	kMenuReplayArcadeYes = 139,
	kMenuReplayArcadeNo = 140,

	kMenuFrustratedArcadeWin = 147,
	kMenuFrustratedArcadeKeepTrying = 148,

	kMenuGameOverQuit = 169,
	kMenuGameOverRestart = 168,
	kMenuGameOverRestore = 170,

	// Tank/train menu in Heart of China
	kMenuTankTrainSkipArcade = 153,
	kMenuTankTrainPlayArcade = 154,

	kMenuButtonNone = 0,
};

Menu::Menu() : _curMenu(kMenuNone), _dragGadget(nullptr), _selectedItem(0), _numSelectable(0), _creditsOffset(0),
	_vcrHelpMode(false) {
_screenBuffer.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
}

Menu::~Menu() {
	_screenBuffer.free();
}

uint16 Menu::mapMenuNum(uint16 num) const {
	// The DE version of Heart of China used different menu IDs for some things.
	// Instead of putting mappings all through the game, just translate them on
	// load to match the other game versions.
	DgdsEngine *engine = DgdsEngine::getInstance();
	if (engine->getGameId() == GID_HOC && engine->getGameLang() == Common::DE_DEU) {
		switch (num) {
		case 50: return kMenuMain;
		case 80: return kMenuSkipPlayIntro;
		case 82: return kMenuSkipArcade;
		default: return num;
		}
	} else {
		return num;
	}
}

void Menu::setRequestData(const REQFileData &data) {
	for (auto &req : data._requests) {
		_menuRequests[mapMenuNum(req._fileNum)] = req;
	}
}

void Menu::loadVCRHelp(const char *fname) {
	DgdsEngine *engine = DgdsEngine::getInstance();
	Common::SeekableReadStream *s = engine->getResourceManager()->getResource(fname);

	if (!s)
		error("Couldn't open RES file for menu help %s", fname);

	static const MenuButtonIds helpIds[] = {
		kMenuButtonNone, // Help not available right now
		kMainMenuWillySave,
		kMainMenuWillyLoad,
		kMainMenuWillyRestart,
		kMainMenuWillyQuit,
		kMainMenuWillyVCRHelp,
		kMainMenuWillySoundsOnOff,
		kMainMenuWillyMusicOnOff,
		kMainMenuWillyHelp,
		kMenuMainCalibrate, // Same ID as Credits in Willy Beamish
		kMenuMainFiles, 	// Same ID as Resume in Willy Beamish
	};

	int menuId = 0;
	_helpStrings.clear();
	Common::Array<Common::String> message;
	while (!s->eos() && !s->err()) {
		Common::String str = s->readLine();
		if (str == "!" || s->eos()) {
			_helpStrings[helpIds[menuId]] = message;
			message.clear();
			menuId++;
		} else {
			message.push_back(str);
		}
	}

	delete s;
}

void Menu::loadCredits() {
	DgdsEngine *engine = DgdsEngine::getInstance();
	Common::SeekableReadStream *s = engine->getResourceManager()->getResource("CREDITS.RES");

	if (!s)
		error("Couldn't open CREDITS.RES");

	_credits.clear();
	while (!s->eos() && !s->err()) {
		_credits.push_back(s->readLine());
	}
	_creditsOffset = 0;
}

void Menu::setScreenBuffer() {
	Graphics::Surface *dst = g_system->lockScreen();
	_screenBuffer.copyFrom(*dst);
	g_system->unlockScreen();
}

bool Menu::updateOptionsGadget(Gadget *gadget) {
	Audio::Mixer *mixer = DgdsEngine::getInstance()->_mixer;
	const char *mouseStr, *soundStr, *musicStr, *onStr, *offStr;
	if (DgdsEngine::getInstance()->getGameLang() == Common::EN_ANY) {
		mouseStr = "MOUSE";
		soundStr = "SOUND";
		musicStr = "MUSIC";
		onStr = "ON";
		offStr = "OFF";
	} else if (DgdsEngine::getInstance()->getGameLang() == Common::DE_DEU) {
		mouseStr = "MAUS";
		soundStr = "TON";
		musicStr = "MUSIK";
		onStr = "AN";
		offStr = "AUS";
	} else {
		error("Unsupported language %d", DgdsEngine::getInstance()->getGameLang());
	}

	switch (gadget->_gadgetNo) {
	case kMenuOptionsJoystickOnOff:
	case kMenuOptionsJoystickOnOffHoC:
		gadget->_buttonName = Common::String::format("JOYSTICK %s", onStr);
		return true;
	case kMenuOptionsMouseOnOff:
	case kMenuOptionsMouseOnOffHoC:
		gadget->_buttonName = Common::String::format("%s %s", mouseStr, onStr);
		return true;
	case kMenuOptionsSoundsOnOff: // same id as kMenuMaybeBetterSaveYes
	case kMenuOptionsSoundsOnOffDE:
	case kMenuOptionsSoundsOnOffHoC: {
		bool isMuted = mixer->isSoundTypeMuted(Audio::Mixer::kSFXSoundType);
		gadget->_buttonName = Common::String::format("%s %s", soundStr, isMuted ? offStr : onStr);
		return true;
	}
	case kMenuOptionsMusicOnOff:
	case kMenuOptionsMusicOnOffHoC: {
		bool isMuted = mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType);
		gadget->_buttonName = Common::String::format("%s %s", musicStr, isMuted ? offStr : onStr);
		return true;
	}
	default:
		return false;
	}
}

void Menu::configureGadget(MenuId menu, Gadget *gadget) {
	DgdsEngine *engine = DgdsEngine::getInstance();

	// a bit of a hack - set up the gadget with the correct value before we draw it.
	if (menu == kMenuControls) {
		SliderGadget *slider = dynamic_cast<SliderGadget *>(gadget);
		if (!slider)
			return;
		switch (gadget->_gadgetNo) {
		case kMenuSliderControlsDifficulty:
			slider->setSteps(3, false);
			slider->setValue(engine->getDifficulty()); // TODO: set a difficulty value
			break;
		case kMenuSliderControlsTextSpeed:
			slider->setSteps(10, false);
			slider->setValue(engine->getTextSpeed());
			break;
		case kMenuSliderControlsDetailLevel:
			slider->setSteps(2, true);
			slider->setValue(engine->getDetailLevel());
			break;
		default:
			break;
			// do nothing.
		}
	} else if (menu == kMenuOptions) {
		updateOptionsGadget(gadget);
	} else if (menu == kMenuMain && engine->getGameId() == GID_WILLY) {
		// HACK: Enable this button which for some reason is disabled
		// by default in data?
		if (gadget->_gadgetNo == kMainMenuWillyHelp)
			toggleGadget(kMainMenuWillyHelp, true);
	}
}

void Menu::onTick() {
	if (_curMenu == kMenuWillyCredits)
		drawMenu(kMenuWillyCredits);
}

void Menu::drawMenu(MenuId menu, bool clearScreen /* = true*/) {
	bool firstDraw = (_curMenu != menu);
	_curMenu = menu;

	Common::Array<Common::SharedPtr<Gadget> > gadgets = _menuRequests[_curMenu]._gadgets;

	// Restore background when drawing submenus
	if (clearScreen)
		g_system->copyRectToScreen(_screenBuffer.getPixels(), _screenBuffer.pitch, 0, 0, _screenBuffer.w, _screenBuffer.h);

	// This is not very efficient, but it only happens once when the menu is opened.
	Graphics::Surface *screen = g_system->lockScreen();
	Graphics::ManagedSurface managed(screen->w, screen->h, screen->format);
	managed.blitFrom(*screen);
	_menuRequests[_curMenu].drawBg(&managed);

	_numSelectable = 0;
	for (Common::SharedPtr<Gadget> &gptr : gadgets) {
		Gadget *gadget = gptr.get();
		if (gadget->_gadgetType == kGadgetButton || gadget->_gadgetType == kGadgetSlider) {
			if (firstDraw)
				configureGadget(menu, gadget);
			gadget->draw(&managed);
			_numSelectable++;
		}
	}
	if (firstDraw) {
		_selectedItem = _numSelectable - 1;
		putMouseOnSelectedItem();
	}

	drawMenuText(managed);

	if (_curMenu == kMenuWillyCredits)
		drawCreditsText(managed);

	// Can't use transparent blit here as the font is often color 0.
	screen->copyRectToSurface(*managed.surfacePtr(), 0, 0, Common::Rect(screen->w, screen->h));

	g_system->unlockScreen();
	g_system->updateScreen();
}

void Menu::hideMenu() {
	_curMenu = kMenuNone;
	if (_vcrHelpMode) {
		_vcrHelpMode = false;
		DgdsEngine::getInstance()->setMouseCursor(-1);
	}
}

Gadget *Menu::getSelectedItem() {
	int item = 0;
	Common::Array<Common::SharedPtr<Gadget> > gadgets = _menuRequests[_curMenu]._gadgets;
	for (Common::SharedPtr<Gadget> &gptr : gadgets) {
		Gadget *gadget = gptr.get();
		if (gadget->_gadgetType == kGadgetButton || gadget->_gadgetType == kGadgetSlider) {
			if (item == _selectedItem)
				return gadget;
			item++;
		}
	}
	return nullptr;
}

void Menu::putMouseOnSelectedItem() {
	Gadget *selected = getSelectedItem();
	if (!selected)
		return;

	const Common::Point midPt = selected->midPoint();
	// put the mouse on the first button/slider
	g_system->warpMouse(midPt.x, midPt.y);
	return;
}


void Menu::drawMenuText(Graphics::ManagedSurface &dst) {
	Common::Array<Common::SharedPtr<Gadget> > gadgets = _menuRequests[_curMenu]._gadgets;
	Common::Array<TextItem> textItems = _menuRequests[_curMenu]._textItemList;

	if (gadgets.empty())
		return;

	// TODO: Get the parent coordinates properly
	uint16 parentX = gadgets[0].get()->_parentX;
	uint16 parentY = gadgets[0].get()->_parentY;
	uint16 pos = 0;

	for (TextItem &textItem : textItems) {
		// HACK: Skip the first entry, which corresponds to the header
		if (pos == 0) {
			pos++;
			continue;
		}

		const DgdsFont *font = RequestData::getMenuFont();
		int w = font->getStringWidth(textItem._txt);
		font->drawString(dst.surfacePtr(), textItem._txt, parentX + textItem._x, parentY + textItem._y, w, 0);
		pos++;
	}
}

static byte _creditsColor(int y) {
	if (y < 31 || y > 86)
		return 17; // Cyan
	else if (y < 32 || y > 85)
		return 18; // Slightly darker cyan
	else if (y < 33 || y > 84)
		return 19; // Darker cyan
	else if (y < 34 || y > 83)
		return 30; // Grey
	return 0; // Black.
}

void Menu::drawCreditsText(Graphics::ManagedSurface &dst) {
	const DgdsFont *font = RequestData::getMenuFont();
	const int lineHeight = font->getFontHeight();

	DgdsRect dlgRect = _menuRequests[_curMenu]._rect;

	const int dlgWidth = dlgRect.width;

	const int yMin = 30;
	const int yMax = 87;
	for (uint i = 0; i < _credits.size(); i++) {
		int dstY = i * lineHeight + yMax - _creditsOffset / 4;
		if (dstY > yMax)
			break;
		if (dstY > yMin) {
			int lineW = font->getStringWidth(_credits[i]);
			int xoff = dlgRect.x + (dlgWidth - lineW) / 2;
			font->drawString(dst.surfacePtr(), _credits[i], xoff, dlgRect.y + dstY, dlgRect.width, _creditsColor(dstY));
		}
	}

	_creditsOffset++;

	if ((uint)_creditsOffset / 4 > lineHeight * (_credits.size() + 1) + (yMax - yMin))
		_creditsOffset = 0;
}

Gadget *Menu::getClickedMenuItem(const Common::Point &mouseClick) {
	if (_curMenu == kMenuNone)
		return nullptr;

	Common::Array<Common::SharedPtr<Gadget> > gadgets = _menuRequests[_curMenu]._gadgets;

	for (Common::SharedPtr<Gadget> &gptr : gadgets) {
		Gadget *gadget = gptr.get();
		if (gadget->_gadgetType == kGadgetButton || gadget->_gadgetType == kGadgetSlider) {
			if (gadget->containsPoint(mouseClick)) {
				return gadget;
			}
		}
	}

	return nullptr;
}

void Menu::onMouseLDown(const Common::Point &mouse) {
	SliderGadget *slider = dynamic_cast<SliderGadget *>(getClickedMenuItem(mouse));
	if (slider) {
		_dragGadget = slider;
		_dragStartPt = mouse;
	}
}

void Menu::onMouseMove(const Common::Point &mouse) {
	if (!_dragGadget)
		return;
	_dragGadget->onDrag(mouse);
	drawMenu(_curMenu);
}

void Menu::onMouseLUp(const Common::Point &mouse) {
	DgdsEngine *engine = DgdsEngine::getInstance();
	if (_dragGadget && mouse != _dragStartPt) {
		int16 setting = _dragGadget->onDragFinish(mouse);
		switch (_dragGadget->_gadgetNo) {
		case kMenuSliderControlsDifficulty:
			engine->setDifficulty(setting);
			break;
		case kMenuSliderControlsTextSpeed:
			engine->setTextSpeed(setting);
			break;
		case kMenuSliderControlsDetailLevel:
			engine->setDetailLevel(static_cast<DgdsDetailLevel>(setting));
			break;
		default:
			break;
		}
		drawMenu(_curMenu);
		_dragGadget = nullptr;
		_dragStartPt = Common::Point();
		return;
	}
	_dragGadget = nullptr;

	Gadget *gadget = getClickedMenuItem(mouse);
	bool isToggle = false;
	if (!gadget)
		return;

	// Click animation
	if (dynamic_cast<ButtonGadget *>(gadget) && !_vcrHelpMode) {
		gadget->toggle(false);
		if (_curMenu == kMenuOptions)
			/*isToggle = */updateOptionsGadget(gadget);
		drawMenu(_curMenu, false);
		g_system->delayMillis(300);
		gadget->toggle(true);
		isToggle = true;
	}

	if (_curMenu == kMenuOptions)
		handleClickOptionsMenu(mouse);
	else if (_curMenu == kMenuSkipPlayIntro)
		handleClickSkipPlayIntroMenu(mouse);
	else
		isToggle &= handleClick(mouse);

	if (isToggle)
		drawMenu(_curMenu);
}


static void _toggleSoundType(Audio::Mixer::SoundType soundType) {
	DgdsEngine *engine = DgdsEngine::getInstance();
	Audio::Mixer *mixer = engine->_mixer;
	//const char *typeStr = (soundType == Audio::Mixer::kMusicSoundType) ? "music" : "sfx";
	if (!mixer->isSoundTypeMuted(soundType)) {
		mixer->muteSoundType(soundType, true);
		engine->_soundPlayer->muteSoundType(soundType);
	} else {
		mixer->muteSoundType(soundType, false);
		engine->_soundPlayer->unmuteSoundType(soundType);
	}
}

bool Menu::handleClick(const Common::Point &mouse) {
	DgdsEngine *engine = DgdsEngine::getInstance();
	int currentScene = engine->getScene()->getNum();
	Gadget *gadget = getClickedMenuItem(mouse);
	int16 clickedMenuItem = gadget->_gadgetNo;

	if (_vcrHelpMode) {
		doVcrHelp(clickedMenuItem);
		return false;
	}

	switch (clickedMenuItem) {
	case kMenuMainPlay:
	case kMenuControlsPlay:
	case kMenuOptionsPlay:
	case kMenuFilesPlay:
	case kMenuMaybeBetterSaveNo:
	//case kMenuCalibratePlay:
	//case kMenuCalibratePlayHoC:
	//case kMenuMouseCalibrationPlay:
		hideMenu();
		if (engine->getGameId() == GID_WILLY && clickedMenuItem == kMainMenuWillyHelp) {
			// TODO: Based on some variable this should instead:
			//drawMenu(kMenuWillyHelp); // with the first message from WVCR.RES
			// The OnLine help system
			// is not available now.
			engine->changeScene(80);
		} else {
			CursorMan.showMouse(false);
		}
		break;
	case kMenuMainControls:
		drawMenu(kMenuControls);
		break;
	case kMenuMainOptions:
		drawMenu(kMenuOptions);
		break;
	case kMenuMainCalibrate: // same as credits button in Willy Beamish
		if (engine->getGameId() == GID_WILLY) {
			hideMenu();
			loadCredits();
			drawMenu(kMenuWillyCredits);
		} else {
			debug("Ignoring calibration request");
		}
		break;
	//case kMenuJoystickCalibrationOK:
	//case kMenuMouseCalibrationCalibrate: // NOTE: same ID as kMenuIntroJumpToGame (for HOC)
	case kMenuIntroJumpToGame:
		if (_curMenu == kMenuSkipPlayIntro) {
			hideMenu();
			engine->setShowClock(true);
			engine->changeScene(24);
		} else {
			// Do nothing - the calibrate menu doesn't offer
			// any functionality in ScummVM
			//drawMenu(kMenuCalibrate);
			drawMenu(_curMenu);
		}
		break;
	case kMenuMainFiles: // Same ID as Play in Willy Beamish
	//case kMenuSaveCancel:
		if (engine->getGameId() == GID_WILLY)
			hideMenu();
		else
			drawMenu(kMenuFiles);
		break;
	case kMenuMainQuit:
		drawMenu(kMenuReallyQuit);
		break;
	case kMainMenuWillyVCRHelp:
		startVcrHelp();
		break;
	//case kMenuCalibrateVCR: // NOTE: same ID as kMenuIntroPlay
	case kMenuIntroPlay:
	case kMenuControlsVCR:
	case kMenuOptionsVCR:
	//case kMenuCalibrateVCRHoC:
	case kMenuFilesVCR:
	case kMenuQuitNo:
	case kMenuRestartNo:
		drawMenu(kMenuMain);
		break;
	//case kMenuCalibrateJoystickBtn:
	//	drawMenu(kMenuCalibrateJoystick);
	//	break;
	//case kMenuCalibrateMouseBtn:
	//	drawMenu(kMenuCalibrateMouse);
	//	break;
	//case kMenuChangeDirectoryCancel:
	//	drawMenu(kMenuSaveDlg);
	//	break;
	case kMenuFilesRestore:
	case kMenuGameOverRestore:
	case kMenuIntroRestore:
	case kMainMenuWillyLoad:
		if (g_engine->loadGameDialog()) {
			hideMenu();
			return false;
		} else {
			drawMenu(_curMenu);
		}
		break;
	case kMenuFilesRestart:
	case kMainMenuWillyRestart:
		drawMenu(kMenuRestart);
		break;
	case kMenuFilesSave: // TODO: Add an option to support original save/load dialogs?
	//case kMenuSavePrevious:
	//case kMenuSaveNext:
	//case kMenuSaveSave:
	case kMenuMaybeBetterSaveYes:
	case kMainMenuWillySave:
		if (g_engine->saveGameDialog())
			hideMenu();
		else
			drawMenu(_curMenu);
		break;
	//case kMenuSaveChangeDirectory:
	//	drawMenu(kMenuChangeDir);
	//	break;
	//case kMenuChangeDirectoryOK:
	//	// Do nothing - the change directory menu doesn't offer
	//	// any functionality in ScummVM
	//	drawMenu(_curMenu);
	//	break;
	case kMenuQuitYes:
		g_engine->quitGame();
		return false;
	case kMenuRestartYes:
		hideMenu();
		engine->restartGame();
		// don't draw the button coming back up.
		return false;
	case kMenuGameOverQuit:
	case kMainMenuWillyQuit:
		drawMenu(kMenuReallyQuit);
		break;
	case kMenuGameOverRestart:
		drawMenu(kMenuRestart);
		break;
	case kMenuSliderControlsDifficulty: {
		SliderGadget *slider = dynamic_cast<SliderGadget *>(gadget);
		if (!slider)
			break;
		int16 setting = slider->onClick(mouse);
		engine->setDifficulty(setting);
		// redraw for update.
		drawMenu(_curMenu);
		break;
	}
	case kMenuSliderControlsTextSpeed: {
		SliderGadget *slider = dynamic_cast<SliderGadget *>(gadget);
		if (!slider)
			break;
		int16 setting = slider->onClick(mouse);
		engine->setTextSpeed(setting);
		drawMenu(_curMenu);
		break;
	}
	case kMenuSliderControlsDetailLevel: {
		SliderGadget *slider = dynamic_cast<SliderGadget *>(gadget);
		if (!slider)
			break;
		int16 setting = slider->onClick(mouse);
		engine->setDetailLevel(static_cast<DgdsDetailLevel>(setting));
		drawMenu(_curMenu);
		break;
	}
	case kMenuReplayArcadeYes:
	case kMenuReplayArcadeNo:
		if (_curMenu != kMenuReplayArcade)
			break;
		if (engine->getGameId() == GID_DRAGON) {
			DragonGlobals *dragonGlobals = static_cast<DragonGlobals *>(engine->getGameGlobals());
			dragonGlobals->setArcadeState(clickedMenuItem == kMenuReplayArcadeYes ? 20 : 10);
		} else if (engine->getGameId() == GID_HOC) {
			engine->getChinaTrain()->setMenuResult(clickedMenuItem == kMenuReplayArcadeYes);
		}
		hideMenu();
		break;
	case kMenuFrustratedArcadeWin:
	case kMenuFrustratedArcadeKeepTrying:
		if (_curMenu != kMenuArcadeFrustrated)
			break;
		if (engine->getGameId() == GID_DRAGON) {
			DragonGlobals *dragonGlobals = static_cast<DragonGlobals *>(engine->getGameGlobals());
			dragonGlobals->setArcadeState(clickedMenuItem == kMenuFrustratedArcadeWin ? 6 : 20);
		} else if (engine->getGameId() == GID_HOC) {
			engine->getChinaTrain()->setMenuResult(clickedMenuItem == kMenuFrustratedArcadeKeepTrying);
		}
		hideMenu();
		break;
	case kMenuTankTrainSkipArcade:
		hideMenu();
		if (currentScene == 73)
			engine->changeScene(12);	// skip tank mini-game
		else if (currentScene == 84)
			engine->changeScene(57);	// skip train mini-game, return to travel map
		break;
	case kMenuTankTrainPlayArcade:
		// Open the "save before arcade" menu before playing the tank
		// or train mini-games.
		drawMenu(kMenuSaveBeforeArcade);
		break;
	case kMainMenuWillySoundsOnOff:
		_toggleSoundType(Audio::Mixer::kSFXSoundType);
		updateOptionsGadget(gadget);
		break;
	case kMainMenuWillyMusicOnOff:
		_toggleSoundType(Audio::Mixer::kMusicSoundType);
		updateOptionsGadget(gadget);
		break;
	case kMenuWillyCreditsDone:
		hideMenu();
		break;
	case kMenuWillyHelpDone:
		drawMenu(kMenuMain, false);
		break;
	default:
		debug(1, "Clicked ID %d", clickedMenuItem);
		break;
	}
	return true;
}

void Menu::startVcrHelp() {
	_vcrHelpMode = true;
	Common::SharedPtr<Graphics::ManagedSurface> iconSurf = RequestData::getCorners()->getSurface(37);
	int hotspotX = iconSurf->w / 2;
	int hotspotY = iconSurf->h / 2;
	CursorMan.replaceCursor(*(iconSurf->surfacePtr()), hotspotX, hotspotY, 0, 0);
}

void Menu::doVcrHelp(int16 button) {
	if (_helpStrings.contains(button)) {
		RequestData &helpRequest = _menuRequests[kMenuWillyVCRHelp];
		const Common::Array<Common::String> msg = _helpStrings[button];
		const DgdsFont *font = RequestData::getMenuFont();
		const Gadget *doneButton = helpRequest.findGadgetByNumWithFlags3Not0x40(174);
		int16 msgHeight = font->getFontHeight() * msg.size();
		int16 msgAreaTop = helpRequest._textItemList.front()._y + font->getFontHeight();
		int16 msgAreaHeight = doneButton->_y - msgAreaTop;
		int16 msgAreaLeft = 0;
		int16 msgAreaWidth = helpRequest._rect.width;

		// Vertically centre the message
		int16 msgTop = msgAreaTop + (msgAreaHeight - msgHeight) / 2;

		while (helpRequest._textItemList.size() > 1)
			helpRequest._textItemList.pop_back();

		for (const Common::String &line : msg) {
			TextItem helpText;
			int msgLen = font->getStringWidth(line);
			// Horizontally centre the line
			helpText._x = msgAreaLeft + (msgAreaWidth - msgLen) / 2;
			helpText._y = msgTop;
			helpText._txt = line;
			msgTop += font->getFontHeight();
			helpRequest._textItemList.push_back(helpText);
		}

		// HACK: Set a different cursor to force an update.
		DgdsEngine::getInstance()->setMouseCursor(1);
		DgdsEngine::getInstance()->setMouseCursor(-1);
		drawMenu(kMenuWillyVCRHelp, false);
		_vcrHelpMode = false;
	}
}

void Menu::handleClickOptionsMenu(const Common::Point &mouse) {
	Gadget *gadget = getClickedMenuItem(mouse);
	int16 clickedMenuItem = gadget->_gadgetNo;

	switch (clickedMenuItem) {
	case kMenuOptionsJoystickOnOff:
	case kMenuOptionsJoystickOnOffHoC:
	case kMenuOptionsMouseOnOff:  // same id as kMenuMaybeBetterSaveNo
	case kMenuOptionsMouseOnOffHoC:
		// Do nothing - we don't toggle joystick or mouse functionality
		break;
	case kMenuOptionsSoundsOnOff: // same id as kMenuMaybeBetterSaveYes
	case kMenuOptionsSoundsOnOffDE:
	case kMenuOptionsSoundsOnOffHoC:
	case kMainMenuWillySoundsOnOff:
		_toggleSoundType(Audio::Mixer::kSFXSoundType);
		updateOptionsGadget(gadget);
		break;
	case kMenuOptionsMusicOnOff:
	case kMenuOptionsMusicOnOffHoC:
	case kMainMenuWillyMusicOnOff:
		_toggleSoundType(Audio::Mixer::kMusicSoundType);
		updateOptionsGadget(gadget);
		break;
	default:
		handleClick(mouse);
		break;
	}
}

void Menu::handleClickSkipPlayIntroMenu(const Common::Point &mouse) {
	DgdsEngine *engine = DgdsEngine::getInstance();
	Gadget *gadget = getClickedMenuItem(mouse);
	int16 clickedMenuItem = gadget->_gadgetNo;

	switch (clickedMenuItem) {
	case kMenuIntroPlay:
		hideMenu();
		break;
	case kMenuIntroSkip:
		hideMenu();
		engine->setShowClock(true);
		engine->changeScene(5);
		break;
	case kMenuIntroJumpToIntroduction:
		hideMenu();
		if (engine->getGameId() == GID_HOC)
			engine->changeScene(100);
		else if (engine->getGameId() == GID_WILLY)
			engine->changeScene(24);
		break;
	case kMenuIntroJumpToGame:
		hideMenu();
		if (engine->getGameId() == GID_HOC)
			engine->changeScene(24);
		else if (engine->getGameId() == GID_WILLY)
			engine->changeScene(4);
		break;
	default:
		handleClick(mouse);
		break;
	}
}

void Menu::toggleGadget(int16 gadgetId, bool enable) {
	Common::Array<Common::SharedPtr<Gadget> > gadgets = _menuRequests[_curMenu]._gadgets;

	for (Common::SharedPtr<Gadget> &gptr : gadgets) {
		Gadget *gadget = gptr.get();
		if (gadget->_gadgetNo == gadgetId) {
			gadget->toggle(enable);
			return;
		}
	}
}

/**
 * Choose the next menu item via keyboard - items are numbered backwards
 * so this *decreases* the counter.
 */
void Menu::nextChoice() {
	_selectedItem--;
	if (_selectedItem < 0)
		_selectedItem = _numSelectable - 1;
	putMouseOnSelectedItem();
}

void Menu::prevChoice() {
	_selectedItem++;
	if (_selectedItem >= _numSelectable)
		_selectedItem = 0;
	putMouseOnSelectedItem();
}

void Menu::activateChoice() {
	Gadget *selected = getSelectedItem();
	if (!selected)
		return;

	handleClick(selected->midPoint());
}

} // End of namespace Dgds
