/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */
#include "audio/audiostream.h"
#include "audio/decoders/wave.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/system.h"
#include "graphics/managed_surface.h"
#include "graphics/palette.h"
#include "image/bmp.h"
#include "image/image_decoder.h"

#include "cryomni3d/mouse_boxes.h"
#include "cryomni3d/font_manager.h"

#include "cryomni3d/versailles/engine.h"

namespace CryOmni3D {
namespace Versailles {

bool CryOmni3DEngine_Versailles::showSubtitles() const {
	return ConfMan.getBool("subtitles");
}

void CryOmni3DEngine_Versailles::drawMenuTitle(Graphics::ManagedSurface *surface, byte color) {
	int offY;

	int oldFont = _fontManager.getCurrentFont();

	int titleX, titleY, subtitleX, subtitleY;
	if (getLanguage() == Common::FR_FRA ||
	        getLanguage() == Common::ES_ESP) {
		titleX = 144;
		titleY = 160;
		subtitleX = 305;
		subtitleY = 160;
	} else if (getLanguage() == Common::DE_DEU) {
		titleX = 122;
		titleY = 80;
		subtitleX = 283;
		subtitleY = 80;
	} else {
		titleX = 100;
		titleY = 80;
		subtitleX = 261;
		subtitleY = 80;
	}

	_fontManager.setSurface(surface);
	_fontManager.setForeColor(color);
	_fontManager.setCurrentFont(1);
	offY = _fontManager.getFontMaxHeight();
	_fontManager.displayStr(titleX, titleY - offY, _messages[23]);
	_fontManager.setCurrentFont(3);
	offY = _fontManager.getFontMaxHeight();
	_fontManager.displayStr(subtitleX, subtitleY - offY, _messages[24]);

	if (getLanguage() == Common::FR_FRA ||
	        getLanguage() == Common::ES_ESP) {
		surface->vLine(100, 146, 172, color);
		surface->hLine(100, 172, 168, color); // minus 1 because hLine draws inclusive
	}

	_fontManager.setCurrentFont(oldFont);
}

uint CryOmni3DEngine_Versailles::displayOptions() {
	Common::Array<int> menuEntries;
	menuEntries.push_back(26);
	menuEntries.push_back(27);
	menuEntries.push_back(28);
	menuEntries.push_back(29);
	menuEntries.push_back(48);
	menuEntries.push_back(30);
	menuEntries.push_back(32);
#if 0
	// Music on HDD setting
	menuEntries.push_back(34);
#endif
	menuEntries.push_back(25);
	menuEntries.push_back(-42);
	menuEntries.push_back(43);
	menuEntries.push_back(40);
	// 1 is for volume box
	MouseBoxes boxes(menuEntries.size() + 1);

	bool end = false;

	int drawState = 1;

	uint volumeCursorMiddleY = _sprites.getCursor(102).getHeight() / 2;
	uint volume = CLIP(ConfMan.getInt("music_volume"), 0, 256);
	uint soundVolumeY = ((283 * (256 - volume)) >> 8) + 101;
	byte volumeForeColor = 243;

	Graphics::ManagedSurface optionsSurface;
	Image::ImageDecoder *imageDecoder = loadHLZ("option.hlz");
	const Graphics::Surface *bgFrame = imageDecoder->getSurface();

	optionsSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);

	setCursor(181);
	showMouse(true);

	uint hoveredBox = uint(-1);
	uint selectedBox = uint(-1);
	int selectedMsg = 0;
	uint volumeBox = uint(-1);
	bool resetScreen = true;
	bool forceEvents = true;

	while (!shouldAbort() && !end) {
		if (resetScreen) {
			setPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
			           imageDecoder->getPaletteColorCount());
			// _cursorPalette has only 248 colors as 8 last colors are for translucency
			setPalette(_cursorPalette + 240 * 3, 240, 8);

			_fontManager.setCurrentFont(3);
			_fontManager.setTransparentBackground(true);
			_fontManager.setForeColor(243);
			_fontManager.setLineHeight(14);
			_fontManager.setSpaceWidth(0);
			_fontManager.setCharSpacing(1);
			_fontManager.setSurface(&optionsSurface);
			resetScreen = false;
		}
		if (drawState > 0) {
			if (drawState == 1) {
				optionsSurface.blitFrom(*bgFrame);
			}
			drawMenuTitle(&optionsSurface, 243);
			_fontManager.setForeColor(volumeForeColor);
			_fontManager.displayStr(550, 407, _messages[39]);
			optionsSurface.vLine(544, 402, 429, volumeForeColor);
			optionsSurface.hLine(544, 429, 613, volumeForeColor); // minus 1 because hLine draws inclusive

			boxes.reset();
			uint boxId = 0;
			uint top = 195;
			uint bottom;
			uint width;

			for (Common::Array<int>::iterator it = menuEntries.begin(); it != menuEntries.end(); it++) {
				if (*it == 30 && !ConfMan.getBool("subtitles")) {
					*it = 31;
				} else if (*it == 32 && (ConfMan.getBool("mute") ||
				                         ConfMan.getBool("music_mute"))) {
					*it = 33;
				}
#if 0
				else if (*it == 34) {
					// What to do with music on HDD setting?
				}
#endif
				else if (*it == 26 && !_isPlaying) {
					*it = -26;
				} else if (*it == 29 && !_isPlaying) {
					*it = -29;
				} else if (*it == -42 && canVisit()) {
					*it = 42;
				} else if (*it == 48) {
					uint omni3D_speed = ConfMan.getInt("omni3d_speed");
					switch (omni3D_speed) {
					case 1:
						*it = 51;
						break;
					case 2:
						*it = 52;
						break;
					case 3:
						*it = 49;
						break;
					case 4:
						*it = 50;
						break;
					default:
						// If it is another value, menu text is 48 (normal speed) and there is nothing to do
						break;
					}
				}

				if (*it > 0) {
					int msgId = *it;
					bottom = top;
					top += 24;

					// Patch on the fly the text displayed
					if (_isVisiting) {
						if (msgId == 26) {
							msgId = 44;
						} else if (msgId == 29) {
							msgId = 45;
						}
					}

					width = _fontManager.getStrWidth(_messages[msgId]);
					//Common::Rect rct(144, top - 39, width + 144, bottom);
					//optionsSurface.frameRect(rct, 0);
					boxes.setupBox(boxId, 144, top - 39, width + 144, bottom);
					if (boxId == hoveredBox) {
						_fontManager.setForeColor(240);
					} else {
						_fontManager.setForeColor(243);
					}
					_fontManager.displayStr(144, top - 39, _messages[msgId]);
				}
				boxId++;
			}

			volumeBox = boxId;
			boxes.setupBox(boxId, 525, 101, 570, 401);
			optionsSurface.transBlitFrom(_sprites.getSurface(102), Common::Point(553, soundVolumeY),
			                             _sprites.getKeyColor(102));

			g_system->copyRectToScreen(optionsSurface.getPixels(), optionsSurface.pitch, 0, 0, optionsSurface.w,
			                           optionsSurface.h);
			drawState = 0;
		}
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (pollEvents() || forceEvents) { // always call pollEvents
			forceEvents = false;
			Common::Point mouse = getMousePos();
			uint boxId = 0;
			Common::Array<int>::iterator it;
			for (it = menuEntries.begin(); it != menuEntries.end(); it++) {
				if (boxes.hitTest(boxId, mouse)) {
					if (hoveredBox != boxId) {
						hoveredBox = boxId;
						drawState = 2;
					}
					// We met a hit, no need to look further
					break;
				}
				boxId++;
			}
			if (it != menuEntries.end()) {
				if (getDragStatus() == 2) {
					selectedMsg = *it;
					selectedBox = hoveredBox;
				}
			} else {
				// no menu selected, check volume
				if (boxes.hitTest(volumeBox, mouse)) {
					if (volumeForeColor != 240) {
						volumeForeColor = 240;
						drawState = 1;
					}
					if (getCurrentMouseButton() == 1) {
						if (soundVolumeY != getMousePos().y - volumeCursorMiddleY) {
							soundVolumeY = CLIP(getMousePos().y - volumeCursorMiddleY, 101u, 384u);
							drawState = 1;
							volume = CLIP(((384 - soundVolumeY) << 8) / 283, 0u, 256u);
							// Global setting
							ConfMan.setInt("music_volume", volume);
							// As we modify speech volume, let's unmute it
							// Using in-game settings resets all scummvm specific settings
							ConfMan.setBool("speech_mute", false);
							ConfMan.setInt("speech_volume", volume);
							syncSoundSettings();
						}
					} else if (getDragStatus() == 2 &&
					           !_mixer->hasActiveChannelOfType(Audio::Mixer::kMusicSoundType) &&
					           _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) > 0) {
						// Finished dragging
						_mixer->stopID(SoundIds::kOrgue);
						do {
							Common::File *audioFile = new Common::File();
							if (!audioFile->open("ORGUE.WAV")) {
								warning("Failed to open sound file %s", "ORGUE.WAV");
								delete audioFile;
								break;
							}

							Audio::SeekableAudioStream *audioDecoder = Audio::makeWAVStream(audioFile, DisposeAfterUse::YES);
							// We lost ownership of the audioFile just set it to nullptr and don't use it
							audioFile = nullptr;
							if (!audioDecoder) {
								break;
							}

							_mixer->playStream(Audio::Mixer::kMusicSoundType, nullptr, audioDecoder, SoundIds::kOrgue);
							// We lost ownership of the audioDecoder just set it to nullptr and don't use it
							audioDecoder = nullptr;
						} while (false);
					}
				} else {
					if (hoveredBox != uint(-1)) {
						hoveredBox = uint(-1);
						drawState = 2;
					}
					if (volumeForeColor != 243) {
						volumeForeColor = 243;
						drawState = 1;
					}
				}
			}
			if (getNextKey().keycode == Common::KEYCODE_ESCAPE && _isPlaying) {
				selectedMsg = 26;
			}
			if (selectedMsg == 27 || selectedMsg == 28 || selectedMsg == 40 || selectedMsg == 42) {
				// New game, Load game, Quit, Visit
				if (!_isPlaying || _isVisiting) {
					end = true;
				} else {
					Common::Rect rct;
					if (getLanguage() == Common::DE_DEU) {
						rct = Common::Rect(286, 433, 555, 475);
					} else if (getLanguage() == Common::ES_ESP ||
					           getLanguage() == Common::IT_ITA) {
						rct = Common::Rect(250, 420, 530, 465);
					} else {
						rct = Common::Rect(235, 420, 505, 465);
					}
					end = displayYesNoBox(optionsSurface, rct, 57);
				}
				drawState = 1;
				if (!end) {
					selectedMsg = 0;
				}
			}
			if (selectedMsg == 25) {
				// Documentation area
				_docManager.handleDocArea();
				drawState = 1;
				resetScreen = true;
				forceEvents = true;
				waitMouseRelease();
				selectedMsg = 0;
			} else if (selectedMsg == 26) {
				// Continue game
				end = true;
			} else if (selectedMsg == 28) {
				Common::String saveName;
				bool wasVisiting = _isVisiting;
				_isVisiting = false;
				uint saveNumber = displayFilePicker(bgFrame, false, saveName);
				if (saveNumber == uint(-1)) {
					_isVisiting = wasVisiting;
					drawState = 1;
					selectedMsg = 0;
				} else {
					_loadedSave = saveNumber;
					end = true;
				}
				waitMouseRelease();
			} else if (selectedMsg == 42) {
				Common::String saveName;
				bool wasVisiting = _isVisiting;
				_isVisiting = true;
				uint saveNumber = displayFilePicker(bgFrame, false, saveName);
				if (saveNumber == uint(-1)) {
					_isVisiting = wasVisiting;
					drawState = 1;
					selectedMsg = 0;
				} else {
					_loadedSave = saveNumber;
					end = true;
				}
				waitMouseRelease();
			} else if (selectedMsg == 29) {
				Common::String saveName;
				uint saveNumber = displayFilePicker(bgFrame, true, saveName);
				if (saveNumber != uint(-1)) {
					saveGame(_isVisiting, saveNumber, saveName);
				}
				drawState = 1;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 30) {
				ConfMan.setBool("subtitles", false);
				drawState = 1;
				menuEntries[selectedBox] = 31;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 31) {
				ConfMan.setBool("subtitles", true);
				drawState = 1;
				menuEntries[selectedBox] = 30;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 32) {
				ConfMan.setBool("music_mute", true);
				syncSoundSettings();
				drawState = 1;
				menuEntries[selectedBox] = 33;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 33) {
				ConfMan.setBool("mute", false);
				ConfMan.setBool("music_mute", false);
				syncSoundSettings();
				drawState = 1;
				menuEntries[selectedBox] = 32;
				selectedMsg = 0;
				waitMouseRelease();
			}
#if 0
			// Music on disk settings
			else if (selectedMsg == 35) {
				drawState = 1;
				menuEntries[selectedBox] = 34;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 34) {
				drawState = 1;
				menuEntries[selectedBox] = 36;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 36) {
				drawState = 1;
				menuEntries[selectedBox] = 35;
				selectedMsg = 0;
				waitMouseRelease();
			}
#endif
			else if (selectedMsg == 39) {
				// Volume
				selectedMsg = 0;
			} else if (selectedMsg == 47) {
				// Unknown
				selectedMsg = 0;
			} else if (selectedMsg == 48) {
				ConfMan.setInt("omni3d_speed", 1);
				drawState = 1;
				menuEntries[selectedBox] = 51;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 51) {
				ConfMan.setInt("omni3d_speed", 2);
				drawState = 1;
				menuEntries[selectedBox] = 52;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 52) {
				ConfMan.setInt("omni3d_speed", 3);
				drawState = 1;
				menuEntries[selectedBox] = 49;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 49) {
				ConfMan.setInt("omni3d_speed", 4);
				drawState = 1;
				menuEntries[selectedBox] = 50;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 50) {
				ConfMan.setInt("omni3d_speed", 0);
				drawState = 1;
				menuEntries[selectedBox] = 48;
				selectedMsg = 0;
				waitMouseRelease();
			} else if (selectedMsg == 43) {
				displayCredits();
				drawState = 1;
				resetScreen = true;
				forceEvents = true;
				selectedMsg = 0;
				waitMouseRelease();
			}
		}
	}

	showMouse(false);

	if (selectedMsg == 42) {
		_abortCommand = kAbortLoadGame;
		// For return value
		selectedMsg = 28;
	} else if (selectedMsg == 28) {
		_abortCommand = kAbortLoadGame;
	} else if (selectedMsg == 40) {
		_abortCommand = kAbortQuit;
	} else if (selectedMsg == 27) {
		_abortCommand = kAbortNewGame;
		_isVisiting = false;
	} else if (shouldAbort()) {
		// Fake a quit
		selectedMsg = 40;
		// shouldAbort called earlier has already set _abortCommand
		// If GMM is called on main menu in game, return value is ignored so quit isn't important
		// If GMM is called on main menu out of game, GMM can only quit game and don't load
	}

	ConfMan.flushToDisk();
	syncOmni3DSettings();
	musicUpdate();

	delete imageDecoder;
	return selectedMsg;
}

uint CryOmni3DEngine_Versailles::displayYesNoBox(Graphics::ManagedSurface &surface,
        const Common::Rect &position, uint msg_id) {
	uint confirmWidth = _fontManager.getStrWidth(_messages[53]);
	uint cancelWidth = _fontManager.getStrWidth(_messages[54]);
	uint oldFont = _fontManager.getCurrentFont();

	_fontManager.setSurface(&surface);
	_fontManager.setForeColor(240);
	_fontManager.setLineHeight(20);
	surface.frameRect(position, 243);

	_fontManager.setupBlock(Common::Rect(position.left + 5, position.top + 5, position.right - 5,
	                                     position.bottom - 5));
	_fontManager.setCurrentFont(5);
	_fontManager.displayBlockText(_messages[msg_id]);
	_fontManager.setCurrentFont(3);

	MouseBoxes boxes(2);
	boxes.setupBox(1, position.left + 5, position.bottom - 15, position.left + confirmWidth,
	               position.bottom, &_messages[53]);
	boxes.setupBox(0, position.right - cancelWidth - 5, position.bottom - 15, position.right,
	               position.bottom, &_messages[54]);

	bool end = false;
	bool redraw = true;
	uint result = uint(-1);

	while (!end || redraw) {
		if (redraw) {
			for (uint boxId = 0; boxId < 2; boxId++) {
				if (boxId == result) {
					_fontManager.setForeColor(240);
				} else {
					_fontManager.setForeColor(243);
				}
				boxes.display(boxId, _fontManager);
			}
			redraw = false;

			g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
		}
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (pollEvents()) {
			Common::Point mouse = getMousePos();
			uint hit_result = uint(-1);
			if (boxes.hitTest(1, mouse)) {
				hit_result = 1;
			} else if (boxes.hitTest(0, mouse)) {
				hit_result = 0;
			}
			if (!end && hit_result != result) {
				result = hit_result;
				redraw = true;
			}
			if ((getCurrentMouseButton() == 1) && (result != uint(-1))) {
				end = true;
			}
			Common::KeyCode keyPressed = getNextKey().keycode;
			if (keyPressed == Common::KEYCODE_ESCAPE) {
				result = 0;
				redraw = true;
				end = true;
			} else if (keyPressed == Common::KEYCODE_RETURN) {
				result = 1;
				redraw = true;
				end = true;
			}
		}
	}
	_fontManager.setCurrentFont(oldFont);
	return result;
}

uint CryOmni3DEngine_Versailles::displayFilePicker(const Graphics::Surface *bgFrame,
        bool saveMode, Common::String &saveName) {
	Graphics::ManagedSurface surface(bgFrame->w, bgFrame->h, bgFrame->format);
	surface.blitFrom(*bgFrame);

	drawMenuTitle(&surface, 243);

	int subtitleId;
	if (_isVisiting) {
		subtitleId = saveMode ? 45 : 46;
	} else {
		subtitleId = saveMode ? 29 : 28;
	}
	_fontManager.displayStr(164, 214, _messages[subtitleId]);

	// Draw an empty screen before we list saves
	showMouse(false);
	g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
	g_system->updateScreen();

	Common::Array<Common::String> savesList;
	getSavesList(_isVisiting, savesList);
	Common::String saveNameBackup;

	showMouse(true);

	MouseBoxes boxes(10); // 6 files + Yes/No/Up/Down buttons

	// Yes/No buttons
	const Common::String &okMsg = _messages[53];
	uint okWidth = _fontManager.getStrWidth(okMsg);
	boxes.setupBox(6, 246, 430, 246 + okWidth, 450, &okMsg);
	const Common::String &cancelMsg = _messages[54];
	uint cancelWidth = _fontManager.getStrWidth(cancelMsg);
	boxes.setupBox(7, 146, 430, 146 + cancelWidth, 450, &cancelMsg);

	// Up/Down buttons
	boxes.setupBox(8, 428, 320, 448, 340);
	boxes.setupBox(9, 428, 360, 448, 380);
	surface.transBlitFrom(_sprites.getSurface(162), Common::Point(428, 320), _sprites.getKeyColor(162));
	surface.transBlitFrom(_sprites.getSurface(185), Common::Point(428, 360), _sprites.getKeyColor(185));

	setCursor(181);

	uint fileListOffset = CLIP(ConfMan.getInt(_isVisiting ? "visits_list_off" :
	                           "saves_list_off"), 0, 100 - 6);

	uint boxHovered = uint(-1);
	uint boxSelected = uint(-1);

	bool textCursorState = false;
	uint textCursorNextState = 0;
	uint textCursorPos = uint(-1);

	bool autoRepeatInhibit = false;
	uint autoRepeatDelay = 250;
	uint autoRepeatEndInhibit = 0;

	bool finished = false;
	bool filesListChanged = true;
	bool redraw = false;
	while (!finished) {
		if (filesListChanged || redraw) {
			if (filesListChanged) {
				for (uint file = 0, fileY = 280; file < 6; file++, fileY += 20) {
					boxes.setupBox(file, 146, fileY, 408, fileY + 14, &savesList[file + fileListOffset]);
				}
				// Redraw background as file list changed
				surface.blitFrom(*bgFrame, Common::Rect(116, 280, 408, 400), Common::Point(116, 280));
				filesListChanged = false;
			}
			// Don't redraw the scroll buttons
			for (uint box = 0; box < 8; box++) {
				if (box == boxSelected) {
					// Selected
					_fontManager.setForeColor(240);
				} else if (box == 6 && boxSelected == uint(-1)) {
					// Ok and no file selected
					_fontManager.setForeColor(245);
				} else if (box == boxHovered) {
					// Hovered
					_fontManager.setForeColor(241);
				} else {
					// Other cases
					_fontManager.setForeColor(243);
				}

				if (box == boxSelected && saveMode) {
					Common::Rect boxRct = boxes.getBoxRect(box);
					boxRct.top -= 2;
					surface.blitFrom(*bgFrame, boxRct, Common::Point(boxRct.left, boxRct.top));
					boxRct.top += 2;
					if (textCursorState) {
						surface.vLine(textCursorPos, boxRct.top, boxRct.top + 11, 240);
					}
				}
				boxes.display(box, _fontManager);
				if (box < 6) {
					// Draw line below
					surface.hLine(116, 280 + box * 20 + 15, 407, 243); // minus 1 because hLine draws inclusive

					// Display file number
					_fontManager.displayInt(126, 280 + box * 20, fileListOffset + box + 1);
				}
			}
			redraw = false;
			g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
		}

		g_system->updateScreen();
		g_system->delayMillis(10);

		pollEvents();
		Common::KeyState key = getNextKey();
		uint mousePressed = getCurrentMouseButton();

		if (!mousePressed) {
			bool boxFound = false;
			// Don't handle scroll arrows hovering
			for (uint box = 0; box < 8; box++) {
				if (boxes.hitTest(box, getMousePos())) {
					boxFound = true;
					if (boxHovered != box) {
						boxHovered = box;
						redraw = true;
					}
				}
			}
			if (!boxFound && boxHovered != uint(-1)) {
				boxHovered = uint(-1);
				redraw = true;
			}
		}
		if (key == Common::KEYCODE_RETURN || (mousePressed == 1 && boxHovered == 6)) {
			// OK
			if (boxSelected != uint(-1)) {
				Common::String &selectedSaveName = savesList[boxSelected + fileListOffset];
				if (!selectedSaveName.size()) {
					selectedSaveName = _messages[56]; // No name
				}
				redraw = true;
				finished = true;
			}
		} else if (mousePressed == 1) {
			if (boxHovered == 7) {
				// Cancel
				boxSelected = uint(-1);
				finished = true;
			} else if (boxHovered != uint(-1) && boxHovered != boxSelected) {
				// This can only be a file
				bool existingSave = (savesList[boxHovered + fileListOffset] != _messages[55]);
				// Don't allow to save on slot 0 when visiting to avoid problems with original visit save
				bool validSave = !(_isVisiting && saveMode && boxSelected == 0);
				if ((saveMode || existingSave) && validSave) {
					// Restore old name
					if (saveMode && boxSelected != uint(-1)) {
						savesList[boxSelected + fileListOffset] = saveNameBackup;
						filesListChanged = true;
					}
					boxSelected = boxHovered;
					// Backup new one
					saveNameBackup = savesList[boxSelected + fileListOffset];
					// Not an existing save clear free name
					if (!existingSave) {
						savesList[boxSelected + fileListOffset] = "";
					}
					redraw = true;
				}
			}
		}
		if (boxSelected != uint(-1) && saveMode) {
			if (key.keycode != Common::KEYCODE_INVALID) {
				// Reference means we edit in place
				Common::String &selectedSaveName = savesList[boxSelected + fileListOffset];
				if (key == Common::KEYCODE_BACKSPACE && selectedSaveName.size() > 0) {
					selectedSaveName.deleteLastChar();
					textCursorNextState = 0;
					redraw = true;
				} else if (key.ascii >= 32 && key.ascii < 256 && selectedSaveName.size() < 20) {
					selectedSaveName += key.ascii;
					textCursorNextState = 0;
					redraw = true;
				}
			}
			if (g_system->getMillis() > textCursorNextState) {
				textCursorNextState = g_system->getMillis() + 200; // Blink at 200ms period
				uint width = _fontManager.getStrWidth(savesList[boxSelected + fileListOffset]);
				Common::Rect boxRct = boxes.getBoxRect(boxSelected);
				textCursorPos = boxRct.left + width;
				textCursorState = !textCursorState;
				redraw = true;
			}
		}
		if (!autoRepeatInhibit) {
			bool autoRepeatTrigger = false;
			uint oldFileListOffset = fileListOffset;
			if (mousePressed) {
				if (boxes.hitTest(8, getMousePos()) && fileListOffset > 0) {
					fileListOffset--;
					autoRepeatTrigger = true;
				} else if (boxes.hitTest(9, getMousePos()) && fileListOffset < 99 - 6) {
					fileListOffset++;
					autoRepeatTrigger = true;
				}
			} else if (key == Common::KEYCODE_UP) {
				if (fileListOffset > 0) {
					fileListOffset--;
					autoRepeatTrigger = true;
				}
			} else if (key == Common::KEYCODE_DOWN) {
				if (fileListOffset < 99 - 6) {
					fileListOffset++;
					autoRepeatTrigger = true;
				}
			} else if (key == Common::KEYCODE_PAGEUP) {
				if (fileListOffset > 6) {
					fileListOffset -= 6;
				} else {
					fileListOffset = 0;
				}
			} else if (key == Common::KEYCODE_PAGEDOWN) {
				if (fileListOffset < 99 - 6 - 6) {
					fileListOffset += 6;
				} else {
					fileListOffset = 99 - 6;
				}
			}
			if (autoRepeatTrigger) {
				// Restore old name
				if (saveMode && boxSelected != uint(-1)) {
					savesList[boxSelected + oldFileListOffset] = saveNameBackup;
				}
				boxHovered = uint(-1);
				boxSelected = uint(-1);
				autoRepeatInhibit = true;
				autoRepeatEndInhibit = g_system->getMillis() + autoRepeatDelay;
				filesListChanged = true;
			}
		}
		if (autoRepeatInhibit && g_system->getMillis() > autoRepeatEndInhibit) {
			autoRepeatInhibit = false;
			autoRepeatDelay = 60; // Next rounds will wait 60ms after first one
		}
		if (!mousePressed && key == Common::KEYCODE_INVALID) {
			// Nothing was clicked or pressed: set back autoRepeatDelay to 250ms
			autoRepeatDelay = 250;
		}
	}
	if (boxSelected != uint(-1)) {
		saveName = savesList[boxSelected + fileListOffset];
		ConfMan.setInt(_isVisiting ? "visits_list_off" : "saves_list_off", fileListOffset);
		return boxSelected + fileListOffset + 1;
	} else {
		return uint(-1);
	}
}

const MsgBoxParameters CryOmni3DEngine_Versailles::kWarpMsgBoxParameters = {
	9, 241, 22, 2, 1, 36, 18, 20, 10, 5
};

const MsgBoxParameters CryOmni3DEngine_Versailles::kFixedimageMsgBoxParameters = {
	3, 241, 22, 2, 1, 40, 20, 20, 10, 3
};

void CryOmni3DEngine_Versailles::displayMessageBox(const MsgBoxParameters &params,
        const Graphics::Surface *surface, const Common::String &msg, const Common::Point &position,
        const Common::Functor0<void> &callback) {
	Graphics::ManagedSurface dstSurface;
	dstSurface.create(surface->w, surface->h, surface->format);
	dstSurface.blitFrom(*surface);

	_fontManager.setSurface(&dstSurface);
	_fontManager.setCurrentFont(params.font);
	_fontManager.setTransparentBackground(true);
	_fontManager.setForeColor(params.foreColor);
	_fontManager.setLineHeight(params.lineHeight);
	_fontManager.setSpaceWidth(params.spaceWidth);
	_fontManager.setCharSpacing(params.charSpacing);

	uint width = params.initialWidth;
	uint height = params.initialHeight;
	uint lineCount = 0;
	Common::Point pt = position;
	Common::Rect rct;

	bool notEnough = true;
	bool tooLarge = false;

	while (notEnough && !tooLarge) {
		width += params.incrementWidth;
		height += params.incrementHeight;
		rct = Common::Rect::center(pt.x, pt.y, width, height);
		if (rct.left < 10) {
			rct.left = 10;
			if (pt.x < 320) {
				pt.x += 10;
			}
		}
		if (rct.right >= 630) {
			rct.right = 630;
			if (pt.x > 320) {
				pt.x -= 10;
			}
		}
		if (rct.top <= 10) {
			rct.top = 10;
			if (pt.y < 240) {
				pt.y += 10;
			}
		}
		if (rct.bottom >= 470) {
			rct.bottom = 470;
			if (pt.y > 235) { // sic.
				pt.y -= 10;
			}
		}
		if (rct.left == 10 && rct.top == 10 && rct.right == 630 && rct.bottom == 470) {
			tooLarge = true;
		}
		lineCount = _fontManager.getLinesCount(msg, rct.width() - 12);
		if (lineCount && lineCount * _fontManager.lineHeight() + 18 < (uint)rct.height()) {
			notEnough = false;
		}
	}
	rct.setHeight(lineCount * _fontManager.lineHeight() + 12);
	if (rct.bottom > 479) {
		rct.bottom = 479;
	}

	Graphics::Surface subSurface = dstSurface.getSubArea(rct);
	makeTranslucent(subSurface, surface->getSubArea(rct));
	rct.grow(-6);
	_fontManager.setupBlock(rct);
	_fontManager.displayBlockText(msg);

	drawCountdown(&dstSurface);

	g_system->copyRectToScreen(dstSurface.getPixels(), dstSurface.pitch, 0, 0,
	                           dstSurface.w, dstSurface.h);

	waitMouseRelease();
	uint disappearTime = g_system->getMillis() + msg.size() * params.timeoutChar * 10;
	bool finished = false;
	while (!finished) {
		callback();

		if (g_system->getMillis() > disappearTime) {
			finished = true;
		}
		if (getCurrentMouseButton() == 1) {
			finished = true;
		}
	}

	// Restore image
	g_system->copyRectToScreen(surface->getPixels(), surface->pitch, 0, 0, surface->w, surface->h);
}

void CryOmni3DEngine_Versailles::displayMessageBoxWarp(const Common::String &message) {
	Common::Point mousePos = getMousePos();
	mousePos += Common::Point(0, 32);
	if (mousePos.x > 639) {
		mousePos.x = 639;
	}
	if (mousePos.y > 479) {
		mousePos.y = 479;
	}
	displayMessageBox(kWarpMsgBoxParameters, _omni3dMan.getSurface(), message, mousePos,
	                  Common::Functor0Mem<void, CryOmni3DEngine_Versailles>(this,
	                          &CryOmni3DEngine_Versailles::warpMsgBoxCB));
}

void CryOmni3DEngine_Versailles::displayCredits() {
	waitMouseRelease();

	Graphics::ManagedSurface creditsSurface;
	Image::ImageDecoder *imageDecoder = loadHLZ("credits.hlz");
	if (!imageDecoder) {
		return;
	}

	const Graphics::Surface *bgFrame = imageDecoder->getSurface();

	byte palette[256 * 3];
	memset(palette, 0, 256 * 3);
	// getPalette returns the first color not index 0
	memcpy(palette + 3 * imageDecoder->getPaletteStartIndex(), imageDecoder->getPalette(),
	       3 * imageDecoder->getPaletteColorCount());
	copySubPalette(palette, _cursorPalette, 240, 8);

	creditsSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);

	_fontManager.setCurrentFont(3);
	_fontManager.setTransparentBackground(true);
	_fontManager.setForeColor(243);
	_fontManager.setLineHeight(14);
	_fontManager.setSpaceWidth(0);
	_fontManager.setCharSpacing(1);
	_fontManager.setSurface(&creditsSurface);

	Common::File creditsFile;
	if (!creditsFile.open(_localizedFilenames[LocalizedFilenames::kCredits])) {
		warning("Failed to open credits file: %s",
		        _localizedFilenames[LocalizedFilenames::kCredits].c_str());
		delete imageDecoder;
		return;
	}

	showMouse(false);

	char line[256];
	bool end = false;
	bool calculatedScreen = false;
	uint lineHeight = 20;
	uint currentY = 0;
	int32 fileOffset = 0;
	bool skipScreen = false;

	while (!end && creditsFile.readLine(line, ARRAYSIZE(line))) {
		// Remove line ending
		line[strlen(line) - 1] = '\0';
		if (!strncmp(line, "###", 3)) {
			// Prefix for commands
			if (!strncmp(line + 3, "ECRAN", 5)) {
				// ECRAN command
				if (calculatedScreen) {
					g_system->copyRectToScreen(creditsSurface.getPixels(), creditsSurface.pitch, 0, 0,
					                           creditsSurface.w, creditsSurface.h);
					if (skipScreen) {
						// Just display palette
						setPalette(palette, 0, 256);
					} else {
						fadeInPalette(palette);
					}
					skipScreen = false;
					// Wait
					uint endScreenTime = g_system->getMillis() + 6000;
					while (g_system->getMillis() < endScreenTime && !skipScreen) {
						g_system->updateScreen();
						g_system->delayMillis(10);

						if (pollEvents()) {
							if (getCurrentMouseButton() == 1) {
								skipScreen = true;
							}
							Common::KeyCode kc = getNextKey().keycode;
							while (kc != Common::KEYCODE_INVALID) {
								if (kc == Common::KEYCODE_SPACE) {
									skipScreen = true;
									break;
								} else if (kc == Common::KEYCODE_ESCAPE) {
									skipScreen = true;
									end = true;
									break;
								}
								kc = getNextKey().keycode;
							}
							clearKeys();
						}
						if (shouldAbort()) {
							skipScreen = true;
							end = true;
						}
					}
					if (!skipScreen) {
						fadeOutPalette();
						fillSurface(0);
					}
					currentY = 0;
					fileOffset = creditsFile.pos();
					calculatedScreen = false;
				} else {
					// We just finished calculated all lines, roll back and display them
					creditsFile.seek(fileOffset, SEEK_SET);
					calculatedScreen = true;
					if (currentY <= 480 - lineHeight) {
						// Center in screen
						currentY = (480 - lineHeight) / 2 - currentY / 2;
					} else {
						currentY = 3;
					}
					creditsSurface.blitFrom(*bgFrame);
				}
			} else if (!strcmp(line + 3, "T0")) {
				_fontManager.setCurrentFont(1);
				lineHeight = _fontManager.getFontMaxHeight() + 10;
			} else if (!strcmp(line + 3, "T1")) {
				_fontManager.setCurrentFont(2);
				lineHeight = _fontManager.getFontMaxHeight() + 10;
			} else if (!strcmp(line + 3, "T2")) {
				_fontManager.setCurrentFont(4);
				lineHeight = _fontManager.getFontMaxHeight() + 10;
			} else if (!strcmp(line + 3, "T3")) {
				_fontManager.setCurrentFont(2);
				lineHeight = _fontManager.getFontMaxHeight() + 10;
			} else if (!strcmp(line + 3, "T4")) {
				_fontManager.setCurrentFont(5);
				lineHeight = _fontManager.getFontMaxHeight() + 10;
			} else if (!strcmp(line + 3, "T5")) {
				_fontManager.setCurrentFont(6);
				lineHeight = _fontManager.getFontMaxHeight() + 10;
			} else {
				warning("Unknown ### command : %s", line + 3);
			}
		} else {
			// Text
			if (calculatedScreen) {
				uint width = _fontManager.getStrWidth(line);
				// Center around 315
				_fontManager.displayStr(315 - width / 2, currentY, line);
			}
			currentY += lineHeight;
		}
	}
	showMouse(true);

	delete imageDecoder;
}

} // End of namespace Versailles
} // End of namespace CryOmni3D