/* 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 "twine/menu/menu.h"
#include "audio/mixer.h"
#include "backends/audiocd/audiocd.h"
#include "backends/keymapper/keymapper.h"
#include "common/config-manager.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/system.h"
#include "common/util.h"
#include "graphics/cursorman.h"
#include "twine/audio/music.h"
#include "twine/audio/sound.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/menu/menuoptions.h"
#include "twine/movies.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/text.h"
#include "twine/twine.h"

namespace TwinE {

static const uint32 kPlasmaEffectFilesize = 262176;

#define HEIGHT_STANDARD 50
#define MENU_SPACE 6

#define	SIZE_INV_OBJ_X	75
#define	SIZE_INV_OBJ_Y	65

namespace MenuButtonTypes {
enum MenuButtonTypesEnum {
	kMusicVolume = 1,
	kSoundVolume = 2,
	kCDVolume = 3,
	kSpeechVolume = 4,
	kAggressiveMode = 5,
	kPolygonDetails = 6,
	kShadowSettings = 7,
	kSceneryZoom = 8,
	kHighResolution = 9,
	kWallCollision = 10,
	kVoice = 11
};
}

#define checkMenuQuit(callMenu)      \
	if ((callMenu) == kQuitEngine) { \
		return kQuitEngine;          \
	}

namespace _priv {

static MenuSettings createMainMenu(bool lba1) {
	MenuSettings settings;
	if (lba1) {
		settings.setButtonsBoxHeight(200);
		settings.addButton(TextId::kNewGame);
		settings.addButton(TextId::kContinueGame);
		settings.addButton(TextId::kOptions);
		settings.addButton(TextId::kQuit);
	} else {
		settings.setButtonsBoxHeight(335);
		settings.addButton(TextId::toContinueGame);
		settings.addButton(TextId::toNewGame);
		settings.addButton(TextId::toOptions);
		settings.addButton(TextId::toQuit);
	}
	return settings;
}

static MenuSettings createLba1ClassicNewGame() {
	MenuSettings settings;
	settings.addButton(TextId::kReturnMenu);
	settings.addButton(TextId::kNewGame);
	settings.addButton(TextId::kNewGamePlus);
	return settings;
}

static MenuSettings createGiveUpMenu() {
	MenuSettings settings;
	settings.setButtonsBoxHeight(240);
	settings.addButton(TextId::kContinue);
	settings.addButton(TextId::kGiveUp);
	return settings;
}

static MenuSettings createGiveUpSaveMenu() {
	MenuSettings settings;
	settings.setButtonsBoxHeight(240);
	settings.addButton(TextId::kContinue);
	settings.addButton(TextId::kCreateSaveGame);
	settings.addButton(TextId::kGiveUp);
	return settings;
}

static MenuSettings createLanguageMenu(bool lba1) {
	MenuSettings settings;
	settings.addButton(TextId::kReturnMenu);
	settings.addButton(TextId::kCustomVoicesEnglish, MenuButtonTypes::kVoice);
	return settings;
}

static MenuSettings createOptionsMenu(bool lba1) {
	MenuSettings settings;
	settings.addButton(TextId::kReturnMenu);
	settings.addButton(TextId::kVolumeSettings);
	settings.addButton(TextId::kSaveManage);
	settings.addButton(TextId::kCustomLanguageOption);
	settings.addButton(TextId::kAdvanced);
	return settings;
}

static MenuSettings createAdvancedOptionsMenu() {
	MenuSettings settings;
	settings.addButton(TextId::kReturnMenu);
	settings.addButton(TextId::kBehaviourAggressiveManual, MenuButtonTypes::kAggressiveMode);
	settings.addButton(TextId::kDetailsPolygonsHigh, MenuButtonTypes::kPolygonDetails);
	settings.addButton(TextId::kDetailsShadowHigh, MenuButtonTypes::kShadowSettings);
	settings.addButton(TextId::kSceneryZoomOn, MenuButtonTypes::kSceneryZoom);
	settings.addButton(TextId::kCustomHighResOptionOn, MenuButtonTypes::kHighResolution);
	settings.addButton(TextId::kCustomWallCollisionOff, MenuButtonTypes::kWallCollision);
	return settings;
}

static MenuSettings createSaveManageMenu() {
	MenuSettings settings;
	settings.addButton(TextId::kReturnMenu);
	settings.addButton(TextId::kCreateSaveGame);
	settings.addButton(TextId::kDeleteSaveGame);
	return settings;
}

static MenuSettings createVolumeMenu() {
	MenuSettings settings;
	settings.addButton(TextId::kReturnMenu);
	settings.addButton(TextId::kMusicVolume, MenuButtonTypes::kMusicVolume);
	settings.addButton(TextId::kSoundVolume, MenuButtonTypes::kSoundVolume);
	settings.addButton(TextId::kCDVolume, MenuButtonTypes::kCDVolume);
	settings.addButton(TextId::kSpeechVolume, MenuButtonTypes::kSpeechVolume);
	return settings;
}

} // namespace _priv

const char *MenuSettings::getButtonText(Text *text, int buttonIndex) {
	if (_buttonTexts[buttonIndex].empty()) {
		TextId textId = getButtonTextId(buttonIndex);
		char dialText[256] = "";
		if (textId == TextId::kNewGamePlus) {
			text->getMenuText(TextId::kNewGame, dialText, sizeof(dialText));
			Common::strlcat(dialText, "+", sizeof(dialText));
		} else {
			text->getMenuText(textId, dialText, sizeof(dialText));
		}
		_buttonTexts[buttonIndex] = dialText;
	}
	return _buttonTexts[buttonIndex].c_str();
}

Menu::Menu(TwinEEngine *engine) {
	_engine = engine;

	_optionsMenuState = _priv::createOptionsMenu(engine->isLBA1());
	_languageMenuState = _priv::createLanguageMenu(engine->isLBA1());
	_giveUpMenuWithSaveState = _priv::createGiveUpSaveMenu();
	_volumeMenuState = _priv::createVolumeMenu();
	_saveManageMenuState = _priv::createSaveManageMenu();
	_giveUpMenuState = _priv::createGiveUpMenu();
	_mainMenuState = _priv::createMainMenu(engine->isLBA1());
	_newGameMenuState = _priv::createLba1ClassicNewGame();
	_advOptionsMenuState = _priv::createAdvancedOptionsMenu();

	Common::fill(&_behaviourAnimState[0], &_behaviourAnimState[4], 0);
	Common::fill(&_itemAngle[0], &_itemAngle[NUM_INVENTORY_ITEMS], 0);
}

Menu::~Menu() {
	free(_plasmaEffectPtr);
}

void Menu::plasmaEffectRenderFrame() {
	for (int32 j = 1; j < PLASMA_HEIGHT - 1; j++) {
		for (int32 i = 1; i < PLASMA_WIDTH - 1; i++) {
			/* Here we calculate the average of all 8 neighbour pixel values */

			int16 c;
			c = _plasmaEffectPtr[(i - 1) + (j - 1) * PLASMA_WIDTH];  // top-left
			c += _plasmaEffectPtr[(i + 0) + (j - 1) * PLASMA_WIDTH]; // top
			c += _plasmaEffectPtr[(i + 1) + (j - 1) * PLASMA_WIDTH]; // top-right

			c += _plasmaEffectPtr[(i - 1) + (j + 0) * PLASMA_WIDTH]; // left
			c += _plasmaEffectPtr[(i + 1) + (j + 0) * PLASMA_WIDTH]; // right

			c += _plasmaEffectPtr[(i - 1) + (j + 1) * PLASMA_WIDTH]; // bottom-left
			c += _plasmaEffectPtr[(i + 0) + (j + 1) * PLASMA_WIDTH]; // bottom
			c += _plasmaEffectPtr[(i + 1) + (j + 1) * PLASMA_WIDTH]; // bottom-right

			/* And the 2 least significant bits are used as a
			 * randomizing parameter for statistically fading the flames */
			c = (c >> 3) | ((c & 0x0003) << 13);

			if (!(c & 0x6500) &&
				(j >= (PLASMA_HEIGHT - 4) || c > 0)) {
				c--; /*fade this pixel*/
			}

			/* plot the pixel using the calculated color */
			_plasmaEffectPtr[i + (PLASMA_HEIGHT + j) * PLASMA_WIDTH] = (uint8)c;
		}
	}

	// flip the double-buffer while scrolling the effect vertically:
	const uint8 *src = _plasmaEffectPtr + (PLASMA_HEIGHT + 1) * PLASMA_WIDTH;
	memcpy(_plasmaEffectPtr, src, PLASMA_HEIGHT * PLASMA_WIDTH);
}

void Menu::processPlasmaEffect(const Common::Rect &rect, int32 color) {
	if (_engine->isLBA2()) {
		// TODO: effects are handled differently here.
		return;
	}
	const int32 max_value = color + 15;

	plasmaEffectRenderFrame();

	const uint8 *in = _plasmaEffectPtr + 5 * PLASMA_WIDTH;
	uint8 *out = (uint8 *)_engine->_imageBuffer.getBasePtr(0, 0);

	for (int32 y = 0; y < PLASMA_HEIGHT / 2; y++) {
		int32 yOffset = y * _engine->_imageBuffer.w;
		const uint8 *colPtr = &in[y * PLASMA_WIDTH];
		for (int32 x = 0; x < PLASMA_WIDTH; x++) {
			const uint8 c = MIN(*colPtr / 2 + color, max_value);
			/* 2x2 squares sharing the same pixel color: */
			const int32 target = 2 * yOffset;
			out[target + 0] = c;
			out[target + 1] = c;
			out[target + _engine->_imageBuffer.w + 0] = c;
			out[target + _engine->_imageBuffer.w + 1] = c;
			++colPtr;
			++yOffset;
		}
	}
	const Common::Rect prect(0, 0, PLASMA_WIDTH, PLASMA_HEIGHT);
	_engine->_frontVideoBuffer.blitFrom(_engine->_imageBuffer, prect, rect);
}

void Menu::drawRectBorders(const Common::Rect &rect, int32 colorLeftTop, int32 colorRightBottom) { // DrawCadre
	_engine->_interface->drawLine(rect.left, rect.top, rect.right, rect.top, colorLeftTop);                   // top line
	_engine->_interface->drawLine(rect.left, rect.top + 1, rect.left, rect.bottom, colorLeftTop);             // left line
	_engine->_interface->drawLine(rect.right, rect.top + 1, rect.right, rect.bottom, colorRightBottom);       // right line
	_engine->_interface->drawLine(rect.left + 1, rect.bottom, rect.right - 1, rect.bottom, colorRightBottom); // bottom line
}

void Menu::drawRectBorders(int32 left, int32 top, int32 right, int32 bottom, int32 colorLeftTop, int32 colorRightBottom) { // DrawCadre
	drawRectBorders(Common::Rect(left, top, right, bottom), colorLeftTop, colorLeftTop);
}

void Menu::drawButtonGfx(const MenuSettings *menuSettings, const Common::Rect &rect, int32 buttonId, const char *dialText, bool hover) {
	if (hover) {
		if (menuSettings == &_volumeMenuState && buttonId <= MenuButtonTypes::kSpeechVolume && buttonId >= MenuButtonTypes::kMusicVolume) {
			int32 newWidth = 0;
			switch (buttonId) {
			case MenuButtonTypes::kMusicVolume: {
				const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
				newWidth = ruleThree32(rect.left, rect.right, Audio::Mixer::kMaxMixerVolume, volume);
				break;
			}
			case MenuButtonTypes::kSoundVolume: {
				const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
				newWidth = ruleThree32(rect.left, rect.right, Audio::Mixer::kMaxMixerVolume, volume);
				break;
			}
			case MenuButtonTypes::kCDVolume: {
				const AudioCDManager::Status status = _engine->_system->getAudioCDManager()->getStatus();
				newWidth = ruleThree32(rect.left, rect.right, Audio::Mixer::kMaxMixerVolume, status.volume);
				break;
			}
			case MenuButtonTypes::kSpeechVolume: {
				const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
				newWidth = ruleThree32(rect.left, rect.right, Audio::Mixer::kMaxMixerVolume, volume);
				break;
			}
			}

			processPlasmaEffect(rect, COLOR_80);
			if (!(_engine->getRandomNumber() % 5)) {
				_plasmaEffectPtr[(_engine->getRandomNumber() % PLASMA_WIDTH * 10) + 20 * PLASMA_WIDTH] = 255;
			}
			_engine->_interface->box(Common::Rect(newWidth, rect.top, rect.right, rect.bottom), COLOR_SELECT_MENU);
		} else {
			processPlasmaEffect(rect, COLOR_64);
			if (!(_engine->getRandomNumber() % 5)) {
				_plasmaEffectPtr[_engine->getRandomNumber() % PLASMA_WIDTH * 10 + 6400] = 255;
			}
		}
	} else {
		_engine->blitWorkToFront(rect);
		_engine->_interface->shadeBox(rect, 4);
	}

	drawRectBorders(rect);

	_engine->_text->setFontColor(COLOR_WHITE);
	_engine->_text->setFont(INTER_LEAVE, INTER_SPACE);
	const int32 textSize = _engine->_text->sizeFont(dialText);
	_engine->_text->drawText((_engine->width() / 2) - (textSize / 2), rect.top + 7, dialText);
}

int16 Menu::drawButtons(MenuSettings *menuSettings, bool hover) {
	int16 buttonNumber = menuSettings->getActiveButton();
	const int32 maxButton = menuSettings->getButtonCount();
	int32 topHeight = menuSettings->getButtonBoxHeight();

	if (topHeight == 0) {
		topHeight = 35;
	} else {
		topHeight = topHeight - (((maxButton - 1) * 6) + (maxButton * HEIGHT_STANDARD)) / 2;
	}

	if (maxButton <= 0) {
		return -1;
	}

	int16 mouseActiveButton = -1;

	for (int16 i = 0; i < maxButton; ++i) {
		if (menuSettings == &_advOptionsMenuState) {
			int16 id = menuSettings->getButtonState(i);
			switch (id) {
			case MenuButtonTypes::kAggressiveMode:
				if (_engine->_actor->_combatAuto) {
					menuSettings->setButtonTextId(i, TextId::kBehaviourAggressiveAuto);
				} else {
					menuSettings->setButtonTextId(i, TextId::kBehaviourAggressiveManual);
				}
				break;
			case MenuButtonTypes::kPolygonDetails:
				if (_engine->_cfgfile.PolygonDetails == 0) {
					menuSettings->setButtonTextId(i, TextId::kDetailsPolygonsLow);
				} else if (_engine->_cfgfile.PolygonDetails == 1) {
					menuSettings->setButtonTextId(i, TextId::kDetailsPolygonsMiddle);
				} else {
					menuSettings->setButtonTextId(i, TextId::kDetailsPolygonsHigh);
				}
				break;
			case MenuButtonTypes::kShadowSettings:
				if (_engine->_cfgfile.ShadowMode == 0) {
					menuSettings->setButtonTextId(i, TextId::kShadowsDisabled);
				} else if (_engine->_cfgfile.ShadowMode == 1) {
					menuSettings->setButtonTextId(i, TextId::kShadowsFigures);
				} else {
					menuSettings->setButtonTextId(i, TextId::kDetailsShadowHigh);
				}
				break;
			case MenuButtonTypes::kSceneryZoom:
				if (_engine->_cfgfile.SceZoom) {
					menuSettings->setButtonTextId(i, TextId::kSceneryZoomOn);
				} else {
					menuSettings->setButtonTextId(i, TextId::kNoSceneryZoom);
				}
				break;
			case MenuButtonTypes::kHighResolution: {
				if (ConfMan.getBool("usehighres")) {
					menuSettings->setButtonTextId(i, TextId::kCustomHighResOptionOn);
				} else {
					menuSettings->setButtonTextId(i, TextId::kCustomHighResOptionOff);
				}
				break;
			}
			case MenuButtonTypes::kWallCollision: {
				if (ConfMan.getBool("wallcollision")) {
					menuSettings->setButtonTextId(i, TextId::kCustomWallCollisionOn);
				} else {
					menuSettings->setButtonTextId(i, TextId::kCustomWallCollisionOff);
				}
				break;
			}
			default:
				break;
			}
		} else if (menuSettings == &_languageMenuState) {
			int16 id = menuSettings->getButtonState(i);
			switch (id) {
			case MenuButtonTypes::kVoice: {
				const int voiceLanguage = ConfMan.getInt("audio_language");
				menuSettings->setButtonTextId(i, (TextId)((int)TextId::kCustomVoicesNone - voiceLanguage));
				break;
			}
			default:
				break;
			}
		}
		const int32 menuItemId = menuSettings->getButtonState(i);
		const char *text = menuSettings->getButtonText(_engine->_text, i);
		const int32 border = 45;
		const int32 mainMenuButtonHeightHalf = HEIGHT_STANDARD / 2;
		const Common::Rect rect(border, topHeight - mainMenuButtonHeightHalf, _engine->width() - border, topHeight + mainMenuButtonHeightHalf);
		if (hover) {
			if (i == buttonNumber) {
				drawButtonGfx(menuSettings, rect, menuItemId, text, hover);
			}
		} else {
			if (i == buttonNumber) {
				drawButtonGfx(menuSettings, rect, menuItemId, text, true);
			} else {
				drawButtonGfx(menuSettings, rect, menuItemId, text, false);
			}
		}
		if (_engine->_input->isMouseHovering(rect)) {
			mouseActiveButton = i;
		}

		topHeight += HEIGHT_STANDARD + MENU_SPACE; // increase button top height
	}
	return mouseActiveButton;
}

void Menu::menuDemo() {
	// TODO: lba2 only show the credits only in the main menu and you could force it by pressing shift+c
	// TODO: lba2 has a cd audio track (2) for the credits
	_engine->_menuOptions->showCredits();
	if (_engine->_movie->playMovie(FLA_DRAGON3)) {
		if (!_engine->_screens->loadImageDelay(TwineImage(Resources::HQR_RESS_FILE, 15, 16), 3)) {
			if (!_engine->_screens->loadImageDelay(TwineImage(Resources::HQR_RESS_FILE, 17, 18), 3)) {
				if (!_engine->_screens->loadImageDelay(TwineImage(Resources::HQR_RESS_FILE, 19, 20), 3)) {
					if (_engine->_movie->playMovie(FLA_BATEAU)) {
						if (_engine->_cfgfile.Version == USA_VERSION) {
							_engine->_screens->loadImageDelay(_engine->_resources->relentLogo(), 3);
						} else {
							_engine->_screens->loadImageDelay(_engine->_resources->lbaLogo(), 3);
						}
						_engine->_screens->adelineLogo();
					}
				}
			}
		}
	}
	_engine->_screens->loadMenuImage(false);
}

int32 Menu::doGameMenu(MenuSettings *menuSettings) {
	int16 currentButton = menuSettings->getActiveButton();
	bool buttonsNeedRedraw = true;
	const int32 numEntry = menuSettings->getButtonCount();
	int32 maxButton = numEntry - 1;

	_engine->_input->enableKeyMap(uiKeyMapId);

	// if we are running the game already, the buttons are just rendered on top of the scene
	if (_engine->_scene->isGameRunning()) {
		_engine->restoreFrontBuffer();
	} else {
		_engine->_screens->loadMenuImage(false);
	}
	uint32 startMillis = _engine->_system->getMillis();
	do {
		FrameMarker frame(_engine);
		const uint32 loopMillis = _engine->_system->getMillis();
		_engine->readKeys();

		if (_engine->_input->toggleActionIfActive(TwinEActionType::UIDown)) {
			currentButton++;
			if (currentButton == numEntry) { // if current button is the last, than next button is the first
				currentButton = 0;
			}
			buttonsNeedRedraw = true;
			startMillis = loopMillis;
		} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIUp)) {
			currentButton--;
			if (currentButton < 0) { // if current button is the first, than previous button is the last
				currentButton = maxButton;
			}
			buttonsNeedRedraw = true;
			startMillis = loopMillis;
		}

		const int16 id = menuSettings->getActiveButtonState();
		if (menuSettings == &_advOptionsMenuState) {
			switch (id) {
			case MenuButtonTypes::kAggressiveMode:
				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft) || _engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
					_engine->_actor->_combatAuto = !_engine->_actor->_combatAuto;
					startMillis = loopMillis;
				}
				break;
			case MenuButtonTypes::kPolygonDetails:
				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft)) {
					_engine->_cfgfile.PolygonDetails--;
					_engine->_cfgfile.PolygonDetails %= 3;
					startMillis = loopMillis;
				} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
					_engine->_cfgfile.PolygonDetails++;
					_engine->_cfgfile.PolygonDetails %= 3;
					startMillis = loopMillis;
				}
				break;
			case MenuButtonTypes::kShadowSettings:
				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft)) {
					_engine->_cfgfile.ShadowMode--;
					_engine->_cfgfile.ShadowMode %= 3;
					startMillis = loopMillis;
				} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
					_engine->_cfgfile.ShadowMode++;
					_engine->_cfgfile.ShadowMode %= 3;
					startMillis = loopMillis;
				}
				break;
			case MenuButtonTypes::kSceneryZoom:
				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft) || _engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
					_engine->_cfgfile.SceZoom = !_engine->_cfgfile.SceZoom;
					startMillis = loopMillis;
				}
				break;
			case MenuButtonTypes::kHighResolution:
				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft) || _engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
					const bool highRes = ConfMan.getBool("usehighres");
					ConfMan.setBool("usehighres", !highRes);
					startMillis = loopMillis;
				}
				break;
			case MenuButtonTypes::kWallCollision:
				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft) || _engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
					const bool highRes = ConfMan.getBool("wallcollision");
					ConfMan.setBool("wallcollision", !highRes);
					startMillis = loopMillis;
				}
				break;
			default:
				break;
			}
		} else if (menuSettings == &_languageMenuState) {
			switch (id) {
			case MenuButtonTypes::kVoice:
				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft)) {
					int voiceLanguage = ConfMan.getInt("audio_language");
					--voiceLanguage;
					if (voiceLanguage < 0) {
						voiceLanguage = 3;
					}
					ConfMan.setInt("audio_language", voiceLanguage);
				} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
					int voiceLanguage = ConfMan.getInt("audio_language");
					++voiceLanguage;
					if (voiceLanguage > 3) {
						voiceLanguage = 0;
					}
					ConfMan.setInt("audio_language", voiceLanguage);
				}
				break;
			default:
				break;
			}
		} else if (menuSettings == &_volumeMenuState) {
			Audio::Mixer *mixer = _engine->_system->getMixer();
			switch (id) {
			case MenuButtonTypes::kMusicVolume: {
				int volume = mixer->getVolumeForSoundType(Audio::Mixer::SoundType::kMusicSoundType);
				if (_engine->_input->isActionActive(TwinEActionType::UILeft)) {
					volume -= 4;
					startMillis = loopMillis;
				} else if (_engine->_input->isActionActive(TwinEActionType::UIRight)) {
					volume += 4;
					startMillis = loopMillis;
				}
				_engine->_music->musicVolume(volume);
				ConfMan.setInt("music_volume", mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
				break;
			}
			case MenuButtonTypes::kSoundVolume: {
				int volume = mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
				if (_engine->_input->isActionActive(TwinEActionType::UILeft)) {
					volume -= 4;
					startMillis = loopMillis;
				} else if (_engine->_input->isActionActive(TwinEActionType::UIRight)) {
					volume += 4;
					startMillis = loopMillis;
				}

				mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume);
				ConfMan.setInt("sfx_volume", mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));
				break;
			}
			case MenuButtonTypes::kCDVolume: {
				AudioCDManager::Status status = _engine->_system->getAudioCDManager()->getStatus();
				if (_engine->_input->isActionActive(TwinEActionType::UILeft)) {
					status.volume -= 4;
					startMillis = loopMillis;
				} else if (_engine->_input->isActionActive(TwinEActionType::UIRight)) {
					status.volume += 4;
					startMillis = loopMillis;
				}
				status.volume = CLIP(status.volume, 0, 255);
				_engine->_system->getAudioCDManager()->setVolume(status.volume);
				break;
			}
			case MenuButtonTypes::kSpeechVolume: {
				int volume = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
				if (_engine->_input->isActionActive(TwinEActionType::UILeft)) {
					volume -= 4;
					startMillis = loopMillis;
				} else if (_engine->_input->isActionActive(TwinEActionType::UIRight)) {
					volume += 4;
					startMillis = loopMillis;
				}
				mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume);
				ConfMan.setInt("speech_volume", mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
				break;
			}
			default:
				break;
			}
		}

		if (buttonsNeedRedraw) {
			// draw all buttons
			const int16 mouseButtonHovered = drawButtons(menuSettings, false);
			if (mouseButtonHovered != -1) {
				currentButton = mouseButtonHovered;
			}
			menuSettings->setActiveButton(currentButton);
		}

		// draw plasma effect for the current selected button
		const int16 mouseButtonHovered = drawButtons(menuSettings, true);
		if (mouseButtonHovered != -1) {
			if (mouseButtonHovered != currentButton) {
				buttonsNeedRedraw = true;
			}
			currentButton = mouseButtonHovered;
		}

		if (_engine->shouldQuit()) {
			return kQuitEngine;
		}
		if (_engine->_input->toggleActionIfActive(TwinEActionType::UIAbort)) {
			for (int i = 0; i < menuSettings->getButtonCount(); ++i) {
				const TextId textId = menuSettings->getButtonTextId(i);
				if (textId == TextId::kReturnMenu || textId == TextId::kReturnGame || textId == TextId::kContinue) {
					return (int32)textId;
				}
			}
			startMillis = loopMillis;
		}
		if (menuSettings == &_mainMenuState) {
			uint32 idleTime = 60 * 3 + 53 * 1000;
			if (_engine->isDemo()) {
				idleTime = 60 * 1000;
			}
			if (loopMillis - startMillis > idleTime) {
				return kDemoMenu;
			}
		}
	} while (!_engine->_input->toggleActionIfActive(TwinEActionType::UIEnter));

	return (int32)menuSettings->getActiveButtonTextId();
}

int32 Menu::advoptionsMenu() {
	_engine->restoreFrontBuffer();

	ScopedCursor scoped(_engine);
	for (;;) {
		switch (doGameMenu(&_advOptionsMenuState)) {
		case (int32)TextId::kReturnMenu: {
			return 0;
		}
		case kQuitEngine:
			return kQuitEngine;
		case (int32)TextId::kBehaviourAggressiveManual:
		case (int32)TextId::kDetailsPolygonsHigh:
		case (int32)TextId::kDetailsShadowHigh:
		case (int32)TextId::kSceneryZoomOn:
		default:
			warning("Unknown menu button handled");
			break;
		}
	}

	return 0;
}

int32 Menu::savemanageMenu() {
	_engine->restoreFrontBuffer();

	ScopedCursor scoped(_engine);
	for (;;) {
		switch (doGameMenu(&_saveManageMenuState)) {
		case (int32)TextId::kReturnMenu:
			return 0;
		case (int32)TextId::kCreateSaveGame:
			_engine->_menuOptions->saveGameMenu();
			break;
		case (int32)TextId::kDeleteSaveGame:
			_engine->_menuOptions->deleteSaveMenu();
			break;
		case kQuitEngine:
			return kQuitEngine;
		default:
			warning("Unknown menu button handled");
			break;
		}
	}

	return 0;
}

int32 Menu::volumeOptions() {
	_engine->restoreFrontBuffer();

	if (_engine->isLBA1()) {
		if (_engine->isCDROM()) {
			_engine->_music->playAllMusic(9);
		} else {
			_engine->_music->playMidiFile(9);
		}
	}

	ScopedCursor scoped(_engine);
	for (;;) {
		switch (doGameMenu(&_volumeMenuState)) {
		case (int32)TextId::kReturnMenu:
			return 0;
		case kQuitEngine:
			return kQuitEngine;
		case (int32)TextId::kMusicVolume:
		case (int32)TextId::kSoundVolume:
		case (int32)TextId::kCDVolume:
		case (int32)TextId::kSpeechVolume:
		default:
			warning("Unknown menu button handled");
			break;
		}
	}

	return 0;
}

int32 Menu::languageMenu() {
	_engine->restoreFrontBuffer();

	ScopedCursor scoped(_engine);
	for (;;) {
		switch (doGameMenu(&_languageMenuState)) {
		case (int32)TextId::kReturnMenu:
			return 0;
		case kQuitEngine:
			return kQuitEngine;
		case (int32)MenuButtonTypes::kVoice:
		default:
			warning("Unknown menu button handled");
			break;
		}
	}

	return 0;
}

void Menu::inGameOptionsMenu() {
	_engine->_text->initDial(TextBankId::Options_and_menus);
	_optionsMenuState.setButtonTextId(0, TextId::kReturnGame);
	_engine->saveFrontBuffer();
	optionsMenu();
	_engine->_text->initSceneTextBank();
	_optionsMenuState.setButtonTextId(0, TextId::kReturnMenu);
}

int32 Menu::optionsMenu() {
	_engine->restoreFrontBuffer();

	_engine->_sound->stopSamples();
	if (_engine->isLBA1()) {
		// LBA's Theme
		if (_engine->isCDROM()) {
			_engine->_music->playCdTrack(9);
		} else {
			_engine->_music->playMidiFile(9);
		}
	}

	ScopedCursor scoped(_engine);
	for (;;) {
		switch (doGameMenu(&_optionsMenuState)) {
		case (int32)TextId::kReturnGame:
		case (int32)TextId::kReturnMenu: {
			return 0;
		}
		case (int32)TextId::kVolumeSettings: {
			checkMenuQuit(volumeOptions()) break;
		}
		case (int32)TextId::kCustomLanguageOption: {
			checkMenuQuit(languageMenu()) break;
		}
		case (int32)TextId::kSaveManage: {
			checkMenuQuit(savemanageMenu()) break;
		}
		case (int32)TextId::kAdvanced: {
			checkMenuQuit(advoptionsMenu()) break;
		}
		case kQuitEngine:
			return kQuitEngine;
		default:
			break;
		}
	}

	return 0;
}

int32 Menu::newGameClassicMenu() {
	_engine->restoreFrontBuffer();

	ScopedCursor scoped(_engine);
	for (;;) {
		switch (doGameMenu(&_newGameMenuState)) {
		case (int32)TextId::kReturnGame:
		case (int32)TextId::kReturnMenu: {
			return 0;
		}
		case (int32)TextId::kNewGamePlus:
			_engine->_gameState->_endGameItems = true;
			if (_engine->_menuOptions->newGameMenu()) {
				return 1;
			}
			break;
		case (int32)TextId::kNewGame: {
			_engine->_gameState->_endGameItems = false;
			if (_engine->_menuOptions->newGameMenu()) {
				return 1;
			}
			break;
		}
		case kQuitEngine:
			return kQuitEngine;
		default:
			break;
		}
	}

	return 0;
}

static const byte cursorArrow[] = {
	1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
	1, 0, 1, 3, 3, 3, 3, 3, 3, 3, 3,
	1, 0, 0, 1, 3, 3, 3, 3, 3, 3, 3,
	1, 0, 0, 0, 1, 3, 3, 3, 3, 3, 3,
	1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3,
	1, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3,
	1, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3,
	1, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3,
	1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3,
	1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
	1, 0, 0, 1, 0, 0, 1, 3, 3, 3, 3,
	1, 0, 1, 3, 1, 0, 0, 1, 3, 3, 3,
	1, 1, 3, 3, 1, 0, 0, 1, 3, 3, 3,
	1, 3, 3, 3, 3, 1, 0, 0, 1, 3, 3,
	3, 3, 3, 3, 3, 1, 0, 0, 1, 3, 3,
	3, 3, 3, 3, 3, 3, 1, 1, 1, 3, 3};

static const byte cursorPalette[] = {
	0, 0, 0,
	0xff, 0xff, 0xff};

bool Menu::init() {
	_engine->_input->enableKeyMap(uiKeyMapId);
	_engine->_screens->loadMenuImage();
	// load menu effect file only once
	_plasmaEffectPtr = (uint8 *)malloc(kPlasmaEffectFilesize);
	memset(_plasmaEffectPtr, 0, kPlasmaEffectFilesize);

	CursorMan.pushCursor(cursorArrow, 11, 16, 1, 1, 3);
	CursorMan.pushCursorPalette(cursorPalette, 0, 2);
	return HQR::getEntry(_plasmaEffectPtr, Resources::HQR_RESS_FILE, RESSHQR_PLASMAEFFECT) > 0;
}

EngineState Menu::run() {
	FrameMarker frame(_engine);
	_engine->_text->initDial(TextBankId::Options_and_menus);

	_engine->_sound->stopSamples();
	if (_engine->isLBA1()) {
		if (_engine->isCDROM()) {
			_engine->_music->playCdTrack(9); // LBA's Theme
		} else {
			_engine->_music->playMidiFile(9); // LBA's Theme
		}
	} else {
		_engine->_music->playMusic(6); // LBA2's Theme
	}

	ScopedCursor scoped(_engine);
	switch (doGameMenu(&_mainMenuState)) {
	case (int32)TextId::toNewGame:
	case (int32)TextId::kNewGame: {
		if (_engine->isLba1Classic()) {
			if (newGameClassicMenu()) {
				return EngineState::GameLoop;
			}
			break;
		}
		if (_engine->_menuOptions->newGameMenu()) {
			return EngineState::GameLoop;
		}
		break;
	}
	case (int32)TextId::toContinueGame:
	case (int32)TextId::kContinueGame: {
		if (_engine->_menuOptions->continueGameMenu()) {
			return EngineState::LoadedGame;
		}
		break;
	}
	case (int32)TextId::toOptions:
	case (int32)TextId::kOptions: {
		optionsMenu();
		break;
	}
	case kDemoMenu: {
		menuDemo();
		break;
	}
	case (int32)TextId::kQuit:
	case (int32)TextId::toQuit:
	case kQuitEngine:
		debug("quit the game");
		return EngineState::QuitGame;
	}
	return EngineState::Menu;
}

int32 Menu::quitMenu() {
	_engine->saveFrontBuffer();
	_engine->_sound->pauseSamples();

	MenuSettings *localMenu;
	if (_engine->_cfgfile.UseAutoSaving) {
		localMenu = &_giveUpMenuState;
	} else {
		localMenu = &_giveUpMenuWithSaveState;
	}

	ScopedCursor scoped(_engine);

	int32 menuId;
	do {
		FrameMarker frame(_engine);
		_engine->_text->initDial(TextBankId::Options_and_menus);
		menuId = doGameMenu(localMenu);
		switch (menuId) {
		case (int32)TextId::kContinue:
			_engine->_sound->resumeSamples();
			break;
		case (int32)TextId::kGiveUp:
			_engine->_gameState->giveUp();
			return 1;
		case (int32)TextId::kCreateSaveGame:
			_engine->_menuOptions->saveGameMenu();
			break;
		case kQuitEngine:
			return kQuitEngine;
		default:
			warning("Unknown menu button handled: %i", menuId);
		}
		_engine->_text->initSceneTextBank();
	} while (menuId != (int32)TextId::kGiveUp && menuId != (int32)TextId::kContinue && menuId != (int32)TextId::kCreateSaveGame);

	return 0;
}

void Menu::drawHealthBar(int32 left, int32 right, int32 top, int32 barLeftPadding, int32 barHeight) {
	_engine->_grid->drawSprite(left, top + 3, _engine->_resources->_spriteData[SPRITEHQR_LIFEPOINTS]);
	const int32 barLeft = left + barLeftPadding;
	const int32 healthBarRight = ruleThree32(barLeft, right, 50, _engine->_scene->_sceneHero->_lifePoint);
	const int32 barBottom = top + barHeight;
	_engine->_interface->box(Common::Rect(barLeft, top, healthBarRight, barBottom), COLOR_91);
	_engine->_interface->box(Common::Rect(healthBarRight, top, left + 325, barBottom), COLOR_BLACK);
	drawRectBorders(Common::Rect(barLeft, top, right, barBottom));
}

void Menu::drawCloverLeafs(int32 newBoxLeft, int32 boxRight, int32 top) {
	// Clover leaf boxes
	for (int32 i = 0; i < _engine->_gameState->_inventoryNumLeafsBox; i++) {
		const int32 leftSpritePos = ruleThree32(newBoxLeft, boxRight, 10, i);
		_engine->_grid->drawSprite(leftSpritePos, top + 58, _engine->_resources->_spriteData[SPRITEHQR_CLOVERLEAFBOX]);
	}

	// Clover leafs
	for (int32 i = 0; i < _engine->_gameState->_inventoryNumLeafs; i++) {
		const int32 leftSpritePos = ruleThree32(newBoxLeft, boxRight, 10, i);
		_engine->_grid->drawSprite(leftSpritePos + 2, top + 60, _engine->_resources->_spriteData[SPRITEHQR_CLOVERLEAF]);
	}
}

void Menu::drawMagicPointsBar(int32 left, int32 right, int32 top, int32 barLeftPadding, int32 barHeight) {
	if (_engine->_gameState->inventoryDisabled()) {
		return;
	}
	if (!_engine->_gameState->hasItem(InventoryItems::kiTunic)) {
		return;
	}
	_engine->_grid->drawSprite(left, top + 1, _engine->_resources->_spriteData[SPRITEHQR_MAGICPOINTS]);
	if (_engine->_gameState->_magicLevelIdx <= 0) {
		return;
	}
	const int32 barLeft = left + barLeftPadding;
	const int32 barBottom = top + barHeight;
	// max magic level is 4
	const int32 maxMagicPoints = 4 * 20;
	const int32 barRight = ruleThree32(barLeft, right, maxMagicPoints, _engine->_gameState->_magicPoint);
	const Common::Rect pointsRect(barLeft, top, barRight, barBottom);
	_engine->_interface->box(pointsRect, COLOR_75);

	for (int32 l = 0; l < _engine->_gameState->_magicLevelIdx; l++) {
		const int32 x1 = ruleThree32(barLeft, right, 40, _engine->_gameState->_magicLevelIdx * 10);
		_engine->_interface->drawLine(x1, top + barHeight, x1, top + 35 + 15 - 1, 0);
	}

	const int32 rectRight = ruleThree32(barLeft, right, 40, _engine->_gameState->_magicLevelIdx * 10);
	drawRectBorders(barLeft, top, rectRight, barBottom);
}

void Menu::drawSpriteAndString(int32 left, int32 top, const SpriteData &spriteData, const Common::String &str, int32 color) {
	_engine->_grid->drawSprite(left, top + 15, spriteData);
	_engine->_text->setFontColor(color);
	_engine->_text->drawText(left + 30, top + 5, str.c_str());
}

void Menu::drawCoins(int32 left, int32 top) {
	const Common::String &inventoryNumKashes = Common::String::format("%d", _engine->_gameState->_goldPieces);
	drawSpriteAndString(left, top, _engine->_resources->_spriteData[SPRITEHQR_KASHES], inventoryNumKashes);
}

void Menu::drawKeys(int32 left, int32 top) {
	const Common::String &inventoryNumKeys = Common::String::format("%d", _engine->_gameState->_nbLittleKeys);
	drawSpriteAndString(left, top, _engine->_resources->_spriteData[SPRITEHQR_KEY], inventoryNumKeys);
}

void Menu::drawInfoMenu(int16 left, int16 top, int16 width) {
	_engine->_interface->unsetClip();
	const int16 height = 80;
	const Common::Rect rect(left, top, left + width, top + height);
	drawRectBorders(rect);
	Common::Rect filledRect(rect);
	filledRect.grow(-1);
	_engine->_interface->box(filledRect, COLOR_BLACK);

	const int32 boxLeft = left + 9;
	const int32 boxRight = left + 325;
	const int32 barPadding = 25;
	const int32 boxTop = top + 10;
	const int32 barHeight = 14;
	drawHealthBar(boxLeft, boxRight, boxTop, barPadding, barHeight);
	drawMagicPointsBar(boxLeft, boxRight, boxTop + 25, barPadding, barHeight);

	const int32 posLeft = left + 340;
	drawCoins(posLeft, top);
	drawKeys(posLeft, top + 35);
	drawCloverLeafs(left + barPadding, boxRight, top);

	_engine->copyBlockPhys(left, top, left + width, top + 135);
}

Common::Rect Menu::calcBehaviourRect(int32 left, int32 top, HeroBehaviourType behaviour) const {
	const int border = 10;
	const int32 padding = 11;
	const int32 width = 99;
	const int height = 119;

	const int32 boxLeft = (int32)behaviour * (width + padding) + left + border;
	const int32 boxRight = boxLeft + width;
	const int32 boxTop = top + border;
	const int32 boxBottom = boxTop + height;
	return Common::Rect(boxLeft, boxTop, boxRight, boxBottom);
}

bool Menu::isBehaviourHovered(int32 left, int32 top, HeroBehaviourType behaviour) const {
	if (!_engine->_cfgfile.Mouse) {
		return false;
	}
	const Common::Rect &boxRect = calcBehaviourRect(left, top, behaviour);
	return _engine->_input->isMouseHovering(boxRect);
}

void Menu::drawBehaviour(int32 left, int32 top, HeroBehaviourType behaviour, int32 angle, bool cantDrawBox) {
	const Common::Rect &boxRect = calcBehaviourRect(left, top, behaviour);

	const int animIdx = _engine->_actor->_heroAnimIdx[(byte)behaviour];
	const AnimData &currentAnimData = _engine->_resources->_animData[animIdx];

	uint currentAnimState = _behaviourAnimState[(byte)behaviour];

	if (_engine->_animations->setInterAnimObjet(currentAnimState, currentAnimData, *_behaviourEntity, &_behaviourAnimData[(byte)behaviour])) {
		currentAnimState++; // keyframe
		if (currentAnimState >= currentAnimData.getNbFramesAnim()) {
			currentAnimState = currentAnimData.getLoopFrame();
		}
		_behaviourAnimState[(byte)behaviour] = currentAnimState;
	}

	if (!cantDrawBox) {
		Common::Rect boxRectCopy(boxRect);
		boxRectCopy.grow(1);
		drawRectBorders(boxRectCopy);
	}

	_engine->_interface->memoClip();
	_engine->_interface->unsetClip();

	if (behaviour == _engine->_actor->_heroBehaviour) {
		const int titleOffset = 10;
		const int titleHeight = 40;
		const int32 titleBoxLeft = left + 10;
		const int32 titleBoxWidth = 430;
		const int32 titleBoxCenter = titleBoxLeft + titleBoxWidth / 2;
		const int32 titleBoxRight = titleBoxLeft + titleBoxWidth;
		const int32 titleBoxTop = boxRect.bottom + titleOffset;
		const int32 titleBoxBottom = titleBoxTop + titleHeight;

		_engine->_interface->box(boxRect, COLOR_BRIGHT_BLUE2);

		// behaviour menu title
		const Common::Rect titleRect(titleBoxLeft, titleBoxTop, titleBoxRight, titleBoxBottom);
		_engine->_interface->box(titleRect, COLOR_BLACK);
		drawRectBorders(titleRect);

		_engine->_text->setFontColor(COLOR_WHITE);

		char dialText[256];
		_engine->_text->getMenuText(_engine->_actor->getTextIdForBehaviour(), dialText, sizeof(dialText));

		_engine->_text->drawText(titleBoxCenter - _engine->_text->sizeFont(dialText) / 2, titleBoxTop + 1, dialText);
	} else {
		_engine->_interface->box(boxRect, COLOR_BLACK);
	}

	_engine->_renderer->drawObj3D(boxRect, -600, angle, *_behaviourEntity, _moveMenu);

	_engine->_interface->restoreClip();
}

void Menu::prepareAndDrawBehaviour(int32 left, int32 top, int32 angle, HeroBehaviourType behaviour) {
	const int animIdx = _engine->_actor->_heroAnimIdx[(byte)behaviour];
	_engine->_animations->setAnimObjet(_behaviourAnimState[(byte)behaviour], _engine->_resources->_animData[animIdx], *_behaviourEntity, &_behaviourAnimData[(byte)behaviour]);
	drawBehaviour(left, top, behaviour, angle, false);
}

void Menu::drawBehaviourMenu(int32 left, int32 top, int32 angle) {
	const int32 width = 450;
	const int32 height = 190;
	const int32 right = left + width;
	const int32 bottom = top + height;

	const Common::Rect titleRect(left, top, right, bottom);
	drawRectBorders(titleRect);

	Common::Rect boxRect(titleRect);
	boxRect.grow(-1);
	_engine->_interface->shadeBox(boxRect, 2);

	prepareAndDrawBehaviour(left, top, angle, HeroBehaviourType::kNormal);
	prepareAndDrawBehaviour(left, top, angle, HeroBehaviourType::kAthletic);
	prepareAndDrawBehaviour(left, top, angle, HeroBehaviourType::kAggressive);
	prepareAndDrawBehaviour(left, top, angle, HeroBehaviourType::kDiscrete);

	_engine->copyBlockPhys(titleRect);

	drawInfoMenu(titleRect.left, titleRect.bottom + 10, titleRect.width());
}

void Menu::processBehaviourMenu(bool behaviourMenu) {
	if (_engine->_actor->_heroBehaviour == HeroBehaviourType::kProtoPack) {
		_engine->_sound->stopSamples();
		_engine->_actor->setBehaviour(HeroBehaviourType::kNormal);
	}

	_behaviourEntity = &_engine->_scene->_sceneHero->_entityDataPtr->getBody(_engine->_scene->_sceneHero->_body);

	_engine->_actor->_heroAnimIdx[(byte)HeroBehaviourType::kNormal] = _engine->_actor->_heroAnimIdxNORMAL;
	_engine->_actor->_heroAnimIdx[(byte)HeroBehaviourType::kAthletic] = _engine->_actor->_heroAnimIdxATHLETIC;
	_engine->_actor->_heroAnimIdx[(byte)HeroBehaviourType::kAggressive] = _engine->_actor->_heroAnimIdxAGGRESSIVE;
	_engine->_actor->_heroAnimIdx[(byte)HeroBehaviourType::kDiscrete] = _engine->_actor->_heroAnimIdxDISCRETE;

	_engine->_movements->initRealAngle(_engine->_scene->_sceneHero->_beta, _engine->_scene->_sceneHero->_beta - LBAAngles::ANGLE_90, LBAAngles::ANGLE_17, &_moveMenu);

	_engine->saveFrontBuffer();

	TextBankId tmpTextBank = _engine->_scene->_sceneTextBank;
	_engine->_scene->_sceneTextBank = TextBankId::None;

	_engine->_text->initDial(TextBankId::Options_and_menus);

	// quick actions to change behaviour don't show the menu in classic edition
	if (!behaviourMenu && _engine->isLba1Classic()) {
		char text[256];
		_engine->_text->getMenuText(_engine->_actor->getTextIdForBehaviour(), text, sizeof(text));
		_engine->_redraw->setRenderText(text);
	} else {
		const int32 left = _engine->width() / 2 - 220;
		const int32 top = _engine->height() / 2 - 140;
		drawBehaviourMenu(left, top, _engine->_scene->_sceneHero->_beta);

		HeroBehaviourType tmpHeroBehaviour = _engine->_actor->_heroBehaviour;

		const int animIdx = _engine->_actor->_heroAnimIdx[(byte)_engine->_actor->_heroBehaviour];
		_engine->_animations->setAnimObjet(_behaviourAnimState[(byte)_engine->_actor->_heroBehaviour], _engine->_resources->_animData[animIdx], *_behaviourEntity, &_behaviourAnimData[(byte)_engine->_actor->_heroBehaviour]);

		int32 tmpTime = _engine->timerRef;

		ScopedCursor scopedCursor(_engine);
		ScopedKeyMap scopedKeyMap(_engine, uiKeyMapId);
		while (_engine->_input->isActionActive(TwinEActionType::BehaviourMenu) || _engine->_input->isQuickBehaviourActionActive()) {
			FrameMarker frame(_engine, 50);
			_engine->readKeys();
			if (_engine->shouldQuit()) {
				break;
			}

			if (isBehaviourHovered(left, top, HeroBehaviourType::kNormal)) {
				_engine->_actor->_heroBehaviour = HeroBehaviourType::kNormal;
			} else if (isBehaviourHovered(left, top, HeroBehaviourType::kAthletic)) {
				_engine->_actor->_heroBehaviour = HeroBehaviourType::kAthletic;
			} else if (isBehaviourHovered(left, top, HeroBehaviourType::kAggressive)) {
				_engine->_actor->_heroBehaviour = HeroBehaviourType::kAggressive;
			} else if (isBehaviourHovered(left, top, HeroBehaviourType::kDiscrete)) {
				_engine->_actor->_heroBehaviour = HeroBehaviourType::kDiscrete;
			}

			int heroBehaviour = (int)_engine->_actor->_heroBehaviour;
			if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft)) {
				heroBehaviour--;
			} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIRight)) {
				heroBehaviour++;
			}

			if (heroBehaviour < (int)HeroBehaviourType::kNormal) {
				heroBehaviour = (int)HeroBehaviourType::kDiscrete;
			} else if (heroBehaviour >= (int)HeroBehaviourType::kProtoPack) {
				heroBehaviour = (int)HeroBehaviourType::kNormal;
			}

			_engine->_actor->_heroBehaviour = (HeroBehaviourType)heroBehaviour;

			if (tmpHeroBehaviour != _engine->_actor->_heroBehaviour) {
				drawBehaviour(left, top, tmpHeroBehaviour, _engine->_scene->_sceneHero->_beta, true);
				tmpHeroBehaviour = _engine->_actor->_heroBehaviour;
				_engine->_movements->initRealAngle(_engine->_scene->_sceneHero->_beta, _engine->_scene->_sceneHero->_beta - LBAAngles::ANGLE_90, LBAAngles::ANGLE_17, &_moveMenu);
				const int tmpAnimIdx = _engine->_actor->_heroAnimIdx[(byte)_engine->_actor->_heroBehaviour];
				_engine->_animations->setAnimObjet(_behaviourAnimState[(byte)_engine->_actor->_heroBehaviour], _engine->_resources->_animData[tmpAnimIdx], *_behaviourEntity, &_behaviourAnimData[(byte)_engine->_actor->_heroBehaviour]);
			}

			drawBehaviour(left, top, _engine->_actor->_heroBehaviour, -1, true);

			// increase the timer to play the animations
			_engine->timerRef++;
			debugC(3, kDebugLevels::kDebugTimers, "Behaviour time: %i", _engine->timerRef);
		}

		_engine->timerRef = tmpTime;

		_engine->_gameState->init3DGame();
	}
	_engine->_actor->setBehaviour(_engine->_actor->_heroBehaviour);

	_engine->_scene->_sceneTextBank = tmpTextBank;
	_engine->_text->initSceneTextBank();
}

Common::Rect Menu::calcItemRect(int32 left, int32 top, int32 item, int32 *centerX, int32 *centerY) const {
	const int32 itemWidth = SIZE_INV_OBJ_X;
	const int32 itemHeight = SIZE_INV_OBJ_Y;
	const int32 itemPadding = 11;
	const int32 itemWidthHalf = itemWidth / 2;
	const int32 itemHeightHalf = itemHeight / 2;
	const int32 itemX = (item / 4) * (itemWidth + itemPadding) + left + itemWidthHalf + itemPadding - 1;
	const int32 itemY = (item % 4) * (itemHeight + itemPadding) + top + itemHeightHalf + itemPadding - 1;
	if (centerX) {
		*centerX = itemX;
	}
	if (centerY) {
		*centerY = itemY;
	}
	return Common::Rect(itemX - itemWidthHalf, itemY - itemHeightHalf, itemX + itemWidthHalf, itemY + itemHeightHalf);
}

void Menu::drawOneInventory(int32 left, int32 top, int32 item) {
	int32 itemX, itemY;
	const Common::Rect rect = calcItemRect(left, top, item, &itemX, &itemY);
	const int32 color = _inventorySelectedItem == item ? _inventorySelectedColor : COLOR_BLACK;

	_engine->_interface->box(rect, color);

	if (item < NUM_INVENTORY_ITEMS && _engine->_gameState->hasItem((InventoryItems)item) && (!_engine->_gameState->inventoryDisabled() || item == InventoryItems::kiCloverLeaf)) {
		_itemAngle[item] += LBAAngles::ANGLE_2;
		_engine->_interface->setClip(rect);
		_engine->_renderer->draw3dObject(itemX, itemY, _engine->_resources->_inventoryTable[item], _itemAngle[item], 15000);
		_engine->_interface->unsetClip();
		if (item == InventoryItems::kGasItem) {
			_engine->_text->setFontColor(COLOR_WHITE);
			const Common::String &inventoryNumGas = Common::String::format("%d", _engine->_gameState->_inventoryNumGas);
			_engine->_text->drawText(rect.left + 3, rect.bottom - 32, inventoryNumGas.c_str());
		}
	}

	drawRectBorders(rect);
}

void Menu::drawListInventory(int32 left, int32 top) {
	constexpr int w = (SIZE_INV_OBJ_X + 10) * 7 + 10;
	constexpr int h = (SIZE_INV_OBJ_Y + 10) * 4 + 10;
	const Common::Rect rect(left, top, left + w, top + h);
	_engine->_interface->shadeBox(rect, 4);
	drawRectBorders(rect);
	for (int32 item = 0; item < NUM_INVENTORY_ITEMS; item++) {
		drawOneInventory(left, top, item);
	}
	_engine->_interface->unsetClip();
}

void Menu::inventory() {
	int32 tmpAlphaLight = _engine->_scene->_alphaLight;
	int32 tmpBetaLight = _engine->_scene->_betaLight;

	_engine->saveFrontBuffer();

	_engine->_renderer->setLightVector(LBAAngles::ANGLE_315, LBAAngles::ANGLE_334, LBAAngles::ANGLE_0);

	_inventorySelectedColor = COLOR_SELECT_MENU;

	if (_engine->_gameState->_inventoryNumLeafs > 0) {
		_engine->_gameState->giveItem(InventoryItems::kiCloverLeaf);
	} else {
		_engine->_gameState->removeItem(InventoryItems::kiCloverLeaf);
	}

	const int32 left = _engine->width() / 2 - 303;
	const int32 top = _engine->height() / 2 - 230;
	drawListInventory(left, top);

	_engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap);

	_engine->_text->setFontCrossColor(COLOR_BRIGHT_BLUE);
	_engine->_text->initDialWindow();

	ProgressiveTextState dialstate = ProgressiveTextState::ContinueRunning;
	bool updateItemText = true;

	ScopedCursor scopedCursor(_engine);
	ScopedKeyMap scopedKeyMap(_engine, uiKeyMapId);
	for (;;) {
		FrameMarker frame(_engine, 66);
		_engine->readKeys();
		int32 prevSelectedItem = _inventorySelectedItem;

		if (_engine->_input->toggleAbortAction() || _engine->shouldQuit()) {
			break;
		}

		for (int32 item = 0; item < NUM_INVENTORY_ITEMS; item++) {
			const Common::Rect &rect = calcItemRect(left, top, item);
			if (_engine->_input->isMouseHovering(rect)) {
				_inventorySelectedItem = item;
				drawOneInventory(left, top, prevSelectedItem);
				updateItemText = true;
				break;
			}
		}

		const bool cursorDown = _engine->_input->toggleActionIfActive(TwinEActionType::UIDown);
		const bool cursorUp = _engine->_input->toggleActionIfActive(TwinEActionType::UIUp);
		const bool cursorLeft = _engine->_input->toggleActionIfActive(TwinEActionType::UILeft);
		const bool cursorRight = _engine->_input->toggleActionIfActive(TwinEActionType::UIRight);

		if (cursorDown) {
			_inventorySelectedItem++;
			if (_inventorySelectedItem >= NUM_INVENTORY_ITEMS) {
				_inventorySelectedItem = 0;
			}
			drawOneInventory(left, top, prevSelectedItem);
			updateItemText = true;
		} else if (cursorUp) {
			_inventorySelectedItem--;
			if (_inventorySelectedItem < 0) {
				_inventorySelectedItem = NUM_INVENTORY_ITEMS - 1;
			}
			drawOneInventory(left, top, prevSelectedItem);
			updateItemText = true;
		} else if (cursorLeft) {
			_inventorySelectedItem -= 4;
			if (_inventorySelectedItem < 0) {
				_inventorySelectedItem += NUM_INVENTORY_ITEMS;
			}
			drawOneInventory(left, top, prevSelectedItem);
			updateItemText = true;
		} else if (cursorRight) {
			_inventorySelectedItem += 4;
			if (_inventorySelectedItem >= NUM_INVENTORY_ITEMS) {
				_inventorySelectedItem -= NUM_INVENTORY_ITEMS;
			}
			drawOneInventory(left, top, prevSelectedItem);
			updateItemText = true;
		}

		if (updateItemText) {
			_engine->_text->secondInitDialWindow();
			if (_inventorySelectedItem < NUM_INVENTORY_ITEMS && _engine->_gameState->hasItem((InventoryItems)_inventorySelectedItem) && !_engine->_gameState->inventoryDisabled()) {
				_engine->_text->initInventoryText((InventoryItems)_inventorySelectedItem);
			} else {
				_engine->_text->initInventoryText(InventoryItems::MaxInventoryItems);
			}
			dialstate = ProgressiveTextState::End;
			updateItemText = false;
		}

		if (dialstate != ProgressiveTextState::NextPage) {
			dialstate = _engine->_text->nextDialChar();
		}

		if (_engine->_input->toggleActionIfActive(TwinEActionType::UINextPage)) {
			// restart the item description to appear from the beginning
			if (dialstate == ProgressiveTextState::End) {
				updateItemText = true;
			}
			if (dialstate == ProgressiveTextState::NextPage) {
				_engine->_text->secondInitDialWindow();
				dialstate = ProgressiveTextState::ContinueRunning;
			}
		}

		drawOneInventory(left, top, _inventorySelectedItem);

		if (_inventorySelectedItem < NUM_INVENTORY_ITEMS && _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter) && _engine->_gameState->hasItem((InventoryItems)_inventorySelectedItem) && !_engine->_gameState->inventoryDisabled()) {
			_engine->_loopInventoryItem = _inventorySelectedItem;
			_inventorySelectedColor = COLOR_91;
			drawOneInventory(left, top, _inventorySelectedItem);
			break;
		}
	}

	_engine->_text->closeDial();

	_engine->_scene->_alphaLight = tmpAlphaLight;
	_engine->_scene->_betaLight = tmpBetaLight;

	_engine->_gameState->init3DGame();

	_engine->_text->initSceneTextBank();

	// this is a hack to 'fix' https://bugs.scummvm.org/ticket/13677
	_engine->_input->toggleActionIfActive(TwinEActionType::RecenterScreenOnTwinsen);
}

} // namespace TwinE
