/* 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 "common/file.h"
#include "common/system.h"
#include "graphics/managed_surface.h"
#include "image/bmp.h"

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

#include "cryomni3d/versailles/documentation.h"
#include "cryomni3d/versailles/engine.h"

namespace CryOmni3D {
namespace Versailles {

const Versailles_Documentation::TimelineEntry Versailles_Documentation::kTimelineEntries[] = {
	{ "1638", 340,  15 },
	{ "1643", 470,  30 },
	{ "1648", 380,  45 },
	{ "1649", 500,  60 },
	{ "1650", 420,  75 },
	{ "1651", 520,  90 },
	{ "1652", 450, 105 },
	{ "1654", 540, 120 },
	{ "1658", 470, 135 },
	{ "1659", 550, 150 },
	{ "1660", 480, 165 },
	{ "1661", 560, 180 },
	{ "1662", 490, 195 },
	{ "1663", 560, 210 },
	{ "1664", 490, 225 },
	{ "1665", 560, 240 },
	{ "1666", 490, 255 },
	{ "1667", 560, 270 },
	{ "1668", 490, 285 },
	{ "1670", 550, 300 },
	{ "1671", 480, 315 },
	{ "1672", 540, 330 },
	{ "1673", 470, 345 },
	{ "1674", 530, 360 },
	{ "1675", 460, 375 },
	{ "1678", 510, 390 },
	{ "1680", 430, 405 },
	{ "1681", 490, 420 },
	{ "1682", 400, 435 },
	{ "1683", 450, 450 },
	{ "1684", 156, 444 },
	{ "1685",  81, 439 },
	{ "1686", 140, 422 },
	{ "1687",  73, 413 },
	{ "1689", 128, 401 },
	{ "1697",  62, 389 },
	{ "1700", 121, 378 },
	{ "1701",  62, 366 },
	{ "1702", 121, 355 },
	{ "1709",  62, 344 },
	{ "1710", 121, 333 },
	{ "1712",  67, 322 },
	{ "1715", 128, 311 },
};

void Versailles_Documentation::init(const Sprites *sprites, FontManager *fontManager,
                                    const Common::StringArray *messages, CryOmni3DEngine *engine,
                                    const Common::String &allDocsFileName, const Common::String &linksDocsFileName) {
	_sprites = sprites;
	_fontManager = fontManager;
	_messages = messages;
	_engine = engine;
	_allDocsFileName = allDocsFileName;
	_linksDocsFileName = linksDocsFileName;

	// Build list of records
	Common::File allDocsFile;

	if (!allDocsFile.open(_allDocsFileName)) {
		error("Can't open %s", _allDocsFileName.c_str());
	}

	uint allDocsSize = allDocsFile.size();
	char *allDocs = new char[allDocsSize + 1];
	char *end = allDocs + allDocsSize;
	allDocsFile.read(allDocs, allDocsSize);
	allDocs[allDocsSize] = '\0';
	allDocsFile.close();

	const char *patterns[] = { "FICH=", nullptr };
	RecordInfo record = { uint(-1), uint(-1), uint(-1) };

	char *currentPos = allDocs;
	char *lastRecordName = nullptr;
	bool first = true;

	while (true) {
		currentPos = getDocPartAddress(currentPos, end, patterns);
		if (!currentPos) {
			break;
		}
		currentPos -= 5;
		if (first) {
			record.position = currentPos - allDocs;
			record.id = 0;

			lastRecordName = currentPos + 5;

			first = false;
		} else {
			record.size = (currentPos - allDocs) - record.position;
			//debug("Found record %s", lastRecordName);
			_records[lastRecordName] = record;
			_recordsOrdered.push_back(lastRecordName);

			record.id++;
			record.position = currentPos - allDocs;

			lastRecordName = currentPos + 5;
		}
		// Next line
		currentPos += strlen(currentPos) + 1;
	}
	record.size = allDocsSize - record.position;
	_records[lastRecordName] = record;
	_recordsOrdered.push_back(lastRecordName);

	delete[] allDocs;
}

void Versailles_Documentation::handleDocArea() {
	_engine->showMouse(false);

	// Load all links lazily and free them at the end to not waste memory
	// Maybe it's not really useful
	getLinks("ALL00", _allLinks);

	bool end = false;
	while (!end) {
		Common::String selectedRecord = docAreaHandleSummary();
		if (selectedRecord == "") {
			end = true;
		} else if (selectedRecord == "VT00") {
			selectedRecord = docAreaHandleTimeline();
			if (selectedRecord != "") {
				if (docAreaHandleRecords(selectedRecord) == 2) {
					end = true;
				}
			}
		} else {
			if (docAreaHandleRecords(selectedRecord) == 2) {
				end = true;
			}
		}
	}

	_allLinks.clear();

	_engine->showMouse(true);
}

void Versailles_Documentation::handleDocInGame(const Common::String &record) {
	_visitTrace.clear();
	_currentRecord = record;

	Graphics::ManagedSurface docSurface;
	Common::String nextRecord;
	MouseBoxes boxes(3);

	_engine->showMouse(false);
	bool end = false;
	while (!end) {
		inGamePrepareRecord(docSurface, boxes);
		uint action = inGameHandleRecord(docSurface, boxes, nextRecord);
		switch (action) {
		case 0:
			// Back
			if (!_visitTrace.empty()) {
				_currentRecord = _visitTrace.back();
				_visitTrace.pop_back();
				break;
			}
		// No previous record, like a quit
		// fall through
		case 1:
			// Quit
			end = true;
			break;
		case 2:
			// Follow hyperlink keeping trace
			_visitTrace.push_back(_currentRecord);
			_currentRecord = nextRecord;
			break;
		default:
			error("Invalid case %d when displaying doc record", action);
		}
	}
	_engine->showMouse(true);
}

Common::String Versailles_Documentation::docAreaHandleSummary() {
	struct Category {
		const char *record;
		const char *bmp;
		Common::Point imgPos;
		Common::Rect linesPos;
		const Common::String *title;
		Graphics::Surface highlightedImg;

		Category(const char *record_, const char *bmp_, const Common::Point &imgPos_,
		         const Common::Rect &linesPos_, const Common::String *title_) :
			record(record_), bmp(bmp_), imgPos(imgPos_), linesPos(linesPos_), title(title_) { }
	} categories[8] = {
		Category(
		    "VA00",
		    "VA.bmp",
		    Common::Point(142, 402),
		    Common::Rect(174, 372, 284, 372),
		    &(*_messages)[68]),
		Category(
		    "VR00",
		    "VR.bmp",
		    Common::Point(82, 187),
		    Common::Rect(89, 187, 217, 212),
		    &(*_messages)[69]),
		Category(
		    "VC00",
		    "VC.bmp",
		    Common::Point(176, 105),
		    Common::Rect(177, 107, 292, 130),
		    &(*_messages)[70]),
		Category(
		    "VT00",
		    "VH.bmp", //sic
		    Common::Point(283, 467),
		    Common::Rect(311, 451, 466, 451),
		    &(*_messages)[73]),
		Category(
		    "VV00",
		    "VV.bmp",
		    Common::Point(68, 305),
		    Common::Rect(94, 292, 300, 292),
		    &(*_messages)[71]),
		Category(
		    "VS00",
		    "VS.bmp",
		    Common::Point(321, 70),
		    Common::Rect(322, 71, 540, 94),
		    &(*_messages)[72]),
		Category(
		    nullptr,
		    nullptr,
		    Common::Point(256, 212),
		    Common::Rect(),
		    nullptr),
		Category(
		    nullptr,
		    nullptr,
		    Common::Point(600, 450),
		    Common::Rect(),
		    nullptr)
	};
	Image::BitmapDecoder bmpDecoder;
	Common::File file;

	Image::ImageDecoder *imageDecoder = _engine->loadHLZ("SOM1.HLZ");
	if (!imageDecoder) {
		return "";
	}
	const Graphics::Surface *bgFrame = imageDecoder->getSurface();

	for (uint i = 0; i < ARRAYSIZE(categories); i++) {
		if (!categories[i].bmp) {
			// No BMP to load
			continue;
		}
		if (!file.open(categories[i].bmp)) {
			error("Failed to open BMP file: %s", categories[i].bmp);
		}
		if (!bmpDecoder.loadStream(file)) {
			error("Failed to load BMP file: %s", categories[i].bmp);
		}
		categories[i].highlightedImg.copyFrom(*bmpDecoder.getSurface());
		bmpDecoder.destroy();
		file.close();
	}

	Graphics::ManagedSurface docSurface;
	docSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
	docSurface.blitFrom(*bgFrame);

	_fontManager->setCurrentFont(0);
	_fontManager->setTransparentBackground(true);
	_fontManager->setLineHeight(14);
	_fontManager->setSpaceWidth(0);
	_fontManager->setCharSpacing(1);
	_fontManager->setSurface(&docSurface);

	MouseBoxes boxes(8);
	boxes.setupBox(0, 104, 335, 177, 408);
	boxes.setupBox(1, 46, 122, 119, 195);
	boxes.setupBox(2, 140, 40, 213, 113);
	boxes.setupBox(3, 247, 402, 320, 475);
	boxes.setupBox(4, 32, 240, 105, 313);
	boxes.setupBox(5, 285, 5, 358, 78);
	// No box for 6
	boxes.setupBox(7, 0, 480 - _sprites->getCursor(225).getHeight(), 640, 480);

	_engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
	                      imageDecoder->getPaletteColorCount());

	_engine->setCursor(181);
	_engine->showMouse(true);

	bool redraw = true;
	uint hoveredBox = uint(-1);
	uint selectedBox = uint(-1);

	while (selectedBox == uint(-1)) {
		if (redraw) {
			// Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
			for (uint i = 0; i < ARRAYSIZE(categories); i++) {
				uint foreColor = 243;
				if (i == hoveredBox) {
					foreColor = 241;
					if (categories[hoveredBox].highlightedImg.getPixels() != nullptr) {
						docSurface.transBlitFrom(categories[i].highlightedImg, categories[i].imgPos - Common::Point(36,
						                         65));
					}
				}
				_fontManager->setForeColor(foreColor);
				if (categories[i].title) {
					uint x = categories[i].linesPos.right - _fontManager->getStrWidth(*categories[i].title);
					uint y = categories[i].linesPos.bottom - _fontManager->getFontMaxHeight() - 5;
					_fontManager->displayStr(x, y, *categories[i].title);

					// Draw line to text
					docSurface.vLine(categories[i].linesPos.left, categories[i].linesPos.top,
					                 categories[i].linesPos.bottom, foreColor);
					docSurface.hLine(categories[i].linesPos.left, categories[i].linesPos.bottom,
					                 categories[i].linesPos.right - 1, foreColor); // minus 1 because hLine draws inclusive
				}
			}
			docSurface.transBlitFrom(_sprites->getSurface(225), boxes.getBoxOrigin(7),
			                         _sprites->getKeyColor(225));

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

			redraw = false;
		}
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (_engine->pollEvents()) {
			if (!_engine->getCurrentMouseButton()) {
				// Don't change highlighted icon when clicking
				Common::Point mouse = _engine->getMousePos();
				bool foundBox = false;
				for (uint i = 0; i < ARRAYSIZE(categories); i++) {
					if (boxes.hitTest(i, mouse)) {
						foundBox = true;
						if (i != hoveredBox) {
							hoveredBox = i;
							redraw = true;
						}
					}
				}
				if (!foundBox && hoveredBox != uint(-1)) {
					if (categories[hoveredBox].highlightedImg.getPixels() != nullptr) {
						// Restore original icon
						const Common::Point &imgPos = categories[hoveredBox].imgPos;
						docSurface.blitFrom(*bgFrame, Common::Rect(
						                        imgPos.x - 36, imgPos.y - 65, imgPos.x + 37, imgPos.y + 8),
						                    Common::Point(imgPos.x - 36, imgPos.y - 65));
					}
					hoveredBox = uint(-1);
					redraw = true;
				}
			}
			if (_engine->getDragStatus() == kDragStatus_Finished) {
				if (hoveredBox != uint(-1)) {
					selectedBox = hoveredBox;
				}
			}
			if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) {
				selectedBox = 7;
			}
		}
		if (_engine->shouldAbort()) {
			selectedBox = 7;
		}
	}

	_engine->showMouse(false);

	for (uint i = 0; i < ARRAYSIZE(categories); i++) {
		categories[i].highlightedImg.free();
	}

	delete imageDecoder;

	if (selectedBox == 7) {
		return "";
	} else {
		return categories[selectedBox].record;
	}
}

Common::String Versailles_Documentation::docAreaHandleTimeline() {
	Image::ImageDecoder *imageDecoder = _engine->loadHLZ("chrono1.HLZ");
	if (!imageDecoder) {
		return "";
	}
	const Graphics::Surface *bgFrame = imageDecoder->getSurface();

	Graphics::ManagedSurface docSurface;
	docSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
	docSurface.blitFrom(*bgFrame);

	_fontManager->setCurrentFont(1);
	_fontManager->setTransparentBackground(true);
	_fontManager->setLineHeight(14);
	_fontManager->setSpaceWidth(0);
	_fontManager->setCharSpacing(1);
	_fontManager->setSurface(&docSurface);

	_engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
	                      imageDecoder->getPaletteColorCount());

	_fontManager->displayStr(78, 10, (*_messages)[73]);
	docSurface.hLine(0, 39, 171, 241); // minus 1 because hLine draws inclusive

	_fontManager->setCurrentFont(0);

	MouseBoxes boxes(ARRAYSIZE(kTimelineEntries) + 1);
	for (uint box_id = 0; box_id < ARRAYSIZE(kTimelineEntries); box_id++) {
		boxes.setupBox(box_id, kTimelineEntries[box_id].x, kTimelineEntries[box_id].y,
		               kTimelineEntries[box_id].x + 30, kTimelineEntries[box_id].y + 20);
	}
	const uint leaveBoxId = ARRAYSIZE(kTimelineEntries);
	boxes.setupBox(leaveBoxId, 639 - _sprites->getCursor(105).getWidth(),
	               479 - _sprites->getCursor(105).getHeight(), 640, 480);

	_engine->setCursor(181);
	_engine->showMouse(true);

	bool redraw = true;
	uint hoveredBox = uint(-1);
	uint selectedBox = uint(-1);

	while (selectedBox == uint(-1)) {
		if (redraw) {
			// Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
			for (uint i = 0; i < ARRAYSIZE(kTimelineEntries); i++) {
				_fontManager->setForeColor(i == hoveredBox ? 241 : 243);
				_fontManager->displayStr(kTimelineEntries[i].x, kTimelineEntries[i].y, kTimelineEntries[i].year);
			}
			docSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(leaveBoxId),
			                         _sprites->getKeyColor(105));

			g_system->copyRectToScreen(docSurface.getPixels(), docSurface.pitch, 0, 0,
			                           docSurface.w, docSurface.h);
			redraw = false;
		}
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (_engine->pollEvents()) {
			Common::Point mouse = _engine->getMousePos();
			if (!_engine->getCurrentMouseButton()) {
				// Don't change highlighted date when clicking
				bool foundBox = false;
				for (uint i = 0; i < ARRAYSIZE(kTimelineEntries); i++) {
					if (boxes.hitTest(i, mouse)) {
						foundBox = true;
						if (i != hoveredBox) {
							hoveredBox = i;
							redraw = true;
						}
					}
				}
				if (!foundBox && hoveredBox != uint(-1)) {
					hoveredBox = uint(-1);
					redraw = true;
				}
			}
			if (_engine->getDragStatus() == kDragStatus_Finished) {
				if (hoveredBox != uint(-1)) {
					selectedBox = hoveredBox;
				}
				if (boxes.hitTest(leaveBoxId, mouse)) {
					selectedBox = leaveBoxId;
				}
			}
			if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) {
				selectedBox = leaveBoxId;
			}
		}
		if (_engine->shouldAbort()) {
			selectedBox = leaveBoxId;
		}
	}

	_engine->showMouse(false);

	delete imageDecoder;

	if (selectedBox == leaveBoxId) {
		return "";
	} else {
		Common::String ret = "VT";
		ret += kTimelineEntries[selectedBox].year;
		return ret;
	}
}

uint Versailles_Documentation::docAreaHandleRecords(const Common::String &record) {
	uint action = uint(-1);

	_currentRecord = record;
	_visitTrace.clear();

	Graphics::ManagedSurface docSurface;
	Common::String nextRecord;
	MouseBoxes boxes(10 + ARRAYSIZE(kTimelineEntries));

	while (true) {
		if (action == uint(-1)) {
			_currentRecord.toUppercase();

			//debug("Displaying %s", _currentRecord.c_str());
			docAreaPrepareNavigation();
			docAreaPrepareRecord(docSurface, boxes);
			action = docAreaHandleRecord(docSurface, boxes, nextRecord);
		}

		switch (action) {
		case 0:
			action = uint(-1);
			// Back
			if (!_visitTrace.empty()) {
				_currentRecord = _visitTrace.back();
				_visitTrace.pop_back();
				break;
			}
		// No previous record, like a back to root
		// fall through
		case 1:
			// Back to root
			return 1;
		case 2:
			action = uint(-1);
			// Follow hyperlink keeping trace
			_visitTrace.push_back(_currentRecord);
			_currentRecord = nextRecord;
			break;
		case 3:
			action = uint(-1);
			// Follow hyperlink losing trace
			_visitTrace.clear();
			_currentRecord = nextRecord;
			break;
		case 6:
			// Quit
			return 2;
		case 7:
			action = uint(-1);
			// General map
			_visitTrace.clear();
			nextRecord = docAreaHandleGeneralMap();
			if (nextRecord == "") {
				// Go back to current record
				break;
			} else if (nextRecord != "VS00") {
				_currentRecord = nextRecord;
				break;
			}
		// castle has been selected, display its map
		// fall through
		case 8:
			action = uint(-1);
			// Castle map
			_visitTrace.clear();
			nextRecord = docAreaHandleCastleMap();
			if (nextRecord == "") {
				// Go back to current record
			} else if (nextRecord != "planG") {
				_currentRecord = nextRecord;
			} else {
				// We can't go up to previous case, so let's do a round
				action = 7;
			}
			break;
		case 9:
			action = uint(-1);
			// Start of category
			_currentRecord = _categoryStartRecord;
			break;
		default:
			error("Invalid case %d when displaying doc record", action);
		}
	}
	error("shouldn't be there");
}

void Versailles_Documentation::docAreaPrepareNavigation() {
	_currentInTimeline = false;
	_currentMapLayout = false;
	_currentHasMap = false;
	_currentLinks.clear();

	if (_currentRecord.hasPrefix("VA")) {
		_categoryStartRecord = "VA00";
		_categoryEndRecord = "VA15";
		_categoryTitle = (*_messages)[68];
	} else if (_currentRecord.hasPrefix("VC")) {
		_categoryStartRecord = "VC00";
		_categoryEndRecord = "VC26";
		_categoryTitle = (*_messages)[70];
	} else if (_currentRecord.hasPrefix("VR")) {
		_categoryStartRecord = "VR00";
		_categoryEndRecord = "VR14";
		_categoryTitle = (*_messages)[69];
	} else if (_currentRecord.hasPrefix("VS")) {
		_categoryStartRecord = "VS00";
		_categoryEndRecord = "VS37";
		_categoryTitle = (*_messages)[72];
		uint id = atoi(_currentRecord.c_str() + 2);
		if (id >= 16 && id <= 40) {
			_currentMapLayout = true;
		}
		if ((id >= 16 && id <= 31) ||
		        (id >= 35 && id <= 39)) {
			_currentHasMap = true;
		}
	} else if (_currentRecord.hasPrefix("VT")) {
		_categoryStartRecord = "VT00";
		_categoryEndRecord = "VT1715";
		_categoryTitle = (*_messages)[73];
		_currentInTimeline = true;
	} else if (_currentRecord.hasPrefix("VV")) {
		_categoryStartRecord = "VV00";
		_categoryEndRecord = "VV15";
		_categoryTitle = (*_messages)[71];
	}
	getLinks(_currentRecord, _currentLinks);
}

void Versailles_Documentation::docAreaPrepareRecord(Graphics::ManagedSurface &surface,
        MouseBoxes &boxes) {
	boxes.reset();

	setupRecordBoxes(true, boxes);

	Common::String title, subtitle, caption;
	Common::StringArray hyperlinks;
	Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks);

	drawRecordData(surface, text, title, subtitle, caption);

	if (_currentInTimeline) {
		surface.hLine(0, 39, 171, 241); // minus 1 because hLine draws inclusive
		_fontManager->setCurrentFont(0);
		_fontManager->setTransparentBackground(true);
		_fontManager->setLineHeight(14);
		_fontManager->setSpaceWidth(0);
		_fontManager->setCharSpacing(1);
		_fontManager->setSurface(&surface);
		_fontManager->setForeColor(243);
		for (uint box_id = 10; box_id < ARRAYSIZE(kTimelineEntries) + 10; box_id++) {
			boxes.display(box_id, *_fontManager);
		}
	}

	drawRecordBoxes(surface, true, boxes);
}

uint Versailles_Documentation::docAreaHandleRecord(Graphics::ManagedSurface &surface,
        MouseBoxes &boxes, Common::String &nextRecord) {
	// Hovering is only handled for timeline entries
	_engine->setCursor(181);
	_engine->showMouse(true);

	bool first = true;
	bool redraw = true;
	uint hoveredBox = uint(-1);
	uint action = uint(-1);

	while (action == uint(-1)) {
		if (redraw) {
			g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
			redraw = false;
		}
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (_engine->pollEvents() || first) {
			first = false;
			if (_engine->shouldAbort()) {
				// Fake the quit
				action = 6;
			}
			Common::Point mouse = _engine->getMousePos();
			if (_currentInTimeline) {
				bool foundBox = false;
				for (uint i = 10; i < 10 + ARRAYSIZE(kTimelineEntries); i++) {
					if (boxes.hitTest(i, mouse)) {
						foundBox = true;
						if (i != hoveredBox) {
							_fontManager->setCurrentFont(0);
							_fontManager->setTransparentBackground(true);
							_fontManager->setSurface(&surface);
							if (hoveredBox != uint(-1)) {
								// Restore the previous entry hovered
								_fontManager->setForeColor(243);
								boxes.display(hoveredBox, *_fontManager);
							}
							hoveredBox = i;
							_fontManager->setForeColor(241);
							boxes.display(hoveredBox, *_fontManager);
							redraw = true;
						}
					}
				}
				if (!foundBox && hoveredBox != uint(-1)) {
					// Restore the previous entry hovered
					_fontManager->setForeColor(243);
					boxes.display(hoveredBox, *_fontManager);
					hoveredBox = uint(-1);
					redraw = true;
				}
			} else if (_currentHasMap) { // Mutually exclusive with timeline
				// No clash is possible for hoveredBox between timeline and map
				if (boxes.hitTest(8, mouse)) {
					if (hoveredBox != 8) {
						_engine->setCursor(145);
						hoveredBox = 8;
					}
				} else {
					if (hoveredBox == 8) {
						_engine->setCursor(181);
						hoveredBox = uint(-1);
					}
				}
			}
			if (_engine->getDragStatus() == kDragStatus_Pressed) {
				if (boxes.hitTest(2, mouse) && _currentLinks.size()) {
					Common::StringArray items;
					for (Common::Array<LinkInfo>::const_iterator it = _currentLinks.begin(); it != _currentLinks.end();
					        it++) {
						items.push_back(it->title);
					}
					Common::Rect iconRect = boxes.getBoxRect(2);
					uint selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top),
					                                    true, 20, items);
					if (selectedItem != uint(-1)) {
						nextRecord = _currentLinks[selectedItem].record;
						action = 2;
					}
				} else if (boxes.hitTest(3, mouse)) {
					Common::StringArray items;
					for (Common::Array<LinkInfo>::const_iterator it = _allLinks.begin(); it != _allLinks.end(); it++) {
						items.push_back(it->title);
					}
					Common::Rect iconRect = boxes.getBoxRect(3);
					uint selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top),
					                                    true, 20, items);
					if (selectedItem != uint(-1)) {
						nextRecord = _allLinks[selectedItem].record;
						action = 3;
					}
				}
			} else if (_engine->getDragStatus() == kDragStatus_Finished) {
				if (boxes.hitTest(0, mouse)) {
					// Back in history
					action = 0;
				} else if (boxes.hitTest(1, mouse)) {
					// Handle summary menu
					Common::StringArray items;
					items.push_back((*_messages)[61]);
					items.push_back((*_messages)[62]);
					uint selectedItem = handlePopupMenu(surface, boxes.getBoxOrigin(1), false, 20, items);
					if (selectedItem == 0) {
						action = 1;
					} else if (selectedItem == 1) {
						action = 7;
					}
				} else if (boxes.hitTest(4, mouse)) {
					// Next
					action = 4;
				} else if (boxes.hitTest(5, mouse)) {
					// Previous
					action = 5;
				} else if (boxes.hitTest(6, mouse)) {
					// Handle quit menu
					Common::StringArray items;
					items.push_back((*_messages)[60]);
					uint selectedItem = handlePopupMenu(surface, boxes.getBoxOrigin(6), false, 20, items);
					if (selectedItem == 0) {
						action = 6;
					}
				} else if (_currentHasMap && boxes.hitTest(8, mouse)) {
					// Map
					action = 8;
				} else if (boxes.hitTest(9, mouse)) {
					// Category name
					action = 9;
				} else if (_currentInTimeline && hoveredBox != uint(-1)) {
					// Clicked on a timeline entry
					nextRecord = "VT";
					nextRecord += kTimelineEntries[hoveredBox - 10].year;
					// Fake a global jump
					action = 3;
				}
			}
			if (action == 4 || action == 5) {
				if (action == 4 && _currentRecord == _categoryEndRecord) {
					action = uint(-1);
					continue;
				}
				if (action == 5 && _currentRecord == _categoryStartRecord) {
					action = uint(-1);
					continue;
				}
				Common::HashMap<Common::String, RecordInfo>::iterator hmIt = _records.find(_currentRecord);
				if (hmIt == _records.end()) {
					// Shouldn't happen
					action = uint(-1);
					continue;
				}
				uint recordId = hmIt->_value.id;
				if (action == 4) {
					recordId++;
				} else if (action == 5) {
					recordId--;
				}
				assert(recordId < _recordsOrdered.size());
				nextRecord = _recordsOrdered[recordId];
				// Fake a global jump
				action = 3;
			}
		}
	}

	_engine->showMouse(false);
	_engine->setCursor(181);
	return action;
}

Common::String Versailles_Documentation::docAreaHandleGeneralMap() {
	struct Area {
		Common::Rect areaPos;
		const char *record;
		const char *bmp;
		uint messageId;
		const Common::String *message;
		Common::Point messagePos;
		Graphics::Surface highlightedImg;

		Area(const Common::Point &areaPos_, const char *bmp_, uint messageId_,
		     const char *record_ = nullptr) :
			areaPos(areaPos_.x, areaPos_.y, areaPos_.x, areaPos_.y), record(record_), bmp(bmp_),
			messageId(messageId_), message(nullptr) { }
		Area(const Common::Rect &areaPos_, uint messageId_, const char *record_ = nullptr) :
			areaPos(areaPos_), record(record_), bmp(nullptr), messageId(messageId_), message(nullptr) { }
	} areas[] = {
		Area(Common::Point(174, 181), "APL.bmp", 74),
		Area(Common::Point(422, 129), "CHAT.bmp", 75, "VS00"),
		Area(Common::Point(193, 204), "COLN.bmp", 76, "VS02"),
		Area(Common::Point(327, 269), "LABY.bmp", 77, "VS33"),
		Area(Common::Point(327, 170), "LATN.bmp", 78),
		Area(Common::Point(396, 271), "ORG.bmp", 79, "VS32"),
		Area(Common::Point(385, 203), "PART.bmp", 80, "VS06"),
		Area(Common::Point(212, 193), "TAP.bmp", 81),
		Area(Common::Rect(0, 194, 154, 211), 86, "VS09"),
		Area(Common::Rect(396, 229, 450, 268), 87),
		Area(Common::Rect(394, 133, 450, 177), 88),
		Area(Common::Rect(489, 376, 592, 479), 89, "VS07"),
		Area(Common::Rect(327, 233, 386, 266), 90),
		Area(Common::Rect(395, 18, 451, 60), 91),
		Area(Common::Rect(383, 381, 477, 479), 92)
	};

	_fontManager->setCurrentFont(0);
	_fontManager->setTransparentBackground(true);
	_fontManager->setLineHeight(14);
	_fontManager->setSpaceWidth(0);
	_fontManager->setCharSpacing(1);

	MouseBoxes boxes(ARRAYSIZE(areas) + 1);

	Image::BitmapDecoder bmpDecoder;
	Common::File file;

	Image::ImageDecoder *imageDecoder = _engine->loadHLZ("PLANGR.HLZ");
	if (!imageDecoder) {
		return "";
	}
	const Graphics::Surface *bgFrame = imageDecoder->getSurface();

	for (uint i = 0; i < ARRAYSIZE(areas); i++) {
		if (areas[i].bmp) {
			if (!file.open(areas[i].bmp)) {
				error("Failed to open BMP file: %s", areas[i].bmp);
			}
			if (!bmpDecoder.loadStream(file)) {
				error("Failed to load BMP file: %s", areas[i].bmp);
			}
			areas[i].highlightedImg.copyFrom(*bmpDecoder.getSurface());
			bmpDecoder.destroy();
			file.close();
			areas[i].areaPos.setWidth(areas[i].highlightedImg.w);
			areas[i].areaPos.setHeight(areas[i].highlightedImg.h);
		}
		areas[i].message = &(*_messages)[areas[i].messageId];
		uint lineWidth = _fontManager->getStrWidth(*areas[i].message);
		areas[i].messagePos.x = (areas[i].areaPos.left + areas[i].areaPos.right) / 2 - lineWidth / 2;
		areas[i].messagePos.y = areas[i].areaPos.top - 40;
		if (areas[i].messagePos.x < 8) {
			areas[i].messagePos.x = 8;
		} else if (areas[i].messagePos.x + lineWidth > 627) {
			areas[i].messagePos.x = 627 - lineWidth;
		}
		if (areas[i].messagePos.y < 5) {
			areas[i].messagePos.y = 5;
		}
		const Common::Rect &areaPos = areas[i].areaPos;
		boxes.setupBox(i, areaPos.left, areaPos.top, areaPos.right, areaPos.bottom);
	}
	boxes.setupBox(ARRAYSIZE(areas), 639 - _sprites->getCursor(105).getWidth(),
	               479 - _sprites->getCursor(105).getHeight(), 640, 480);

	Graphics::ManagedSurface mapSurface;
	mapSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
	mapSurface.blitFrom(*bgFrame);

	_fontManager->setSurface(&mapSurface);

	_engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
	                      imageDecoder->getPaletteColorCount());

	_engine->setCursor(181);
	_engine->showMouse(true);

	bool redraw = true;
	uint hoveredBox = uint(-1);
	uint selectedBox = uint(-1);

	while (selectedBox == uint(-1)) {
		if (redraw) {
			// Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
			if (hoveredBox != uint(-1)) {
				if (areas[hoveredBox].highlightedImg.getPixels() != nullptr) {
					mapSurface.transBlitFrom(areas[hoveredBox].highlightedImg,
					                         Common::Point(areas[hoveredBox].areaPos.left, areas[hoveredBox].areaPos.top));
				} else {
					uint middleX = (areas[hoveredBox].areaPos.left + areas[hoveredBox].areaPos.right) / 2;
					uint middleY = (areas[hoveredBox].areaPos.top + areas[hoveredBox].areaPos.bottom) / 2;
					uint spriteX = middleX - _sprites->getCursor(163).getWidth() / 2;
					uint spriteY = middleY - _sprites->getCursor(163).getHeight() / 2;
					mapSurface.transBlitFrom(_sprites->getSurface(163), Common::Point(spriteX, spriteY),
					                         _sprites->getKeyColor(163));
				}
				_fontManager->setForeColor(areas[hoveredBox].record == nullptr ? 243 : 240);
				Graphics::Surface subSurface = mapSurface.getSubArea(Common::Rect(areas[hoveredBox].messagePos.x -
				                               3,
				                               areas[hoveredBox].messagePos.y - 3,
				                               areas[hoveredBox].messagePos.x + _fontManager->getStrWidth(*areas[hoveredBox].message) + 3,
				                               areas[hoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 8));
				_engine->makeTranslucent(subSurface, subSurface);
				_fontManager->displayStr(areas[hoveredBox].messagePos.x, areas[hoveredBox].messagePos.y,
				                         *areas[hoveredBox].message);
			}
			mapSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(ARRAYSIZE(areas)),
			                         _sprites->getKeyColor(105));
			/*
			// For debugging only
			for(uint i = 0; i < ARRAYSIZE(areas); i++) {
			    mapSurface.frameRect(areas[i].areaPos, 0);
			}
			*/

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

			redraw = false;
		}
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (_engine->pollEvents()) {
			Common::Point mouse = _engine->getMousePos();
			if (!_engine->getCurrentMouseButton()) {
				// Don't change highlighted icon when clicking
				bool foundBox = false;
				uint oldHoveredBox = hoveredBox;
				for (uint i = 0; i < ARRAYSIZE(areas); i++) {
					if (boxes.hitTest(i, mouse)) {
						if (i != hoveredBox) {
							hoveredBox = i;
							redraw = true;
						}
						foundBox = true;
						break;
					}
				}
				if (!foundBox && hoveredBox != uint(-1)) {
					hoveredBox = uint(-1);
					redraw = true;
				}
				if (hoveredBox != oldHoveredBox && oldHoveredBox != uint(-1)) {
					// Restore original area
					mapSurface.blitFrom(*bgFrame, areas[oldHoveredBox].areaPos,
					                    Common::Point(areas[oldHoveredBox].areaPos.left, areas[oldHoveredBox].areaPos.top));
					Common::Rect textRect(areas[oldHoveredBox].messagePos.x - 3,
					                      areas[oldHoveredBox].messagePos.y - 3,
					                      areas[oldHoveredBox].messagePos.x + _fontManager->getStrWidth(*areas[oldHoveredBox].message) + 3,
					                      areas[oldHoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 8);
					mapSurface.blitFrom(*bgFrame, textRect,
					                    Common::Point(textRect.left, textRect.top));
				}
			}
			if (_engine->getDragStatus() == kDragStatus_Finished) {
				if (hoveredBox != uint(-1) && areas[hoveredBox].record) {
					selectedBox = hoveredBox;
				} else if (boxes.hitTest(ARRAYSIZE(areas), mouse)) {
					selectedBox = ARRAYSIZE(areas);
				}
			}
			if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) {
				selectedBox = ARRAYSIZE(areas);
			}
			if (_engine->shouldAbort()) {
				selectedBox = ARRAYSIZE(areas);
			}
		}
	}

	_engine->showMouse(false);

	for (uint i = 0; i < ARRAYSIZE(areas); i++) {
		areas[i].highlightedImg.free();
	}

	delete imageDecoder;

	if (selectedBox == ARRAYSIZE(areas)) {
		return "";
	} else {
		return areas[selectedBox].record;
	}
}

Common::String Versailles_Documentation::docAreaHandleCastleMap() {
	struct Area {
		Common::Rect areaPos;
		bool fillArea;
		const char *record;
		uint messageId;
		Common::String message;
		Common::Point messagePos;
		Common::Rect areaPos1;
		Common::Rect areaPos2;

		Area(const Common::Rect &areaPos_, const char *record_, bool fillArea_ = true,
		     uint messageId_ = uint(-1)) :
			areaPos(areaPos_), record(record_), fillArea(fillArea_), messageId(messageId_) { }
		Area(const Common::Rect &areaPos_, const Common::Rect &areaPos1_,
		     const Common::Rect &areaPos2_, const char *record_, bool fillArea_ = true,
		     uint messageId_ = uint(-1)) :
			areaPos(areaPos_), areaPos1(areaPos1_), areaPos2(areaPos2_),
			record(record_), fillArea(fillArea_), messageId(messageId_) { }
	} areas[] = {
		/* 0 */
		Area(Common::Rect(212, 134, 239, 164), "VS16"),
		Area(Common::Rect(74, 160, 89, 173), "VS24"),
		Area(Common::Rect(93, 160, 109, 173), "VS25"),
		Area(Common::Rect(130, 160, 154, 173), "VS26"),
		Area(Common::Rect(158, 160, 171, 173), "VS27"),
		Area(Common::Rect(199, 160, 209, 171), "VS28"),
		Area(Common::Rect(74, 177, 89, 291), "VS31"),
		Area(Common::Rect(158, 178, 195, 193), "VS30"),
		Area(Common::Rect(199, 175, 209, 188), "VS29"),
		Area(Common::Rect(112, 220, 160, 249), "VS35"),
		/* 10 */
		Area(Common::Rect(93, 227, 106, 240), "VS23"),
		Area(Common::Rect(93, 244, 106, 257), "VS22"),
		Area(Common::Rect(93, 261, 106, 274), "VS20"),
		Area(Common::Rect(110, 255, 126, 269), "VS19"),
		Area(Common::Rect(133, 255, 155, 271), "VS18"),
		Area(Common::Rect(93, 285, 99, 295), "VS21"),
		Area(Common::Rect(152, 279, 173, 288), "VS17"),
		Area(Common::Rect(336, 113, 359, 136), Common::Rect(359, 116, 448, 134), Common::Rect(449, 113, 473, 136), "VS36"),
		Area(Common::Rect(336, 328, 359, 351), Common::Rect(359, 331, 448, 348), Common::Rect(449, 328, 473, 351), "VS36"),
		Area(Common::Rect(563, 0, 624, 139), "planG", false, 82),
		/* 20 */
		Area(Common::Rect(563, 300, 624, 462), "planG", false, 83),
		Area(Common::Rect(0, 0, 205, 152), "planG", false, 84),
		Area(Common::Rect(0, 318, 205, 465), "planG", false, 84),
		Area(Common::Rect(160, 210, 329, 267), "VS40", false),
		Area(Common::Rect(330, 158, 561, 315), "planG", false, 85),
	};

	_fontManager->setCurrentFont(0);
	_fontManager->setTransparentBackground(true);
	_fontManager->setLineHeight(14);
	_fontManager->setSpaceWidth(0);
	_fontManager->setCharSpacing(1);

	MouseBoxes boxes(ARRAYSIZE(areas) + 1);

	for (uint i = 0; i < ARRAYSIZE(areas); i++) {
		if (areas[i].messageId != uint(-1)) {
			areas[i].message = (*_messages)[areas[i].messageId];
		} else {
			areas[i].message = getRecordTitle(areas[i].record);
		}
		uint lineWidth = _fontManager->getStrWidth(areas[i].message);
		uint right;
		if (areas[i].areaPos2.right) {
			right = areas[i].areaPos2.right;
		} else {
			right = areas[i].areaPos.right;
		}
		areas[i].messagePos.x = (areas[i].areaPos.left + right) / 2 - lineWidth / 2;
		if (areas[i].fillArea) {
			areas[i].messagePos.y = areas[i].areaPos.top - 30;
		} else {
			areas[i].messagePos.y = (areas[i].areaPos.top + areas[i].areaPos.bottom) / 2 - 50;
		}
		if (areas[i].messagePos.x < 5) {
			areas[i].messagePos.x = 5;
		} else if (areas[i].messagePos.x + lineWidth > 630) {
			areas[i].messagePos.x = 630 - lineWidth;
		}
		if (areas[i].messagePos.y < 2) {
			areas[i].messagePos.y = 2;
		}
		Common::Rect areaPos = areas[i].areaPos;
		if (areas[i].areaPos2.right) {
			areaPos.right = areas[i].areaPos2.right;
			areaPos.bottom = areas[i].areaPos2.bottom;
		}
		boxes.setupBox(i, areaPos.left, areaPos.top, areaPos.right, areaPos.bottom);
	}
	boxes.setupBox(ARRAYSIZE(areas), 639 - _sprites->getCursor(105).getWidth(),
	               479 - _sprites->getCursor(105).getHeight(), 640, 480);

	Image::ImageDecoder *imageDecoder = _engine->loadHLZ("PLAN.HLZ");
	if (!imageDecoder) {
		return "";
	}
	const Graphics::Surface *bgFrame = imageDecoder->getSurface();

	Graphics::ManagedSurface mapSurface;
	mapSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
	mapSurface.blitFrom(*bgFrame);

	_fontManager->setSurface(&mapSurface);

	_engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
	                      imageDecoder->getPaletteColorCount());

	_engine->setCursor(181);
	_engine->showMouse(true);

	bool redraw = true;
	uint hoveredBox = uint(-1);
	uint selectedBox = uint(-1);

	while (selectedBox == uint(-1)) {
		if (redraw) {
			// Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
			if (hoveredBox != uint(-1)) {
				if (areas[hoveredBox].fillArea) {
					Common::Rect rect(areas[hoveredBox].areaPos);
					rect.bottom += 1; // fillRect needs to fill including the limit
					rect.right += 1;
					mapSurface.fillRect(rect, 243);
					if (areas[hoveredBox].areaPos1.right) {
						rect = Common::Rect(areas[hoveredBox].areaPos1);
						rect.bottom += 1; // fillRect needs to fill including the limit
						rect.right += 1;
						mapSurface.fillRect(rect, 243);
					}
					if (areas[hoveredBox].areaPos2.right) {
						rect = Common::Rect(areas[hoveredBox].areaPos2);
						rect.bottom += 1; // fillRect needs to fill including the limit
						rect.right += 1;
						mapSurface.fillRect(rect, 243);
					}
				} else {
					uint middleX = (areas[hoveredBox].areaPos.left + areas[hoveredBox].areaPos.right) / 2;
					uint middleY = (areas[hoveredBox].areaPos.top + areas[hoveredBox].areaPos.bottom) / 2;
					uint spriteX = middleX - _sprites->getCursor(163).getWidth() / 2;
					uint spriteY = middleY - _sprites->getCursor(163).getHeight() / 2;
					mapSurface.transBlitFrom(_sprites->getSurface(163), Common::Point(spriteX, spriteY),
					                         _sprites->getKeyColor(163));
				}
				Common::Rect textRect(areas[hoveredBox].messagePos.x - 4,
				                      areas[hoveredBox].messagePos.y,
				                      areas[hoveredBox].messagePos.x + _fontManager->getStrWidth(areas[hoveredBox].message) + 5,
				                      areas[hoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 5);
				mapSurface.fillRect(textRect, 247);
				_fontManager->setForeColor(strcmp(areas[hoveredBox].record, "planG") == 0 ? 243 : 241);
				_fontManager->displayStr(areas[hoveredBox].messagePos.x, areas[hoveredBox].messagePos.y,
				                         areas[hoveredBox].message);
			}
			mapSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(ARRAYSIZE(areas)),
			                         _sprites->getKeyColor(105));
			/*
			// For debugging only
			for(uint i = 0; i < ARRAYSIZE(areas); i++) {
			    mapSurface.frameRect(areas[i].areaPos, 0);
			    if (areas[i].areaPos1.right) {
			        mapSurface.frameRect(areas[i].areaPos1, 0);
			    }
			    if (areas[i].areaPos2.right) {
			        mapSurface.frameRect(areas[i].areaPos2, 0);
			    }
			}
			*/

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

			redraw = false;
		}
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (_engine->pollEvents()) {
			Common::Point mouse = _engine->getMousePos();
			if (!_engine->getCurrentMouseButton()) {
				// Don't change highlighted icon when clicking
				bool foundBox = false;
				uint oldHoveredBox = hoveredBox;
				for (uint i = 0; i < ARRAYSIZE(areas); i++) {
					if (boxes.hitTest(i, mouse)) {
						if (i != hoveredBox) {
							hoveredBox = i;
							redraw = true;
						}
						foundBox = true;
						break;
					}
				}
				if (!foundBox && hoveredBox != uint(-1)) {
					hoveredBox = uint(-1);
					redraw = true;
				}
				if (hoveredBox != oldHoveredBox && oldHoveredBox != uint(-1)) {
					// Restore original area
					Common::Rect areaPos = areas[oldHoveredBox].areaPos;
					if (areas[oldHoveredBox].areaPos2.right) {
						areaPos.right = areas[oldHoveredBox].areaPos2.right;
						areaPos.bottom = areas[oldHoveredBox].areaPos2.bottom;
					}
					areaPos.right += 1;
					areaPos.bottom += 1;
					mapSurface.blitFrom(*bgFrame, areaPos,
					                    Common::Point(areaPos.left, areaPos.top));
					Common::Rect textRect(areas[oldHoveredBox].messagePos.x - 4,
					                      areas[oldHoveredBox].messagePos.y,
					                      areas[oldHoveredBox].messagePos.x + _fontManager->getStrWidth(areas[oldHoveredBox].message) + 5,
					                      areas[oldHoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 5);
					mapSurface.blitFrom(*bgFrame, textRect,
					                    Common::Point(textRect.left, textRect.top));
				}
			}
			if (_engine->getDragStatus() == kDragStatus_Finished) {
				if (hoveredBox != uint(-1) && areas[hoveredBox].record) {
					selectedBox = hoveredBox;
				} else if (boxes.hitTest(ARRAYSIZE(areas), mouse)) {
					selectedBox = ARRAYSIZE(areas);
				}
			}
			if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) {
				selectedBox = ARRAYSIZE(areas);
			}
			if (_engine->shouldAbort()) {
				selectedBox = ARRAYSIZE(areas);
			}
		}
	}

	_engine->showMouse(false);

	delete imageDecoder;

	if (selectedBox == ARRAYSIZE(areas)) {
		return "";
	} else {
		return areas[selectedBox].record;
	}
}

void Versailles_Documentation::inGamePrepareRecord(Graphics::ManagedSurface &surface,
        MouseBoxes &boxes) {
	_categoryStartRecord = "";
	_categoryEndRecord = "";
	_categoryTitle = "";
	_currentLinks.clear();
	_currentInTimeline = false;
	_currentMapLayout = false;
	_currentHasMap = false;

	if (_currentRecord.hasPrefix("VS")) {
		uint id = atoi(_currentRecord.c_str() + 2);
		if (id >= 16 && id <= 40) {
			_currentMapLayout = true;
		}
	} else if (_currentRecord.hasPrefix("VT")) {
		error("There shouldn't be the timeline in game");
	}

	boxes.reset();

	setupRecordBoxes(false, boxes);

	Common::String title, subtitle, caption;
	Common::StringArray hyperlinks;
	Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks);
	convertHyperlinks(hyperlinks, _currentLinks);

	drawRecordData(surface, text, title, subtitle, caption);
	drawRecordBoxes(surface, false, boxes);
}

uint Versailles_Documentation::inGameHandleRecord(Graphics::ManagedSurface &surface,
        MouseBoxes &boxes, Common::String &nextRecord) {
	_engine->setCursor(181);
	_engine->showMouse(true);

	uint action = uint(-1);

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

	while (action == uint(-1)) {
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (_engine->pollEvents()) {
			if (_engine->shouldAbort()) {
				// Fake the quit
				action = 1;
			}
			Common::Point mouse = _engine->getMousePos();
			if (_engine->getDragStatus() == kDragStatus_Pressed) {
				if (boxes.hitTest(2, mouse) && _currentLinks.size()) {
					Common::StringArray items;
					for (Common::Array<LinkInfo>::const_iterator it = _currentLinks.begin(); it != _currentLinks.end();
					        it++) {
						items.push_back(it->title);
					}
					Common::Rect iconRect = boxes.getBoxRect(2);
					uint selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top),
					                                    true, 20, items);
					if (selectedItem != uint(-1)) {
						nextRecord = _currentLinks[selectedItem].record;
						action = 2;
					}
				}
			} else if (_engine->getDragStatus() == kDragStatus_Finished) {
				if (boxes.hitTest(0, mouse)) {
					// Back in history
					action = 0;
				} else if (boxes.hitTest(1, mouse)) {
					// Quit
					action = 1;
				}
			}
		}
	}

	_engine->showMouse(false);
	_engine->setCursor(181);
	return action;
}

void Versailles_Documentation::drawRecordData(Graphics::ManagedSurface &surface,
        const Common::String &text, const Common::String &title,
        const Common::String &subtitle, const Common::String &caption) {
	bool displayMap = false;
	unsigned char foreColor = 247;
	Common::String background;
	Common::Rect blockTitle;
	Common::Rect blockHLine;
	Common::Rect blockSubTitle;
	Common::Rect blockCaption;
	Common::Rect blockContent1;
	Common::Rect blockContent2;

	if (_currentMapLayout) {
		blockTitle = Common::Rect(30, 8, 361, 38);
		blockHLine = Common::Rect(60, 35, 286, 35);
		blockSubTitle = Common::Rect(60, 40, 361, 70);
		blockCaption = Common::Rect(378, 293, 630, 344);
		blockContent1 = Common::Rect(60, 60, 272, 295);
		blockContent2 = Common::Rect(60, 295, 383, 437);
	} else if (_currentInTimeline) {
		blockTitle = Common::Rect(78, 10, 170, 33);
		//blockHLine = Common::Rect();
		blockSubTitle = Common::Rect(60, 40, 361, 70);
		blockCaption = Common::Rect(378, 293, 630, 344);
		blockContent1 = Common::Rect(47, 70, 420, 306);
		blockContent2 = Common::Rect(174, 306, 414, 411);
	} else if (_currentRecord == "VC02" ||
	           _currentRecord == "VC03" ||
	           _currentRecord == "VV01") {
		blockTitle = Common::Rect(30, 8, 361, 38);
		blockHLine = Common::Rect(60, 35, 378, 35);
		blockSubTitle = Common::Rect(60, 40, 361, 70);
		blockCaption = Common::Rect(378, 293, 630, 360);
		blockContent1 = Common::Rect(60, 80, 351, 355);
		blockContent2 = Common::Rect(60, 355, 605, 437);
	} else if (_currentRecord == "VV13" ||
	           _currentRecord == "VV08") {
		blockTitle = Common::Rect(30, 8, 361, 38);
		blockHLine = Common::Rect(60, 35, 286, 35);
		blockSubTitle = Common::Rect(60, 40, 361, 70);
		blockCaption = Common::Rect(378, 422, 630, 480);
		blockContent1 = Common::Rect(60, 60, 378, 285);
		blockContent2 = Common::Rect(60, 285, 378, 437);
	} else {
		blockTitle = Common::Rect(30, 8, 361, 38);
		blockHLine = Common::Rect(60, 35, 378, 35);
		blockSubTitle = Common::Rect(60, 40, 361, 70);
		blockCaption = Common::Rect(378, 293, 630, 360);
		blockContent1 = Common::Rect(60, 80, 351, 345);
		blockContent2 = Common::Rect(60, 345, 605, 437);
	}
	if (_currentInTimeline) {
		background = "CHRONO1";
		foreColor = 241;
	} else {
		background = _currentRecord;
	}
	background = _engine->prepareFileName(background, "hlz");
	Common::File backgroundFl;
	if (!backgroundFl.open(background)) {
		background = displayMap ? "pas_fonP.hlz" : "pas_fond.hlz";
	} else {
		backgroundFl.close();
	}

	Image::ImageDecoder *imageDecoder = _engine->loadHLZ(background);
	const Graphics::Surface *bgFrame = imageDecoder->getSurface();

	_engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
	                      imageDecoder->getPaletteColorCount());

	surface.create(bgFrame->w, bgFrame->h, bgFrame->format);
	surface.blitFrom(*bgFrame);

	/*Common::String title, subtitle, caption;
	Common::StringArray hyperlinks;

	Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks);*/

	uint lineHeight = 21;
	_fontManager->setCurrentFont(4);
	_fontManager->setTransparentBackground(true);
	_fontManager->setSpaceWidth(1);
	_fontManager->setCharSpacing(1);
	_fontManager->setForeColor(foreColor);
	_fontManager->setSurface(&surface);

	/*
	surface.frameRect(blockContent1, foreColor);
	surface.frameRect(blockContent2, foreColor);
	surface.frameRect(blockTitle, foreColor);
	surface.frameRect(blockSubTitle, foreColor);
	surface.frameRect(blockCaption, foreColor);
	*/

	Graphics::ManagedSurface backupSurface;
	backupSurface.copyFrom(surface);

	// This loop tries to adapt the interline space to make all the text fit in the blocks
	while (true) {
		_fontManager->setLineHeight(lineHeight);
		_fontManager->setupBlock(blockContent1);
		if (!_fontManager->displayBlockText(text)) {
			// All text was drawn
			break;
		}

		// Setup second zone
		blockContent2.top = _fontManager->blockTextLastPos().y + lineHeight;
		_fontManager->setupBlock(blockContent2);

		if (!_fontManager->displayBlockText(text, _fontManager->blockTextRemaining())) {
			// All text was drawn
			break;
		}

		// Not all text could be drawn: shrink everything, restore image and do it again
		lineHeight--;
		surface.copyFrom(backupSurface);
	}

	_fontManager->setForeColor(foreColor);
	_fontManager->setCurrentFont(0);
	_fontManager->setTransparentBackground(true);
	_fontManager->setLineHeight(20);
	_fontManager->setCharSpacing(0);
	_fontManager->setSpaceWidth(2);

	//debug("Title: %s", title.c_str());
	_fontManager->setupBlock(blockTitle);
	_fontManager->displayBlockText(title);

	_fontManager->setCurrentFont(6);
	_fontManager->setLineHeight(14);
	_fontManager->setSpaceWidth(1);

	//debug("Subtitle: %s", subtitle.c_str());
	_fontManager->setupBlock(blockSubTitle);
	_fontManager->displayBlockText(subtitle);

	if (!_currentInTimeline) {
		surface.hLine(blockHLine.left, blockHLine.top, blockHLine.right - 1,
		              foreColor); // minus 1 because hLine draws inclusive
	}

	_fontManager->setSpaceWidth(0);

	_fontManager->setupBlock(blockCaption);
	_fontManager->displayBlockText(caption);

	delete imageDecoder;
}

void Versailles_Documentation::setupRecordBoxes(bool inDocArea, MouseBoxes &boxes) {
	// Layout of bar in doc area is Quit | Back |   | Previous | Category | Next |   | Trace | Hyperlinks | All records
	// Layout of bar in game is ==> Trace | Hyperlinks | Quit
	uint allRecordsX = 640 - _sprites->getCursor(19).getWidth();
	uint hyperlinksX = allRecordsX - _sprites->getCursor(242).getWidth() - 10;
	uint traceX = hyperlinksX - _sprites->getCursor(105).getWidth() - 10;

	if (_visitTrace.size()) {
		boxes.setupBox(0, traceX, 480 - _sprites->getCursor(105).getHeight() - 3,
		               traceX + _sprites->getCursor(105).getWidth(), 480);
	}
	if (inDocArea) {
		uint backX = _sprites->getCursor(225).getWidth() + 10; //Right to quit button

		_fontManager->setCurrentFont(0);
		_fontManager->setTransparentBackground(true);
		_fontManager->setSpaceWidth(0);
		_fontManager->setCharSpacing(1);
		uint categoryHalfWidth = _fontManager->getStrWidth(_categoryTitle) / 2;
		unsigned nextX = 320 + categoryHalfWidth + 20;
		unsigned prevX = 320 - categoryHalfWidth - 20 - _sprites->getCursor(76).getWidth();

		boxes.setupBox(3, allRecordsX, 480 - _sprites->getCursor(19).getHeight(),
		               allRecordsX + _sprites->getCursor(19).getWidth(), 480);
		boxes.setupBox(1, backX, 480 - _sprites->getCursor(227).getHeight(),
		               backX + _sprites->getCursor(227).getWidth(), 480);
		boxes.setupBox(9, 320 - categoryHalfWidth - 5, 480 - _sprites->getCursor(227).getHeight(),
		               320 + categoryHalfWidth + 5, 480);
		boxes.setupBox(4, nextX, 476 - _sprites->getCursor(72).getHeight(),
		               nextX + _sprites->getCursor(72).getWidth(), 476);
		boxes.setupBox(5, prevX, 476 - _sprites->getCursor(76).getHeight(),
		               prevX + _sprites->getCursor(76).getWidth(), 476);
		// Quit button
		boxes.setupBox(6, 0, 480 - _sprites->getCursor(225).getHeight(),
		               _sprites->getCursor(225).getWidth(), 480);
		// Map
		boxes.setupBox(8, 403, 305, 622, 428);
		if (_currentInTimeline) {
			for (uint box_id = 0; box_id < ARRAYSIZE(kTimelineEntries); box_id++) {
				boxes.setupBox(10 + box_id, kTimelineEntries[box_id].x, kTimelineEntries[box_id].y,
				               kTimelineEntries[box_id].x + 30, kTimelineEntries[box_id].y + 15, kTimelineEntries[box_id].year);
			}
		}
	} else {
		uint quitInGameX = 640 - _sprites->getCursor(105).getWidth();
		boxes.setupBox(1, quitInGameX, 480 - _sprites->getCursor(105).getHeight(),
		               quitInGameX + _sprites->getCursor(105).getWidth(), 480);
	}
	boxes.setupBox(2, hyperlinksX, 480 - _sprites->getCursor(242).getHeight(),
	               hyperlinksX + _sprites->getCursor(242).getWidth(), 480);
}

void Versailles_Documentation::drawRecordBoxes(Graphics::ManagedSurface &surface, bool inDocArea,
        MouseBoxes &boxes) {
	if (_visitTrace.size()) {
		surface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(0), _sprites->getKeyColor(105));
	}
	if (inDocArea) {
		surface.transBlitFrom(_sprites->getSurface(19), boxes.getBoxOrigin(3), _sprites->getKeyColor(19));
		surface.transBlitFrom(_sprites->getSurface(227), boxes.getBoxOrigin(1), _sprites->getKeyColor(227));

		surface.fillRect(boxes.getBoxRect(9), 243);

		_fontManager->setCurrentFont(0);
		_fontManager->setTransparentBackground(true);
		_fontManager->setSpaceWidth(0);
		_fontManager->setCharSpacing(1);
		_fontManager->setForeColor(240);
		Common::Point catPos = boxes.getBoxOrigin(9);
		catPos += Common::Point(5, 3);
		_fontManager->displayStr(catPos.x, catPos.y, _categoryTitle);

		if (_currentRecord == _categoryEndRecord) {
			surface.transBlitFrom(_sprites->getSurface(75), boxes.getBoxOrigin(4), _sprites->getKeyColor(75));
		} else {
			surface.transBlitFrom(_sprites->getSurface(72), boxes.getBoxOrigin(4), _sprites->getKeyColor(72));
		}
		if (_currentRecord == _categoryStartRecord) {
			surface.transBlitFrom(_sprites->getSurface(77), boxes.getBoxOrigin(5), _sprites->getKeyColor(77));
		} else {
			surface.transBlitFrom(_sprites->getSurface(76), boxes.getBoxOrigin(5), _sprites->getKeyColor(76));
		}
		surface.transBlitFrom(_sprites->getSurface(225), boxes.getBoxOrigin(6), _sprites->getKeyColor(225));
	} else {
		surface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(1), _sprites->getKeyColor(105));
	}
	if (_currentLinks.size()) {
		surface.transBlitFrom(_sprites->getSurface(242), boxes.getBoxOrigin(2), _sprites->getKeyColor(242));
	} else {
		surface.transBlitFrom(_sprites->getSurface(244), boxes.getBoxOrigin(2), _sprites->getKeyColor(244));
	}
}

uint Versailles_Documentation::handlePopupMenu(const Graphics::ManagedSurface
        &originalSurface,
        const Common::Point &anchor, bool rightAligned, uint itemHeight,
        const Common::StringArray &items) {

	uint maxTextWidth = 0;

	_fontManager->setCurrentFont(4);
	_fontManager->setTransparentBackground(true);
	_fontManager->setCharSpacing(1);

	for (Common::StringArray::const_iterator it = items.begin(); it != items.end(); it++) {
		uint width = _fontManager->getStrWidth(*it);
		if (width > maxTextWidth) {
			maxTextWidth = width;
		}
	}

	uint width = maxTextWidth + 2 * kPopupMenuMargin;
	uint height = itemHeight * items.size() + 2 * kPopupMenuMargin;

	uint hiddenItems = 0;
	int top = anchor.y - height;
	while (top < 0) {
		hiddenItems++;
		top += itemHeight;
	}
	unsigned shownItems = items.size() - hiddenItems;

	Common::Rect popupRect;
	if (rightAligned) {
		popupRect = Common::Rect(anchor.x - width, top, anchor.x, anchor.y);
	} else {
		popupRect = Common::Rect(anchor.x, top, anchor.x + width, anchor.y);
	}

	Graphics::ManagedSurface surface;
	surface.copyFrom(originalSurface);

	MouseBoxes boxes(shownItems);
	for (uint i = 0; i < shownItems; i++) {
		boxes.setupBox(i, popupRect.left + kPopupMenuMargin,
		               popupRect.top + kPopupMenuMargin + i * itemHeight,
		               popupRect.right - kPopupMenuMargin,
		               popupRect.top + kPopupMenuMargin + (i + 1) * itemHeight);
	}

	_fontManager->setSurface(&surface);

	bool fullRedraw = true;
	bool redraw = true;
	uint hoveredBox = uint(-1);
	uint action = uint(-1);
	uint lastShownItem = items.size() - 1;
	uint firstShownItem = lastShownItem - shownItems + 1;

	uint slowScrollNextEvent = g_system->getMillis() + 250;

	Common::Point mouse;

	while (action == uint(-1)) {
		if (redraw) {
			if (fullRedraw) {
				surface.fillRect(popupRect, 247);
				fullRedraw = false;
			}
			for (uint i = 0; i < shownItems; i++) {
				if (i == 0 && firstShownItem != 0) {
					// There are items before the first one: display an arrow
					surface.transBlitFrom(_sprites->getSurface(162),
					                      Common::Point(popupRect.left + kPopupMenuMargin,
					                                    popupRect.top + kPopupMenuMargin + i * itemHeight + 3),
					                      _sprites->getKeyColor(162));
				} else if (i == shownItems - 1 && lastShownItem != items.size() - 1) {
					// There are items after the last one: display an arrow
					surface.transBlitFrom(_sprites->getSurface(185),
					                      Common::Point(popupRect.left + kPopupMenuMargin,
					                                    popupRect.top + kPopupMenuMargin + i * itemHeight + 3),
					                      _sprites->getKeyColor(185));
				} else {
					// Display the item text
					_fontManager->setForeColor(i == hoveredBox ? 241 : 243);
					_fontManager->displayStr(popupRect.left + kPopupMenuMargin,
					                         popupRect.top + kPopupMenuMargin + i * itemHeight + 3, items[firstShownItem + i]);
				}
			}
			g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
			redraw = false;
		}
		g_system->updateScreen();
		g_system->delayMillis(10);

		if (_engine->pollEvents()) {
			if (_engine->shouldAbort()) {
				// Fake the quit
				break;
			}
			mouse = _engine->getMousePos();

			uint newHovered = uint(-1);
			for (uint i = 0; i < shownItems; i++) {
				if (boxes.hitTest(i, mouse)) {
					newHovered = i;
					break;
				}
			}

			if (newHovered != hoveredBox) {
				hoveredBox = newHovered;
				redraw = true;
			}
		}

		DragStatus dragStatus = _engine->getDragStatus();

		if (hoveredBox == uint(-1)) {
			if (dragStatus == kDragStatus_Pressed) {
				break;
			} else {
				continue;
			}
		}

		// From there we only act if there is something hovered
		if (hoveredBox == 0 && firstShownItem > 0) {
			// Scroll up fast
			firstShownItem--;
			lastShownItem--;
			slowScrollNextEvent = g_system->getMillis() + 250;
			fullRedraw = true;
			redraw = true;
		} else if (hoveredBox == 1 && firstShownItem > 0) {
			// Scroll up slow
			if (g_system->getMillis() > slowScrollNextEvent) {
				firstShownItem--;
				lastShownItem--;
				slowScrollNextEvent = g_system->getMillis() + 250;
				fullRedraw = true;
				redraw = true;
			}
		} else if (hoveredBox == shownItems - 2 && lastShownItem < items.size() - 1) {
			// Scroll down slow
			if (g_system->getMillis() > slowScrollNextEvent) {
				firstShownItem++;
				lastShownItem++;
				slowScrollNextEvent = g_system->getMillis() + 250;
				fullRedraw = true;
				redraw = true;
			}
		} else if (hoveredBox == shownItems - 1 && lastShownItem < items.size() - 1) {
			// Scroll down fast
			firstShownItem++;
			lastShownItem++;
			slowScrollNextEvent = g_system->getMillis() + 250;
			fullRedraw = true;
			redraw = true;
		} else if (dragStatus == kDragStatus_Finished) {
			action = hoveredBox + firstShownItem;
			continue;
		}
	}

	// Restore original surface
	g_system->copyRectToScreen(originalSurface.getPixels(), originalSurface.pitch, 0, 0,
	                           originalSurface.w, originalSurface.h);
	g_system->updateScreen();

	_engine->waitMouseRelease();

	return action;
}

/* Below is documentation files parsing */

char *Versailles_Documentation::getDocPartAddress(char *start, char *end, const char *patterns[]) {
	if (!start) {
		return nullptr;
	}
	char *foundPos = nullptr;
	const char *pattern;
	uint patternLen = uint(-1);
	for (const char **patternP = patterns; *patternP && !foundPos; patternP++) {
		pattern = *patternP;
		patternLen = strlen(pattern);
		/*debug("Matching %.10s", pattern);*/
		for (char *p = start; p < end - patternLen - 1; p++) {
			/*if (p == start || *p == '\r' || *p == '\0') {
			    debug("Line %.10s", p == start ? start : p+1);
			}*/
			if (p == start && !strncmp(p, pattern, patternLen)) {
				foundPos = p;
				break;
			} else if ((*p == '\r' || *p == '\0') && !strncmp(p + 1, pattern, patternLen)) {
				foundPos = p + 1;
				break;
			}
		}
	}
	if (!foundPos) {
		return nullptr;
	}
	/*debug("Matched %.10s", foundPos);*/
	foundPos += patternLen;
	char *eol = foundPos;
	for (; *eol != '\r' && *eol != '\0'; eol++) {}
	*eol = '\0';
	return foundPos;
}

static bool hasEqualInLine(const char *text, const char *end) {
	for (; text < end && *text && *text != '\r' && *text != '='; text++) { }
	return text < end && *text == '=';
}

static const char *nextLine(const char *text, const char *end) {
	for (; text < end && *text && *text != '\r'; text++) { }
	return text < end ? text + 1 : end;
}

const char *Versailles_Documentation::getDocTextAddress(char *start, char *end) {
	if (!start) {
		return nullptr;
	}
	const char *foundPos = nullptr;
	const char *p = start;
	while (p < end) {
		if (hasEqualInLine(p, end)) {
			p = nextLine(p, end);
			if (p < end && !hasEqualInLine(p, end)) {
				// Only return the text that is after the last =
				foundPos = p;
			}
		} else {
			p = nextLine(p, end);
		}
	}
	return foundPos;
}

const char *Versailles_Documentation::getRecordCaption(char *start, char *end) {
	const char *patterns[] = { "LEGENDE=", "LEGENDE =", nullptr };
	const char *ret = getDocPartAddress(start, end, patterns);
	return ret;
}

const char *Versailles_Documentation::getRecordTitle(char *start, char *end) {
	const char *patterns[] = { "TITRE=", "TITRE =", nullptr };
	const char *ret = getDocPartAddress(start, end, patterns);
	return ret;
}

const char *Versailles_Documentation::getRecordSubtitle(char *start, char *end) {
	const char *patterns[] = { "SOUS-TITRE=", "SOUS_TITRE=", "SOUS-TITRE =", "SOUS_TITRE =", "SOUS TITRE=", nullptr };
	char *ret = getDocPartAddress(start, end, patterns);
	if (!ret) {
		return nullptr;
	}

	uint ln = strlen(ret);
	char *p = ret + ln + 1; // Got to end of line and check next line
	for (; p < end && *p && *p != '\r' && *p != '=' ; p++) { }
	if (*p == '=') {
		// Next line has a =, so it's not multiline
		return ret;
	}

	if (*p == '\r') {
		*p = '\0';
	}
	ret[ln] = '\r';

	return ret;
}

void Versailles_Documentation::getRecordHyperlinks(char *start, char *end,
        Common::StringArray &hyperlinks) {
	const char *const hyperlinksPatterns[] = { "SAVOIR-PLUS 1=", "SAVOIR-PLUS 2=", "SAVOIR-PLUS 3=" };

	hyperlinks.clear();
	for (uint hyperlinkId = 0; hyperlinkId < ARRAYSIZE(hyperlinksPatterns); hyperlinkId++) {
		const char *patterns[] = { hyperlinksPatterns[hyperlinkId], nullptr };
		const char *ret = getDocPartAddress(start, end, patterns);
		if (ret) {
			hyperlinks.push_back(ret);
		}
	}
}

Common::String Versailles_Documentation::getRecordTitle(const Common::String &record) {
	Common::HashMap<Common::String, RecordInfo>::iterator it = _records.find(record);
	if (it == _records.end()) {
		return "";
	}

	const RecordInfo &recordInfo = it->_value;
	Common::File allDocsFile;

	if (!allDocsFile.open(_allDocsFileName)) {
		error("Can't open %s", _allDocsFileName.c_str());
	}
	allDocsFile.seek(recordInfo.position);

	char *recordData = new char[recordInfo.size + 1];
	allDocsFile.read(recordData, recordInfo.size);
	recordData[recordInfo.size] = '\0';
	char *recordDataEnd = recordData + recordInfo.size + 1;

	Common::String title = getRecordTitle(recordData, recordDataEnd);

	delete[] recordData;

	return title;
}

Common::String Versailles_Documentation::getRecordData(const Common::String &record,
        Common::String &title, Common::String &subtitle, Common::String &caption,
        Common::StringArray &hyperlinks) {
	Common::HashMap<Common::String, RecordInfo>::iterator it = _records.find(record);
	if (it == _records.end()) {
		warning("Can't find %s record data", record.c_str());
		return "";
	}

	const RecordInfo &recordInfo = it->_value;
	Common::File allDocsFile;

	if (!allDocsFile.open(_allDocsFileName)) {
		error("Can't open %s", _allDocsFileName.c_str());
	}
	allDocsFile.seek(recordInfo.position);

	char *recordData = new char[recordInfo.size + 1];
	allDocsFile.read(recordData, recordInfo.size);
	recordData[recordInfo.size] = '\0';
	char *recordDataEnd = recordData + recordInfo.size + 1;

	const char *titleP = getRecordTitle(recordData, recordDataEnd);
	/*debug("Title: %s", titleP);*/
	title = titleP ? titleP : "";
	const char *subtitleP = getRecordSubtitle(recordData, recordDataEnd);
	/*debug("SubTitle: %s", subtitleP);*/
	subtitle = subtitleP ? subtitleP : "";
	const char *captionP = getRecordCaption(recordData, recordDataEnd);
	/*debug("Caption: %s", captionP);*/
	caption = captionP ? captionP : "";
	getRecordHyperlinks(recordData, recordDataEnd, hyperlinks);

	Common::String text(getDocTextAddress(recordData, recordDataEnd));

	delete[] recordData;

	return text;
}

void Versailles_Documentation::convertHyperlinks(const Common::StringArray &hyperlinks,
        Common::Array<LinkInfo> &links) {
	for (Common::StringArray::const_iterator it = hyperlinks.begin(); it != hyperlinks.end(); it++) {
		LinkInfo link;
		link.record = *it;
		link.record.toUppercase();
		link.title = getRecordTitle(link.record);
		links.push_back(link);
	}
}

void Versailles_Documentation::loadLinksFile() {
	if (_linksData) {
		return;
	}

	Common::File linksFile;
	if (!linksFile.open(_linksDocsFileName)) {
		error("Can't open links file: %s", _linksDocsFileName.c_str());
	}

	_linksSize = linksFile.size();
	_linksData = new char[_linksSize + 1];

	linksFile.read(_linksData, _linksSize);
	_linksData[_linksSize] = '\0';
}

void Versailles_Documentation::getLinks(const Common::String &record,
                                        Common::Array<LinkInfo> &links) {
	loadLinksFile();

	links.clear();

	Common::String pattern = "\r";
	pattern += record;

	const char *recordStart = strstr(_linksData, pattern.c_str());
	if (!recordStart) {
		return;
	}

	const char *p = recordStart + pattern.size(); // Go beyond the record name
	for (; *p != '\r' && *p != '\0'; p++) { } // Goto next line
	if (!*p) {
		return;
	}
	p++;

	bool finished = false;
	while (!finished) {
		if (!scumm_strnicmp(p, "REM=", 4)) {
			// Comment: goto next line
			for (; *p != '\r' && *p != '\0'; p++) { }
		} else if (!scumm_strnicmp(p, "LIEN=", 5)) {
			// Link: read it
			const char *linkStart = p + 5;
			const char *linkEnd = linkStart;
			for (; *linkEnd != '\r' && *linkEnd != ' ' && *linkEnd != '\0'; linkEnd++) { }
			LinkInfo link;
			link.record = Common::String(linkStart, linkEnd);
			link.record.toUppercase();
			link.title = getRecordTitle(link.record);
			links.push_back(link);
			// Advance to end of link and finish the line
			p = linkEnd;
			for (; *p != '\r' && *p != '\0'; p++) { }
			//debug("Link %s/%s", link.record.c_str(), link.title.c_str());
		} else {
			// Something else: we expect a blank line to continue, else we are on a new record
			for (; *p != '\r' && *p != '\0'; p++) {
				if (*p != ' ' && *p != '\t' && *p != '\n') {
					finished = true;
					break;
				}
			}
		}
		if (*p == '\0') {
			break;
		}
		p++;
	}
}

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