/* 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 "gob/gob.h"
#include "gob/global.h"
#include "gob/util.h"
#include "gob/dataio.h"
#include "gob/surface.h"
#include "gob/draw.h"
#include "gob/video.h"
#include "gob/anifile.h"
#include "gob/aniobject.h"

#include "gob/pregob/txtfile.h"
#include "gob/pregob/gctfile.h"

#include "gob/pregob/onceupon/onceupon.h"
#include "gob/pregob/onceupon/palettes.h"
#include "gob/pregob/onceupon/title.h"
#include "gob/pregob/onceupon/parents.h"
#include "gob/pregob/onceupon/chargenchild.h"

static const uint kLanguageCount = 5;

static const uint kCopyProtectionHelpStringCount = 3;

static const char *kCopyProtectionHelpStrings[Gob::OnceUpon::OnceUpon::kLanguageCount][kCopyProtectionHelpStringCount] = {
	{ // French
		"Consulte le livret des animaux, rep\212re la",
		"page correspondant \205 la couleur de l\'\202cran",
		"et clique le symbole associ\202 \205 l\'animal affich\202.",
	},
	{ // German
		"Suche im Tieralbum die Seite, die der Farbe auf",
		"dem Bildschirm entspricht und klicke auf das",
		"Tiersymbol.",
	},
	{ // English
		"Consult the book of animals, find the page",
		"corresponding to the colour of screen and click",
		"the symbol associated with the animal displayed.",
	},
	{ // Spanish
		"Consulta el libro de los animales, localiza la ",
		"p\240gina que corresponde al color de la pantalla.",
		"Cliquea el s\241mbolo asociado al animal que aparece.",
	},
	{ // Italian
		"Guarda il libretto degli animali, trova la",
		"pagina che corrisponde al colore dello schermo,",
		"clicca il simbolo associato all\'animale presentato",
	}
};

static const char *kCopyProtectionWrongStrings[Gob::OnceUpon::OnceUpon::kLanguageCount] = {
	"Tu t\'es tromp\202, dommage...", // French
	"Schade, du hast dich geirrt."  , // German
	"You are wrong, what a pity!"   , // English
	"Te equivocas, l\240stima..."   , // Spanish
	"Sei Sbagliato, peccato..."       // Italian
};

static const uint kCopyProtectionShapeCount = 5;

static const int16 kCopyProtectionShapeCoords[kCopyProtectionShapeCount][6] = {
	{  0,  51,  26,  75,  60, 154},
	{ 28,  51,  58,  81,  96, 151},
	{ 60,  51,  94,  79, 136, 152},
	{ 96,  51, 136,  71, 180, 155},
	{140,  51, 170,  77, 228, 153}
};

enum ClownAnimation {
	kClownAnimationClownCheer = 0,
	kClownAnimationClownStand = 1,
	kClownAnimationClownCry   = 6
};

// 12 seconds delay for one area full of GCT text
static const uint32 kGCTDelay = 12000;

namespace Gob {

namespace OnceUpon {

const OnceUpon::MenuButton OnceUpon::kMainMenuDifficultyButton[] = {
	{false,  29, 18,  77, 57, 0, 0, 0, 0, 0, 0, (int)kDifficultyBeginner},
	{false, 133, 18, 181, 57, 0, 0, 0, 0, 0, 0, (int)kDifficultyIntermediate},
	{false, 241, 18, 289, 57, 0, 0, 0, 0, 0, 0, (int)kDifficultyAdvanced},
};

const OnceUpon::MenuButton OnceUpon::kSectionButtons[] = {
	{false,  27, 121,  91, 179,   0, 0,   0,  0,   0,   0,  0},
	{ true,  95, 121, 159, 179,   4, 1,  56, 49, 100, 126,  2},
	{ true, 163, 121, 227, 179,  64, 1, 120, 49, 168, 126,  6},
	{ true, 231, 121, 295, 179, 128, 1, 184, 49, 236, 126, 10}
};

const OnceUpon::MenuButton OnceUpon::kIngameButtons[] = {
	{true, 108, 83, 139, 116,   0,   0,  31,  34, 108,  83, 0},
	{true, 144, 83, 175, 116,  36,   0,  67,  34, 144,  83, 1},
	{true, 180, 83, 211, 116,  72,   0, 103,  34, 180,  83, 2}
};

const OnceUpon::MenuButton OnceUpon::kAnimalNamesBack = {
	true, 19, 13, 50, 46, 36, 0, 67, 34, 19, 13, 1
};

const OnceUpon::MenuButton OnceUpon::kLanguageButtons[] = {
	{true,  43,  80,  93, 115,   0, 55,  50, 90,  43,  80, 0},
	{true, 132,  80, 182, 115,  53, 55, 103, 90, 132,  80, 1},
	{true, 234,  80, 284, 115, 106, 55, 156, 90, 234,  80, 2},
	{true,  43, 138,  93, 173, 159, 55, 209, 90,  43, 138, 3},
	{true, 132, 138, 182, 173, 212, 55, 262, 90, 132, 138, 4},
	{true, 234, 138, 284, 173, 265, 55, 315, 90, 234, 138, 2}
};

const char *OnceUpon::kSound[kSoundCount] = {
	"diamant.snd", // kSoundClick
	"cigogne.snd", // kSoundStork
	"saute.snd"    // kSoundJump
};

const OnceUpon::SectionFunc OnceUpon::kSectionFuncs[kSectionCount] = {
	&OnceUpon::sectionStork,
	&OnceUpon::sectionChapter1,
	&OnceUpon::sectionParents,
	&OnceUpon::sectionChapter2,
	&OnceUpon::sectionForest0,
	&OnceUpon::sectionChapter3,
	&OnceUpon::sectionEvilCastle,
	&OnceUpon::sectionChapter4,
	&OnceUpon::sectionForest1,
	&OnceUpon::sectionChapter5,
	&OnceUpon::sectionBossFight,
	&OnceUpon::sectionChapter6,
	&OnceUpon::sectionForest2,
	&OnceUpon::sectionChapter7,
	&OnceUpon::sectionEnd
};


OnceUpon::ScreenBackup::ScreenBackup() : palette(-1), changedCursor(false), cursorVisible(false) {
	screen = new Surface(320, 200, 1);
}

OnceUpon::ScreenBackup::~ScreenBackup() {
	delete screen;
}


OnceUpon::OnceUpon(GobEngine *vm) : PreGob(vm), _openedArchives(false),
	_jeudak(0), _lettre(0), _plettre(0), _glettre(0) {

}

OnceUpon::~OnceUpon() {
	deinit();
}

void OnceUpon::init() {
	deinit();

	// Open data files

	bool hasSTK1 = _vm->_dataIO->openArchive("stk1.stk", true);
	bool hasSTK2 = _vm->_dataIO->openArchive("stk2.stk", true);
	bool hasSTK3 = _vm->_dataIO->openArchive("stk3.stk", true);

	if (!hasSTK1 || !hasSTK2 || !hasSTK3)
		error("OnceUpon::OnceUpon(): Failed to open archives");

	_openedArchives = true;

	// Open fonts

	_jeudak  = _vm->_draw->loadFont("jeudak.let");
	_lettre  = _vm->_draw->loadFont("lettre.let");
	_plettre = _vm->_draw->loadFont("plettre.let");
	_glettre = _vm->_draw->loadFont("glettre.let");

	if (!_jeudak || !_lettre || !_plettre || !_glettre)
		error("OnceUpon::OnceUpon(): Failed to fonts (%d, %d, %d, %d)",
		      _jeudak != 0, _lettre != 0, _plettre != 0, _glettre != 0);

	// Verify the language

	if (_vm->_global->_language == kLanguageAmerican)
		_vm->_global->_language = kLanguageBritish;

	if ((_vm->_global->_language >= kLanguageCount))
		error("We do not support the language \"%s\".\n"
		      "If you are certain that your game copy includes this language,\n"
		      "please contact the ScummVM team with details about this version.\n"
		      "Thanks", _vm->getLangDesc(_vm->_global->_language));

	// Load all our sounds and init the screen

	loadSounds(kSound, kSoundCount);
	initScreen();

	// We start with an invalid palette
	_palette = -1;

	// No quit requested at start
	_quit = false;

	// We start with no selected difficulty and at section 0
	_difficulty = kDifficultyCount;
	_section    = 0;

	// Default name
	_name = "Nemo";

	// Default character properties
	_house         = 0;
	_head          = 0;
	_colorHair     = 0;
	_colorJacket   = 0;
	_colorTrousers = 0;
}

void OnceUpon::deinit() {
	// Free sounds
	freeSounds();

	// Free fonts

	delete _jeudak;
	delete _lettre;
	delete _plettre;
	delete _glettre;

	_jeudak  = 0;
	_lettre  = 0;
	_plettre = 0;
	_glettre = 0;

	// Close archives

	if (_openedArchives) {
		_vm->_dataIO->closeArchive(true);
		_vm->_dataIO->closeArchive(true);
		_vm->_dataIO->closeArchive(true);
	}

	_openedArchives = false;
}

void OnceUpon::setGamePalette(uint palette) {
	if (palette >= kPaletteCount)
		return;

	_palette = palette;

	setPalette(kGamePalettes[palette], kPaletteSize);
}

void OnceUpon::setGameCursor() {
	Surface cursor(320, 16, 1);

	// Set the default game cursor
	_vm->_video->drawPackedSprite("icon.cmp", cursor);
	setCursor(cursor, 105, 0, 120, 15, 0, 0);
}

void OnceUpon::drawLineByLine(const Surface &src, int16 left, int16 top, int16 right, int16 bottom,
                              int16 x, int16 y) const {

	// A special way of drawing something:
	// Draw every other line "downwards", wait a bit after each line
	// Then, draw the remaining lines "upwards" and again wait a bit after each line.

	if (_vm->shouldQuit())
		return;

	const int16 width  = right  - left + 1;
	const int16 height = bottom - top  + 1;

	if ((width <= 0) || (height <= 0))
		return;

	// Draw the even lines downwards
	for (int16 i = 0; i < height; i += 2) {
		if (_vm->shouldQuit())
			return;

		_vm->_draw->_backSurface->blit(src, left, top + i, right, top + i, x, y + i);

		_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, x, y + i, x + width - 1, y + 1);
		_vm->_draw->blitInvalidated();

		_vm->_util->longDelay(1);
	}

	// Draw the odd lines upwards
	for (int16 i = (height & 1) ? height : (height - 1); i >= 0; i -= 2) {
		if (_vm->shouldQuit())
			return;

		_vm->_draw->_backSurface->blit(src, left, top + i, right, top + i, x, y + i);

		_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, x, y + i, x + width - 1, y + 1);
		_vm->_draw->blitInvalidated();

		_vm->_util->longDelay(1);
	}
}

void OnceUpon::backupScreen(ScreenBackup &backup, bool setDefaultCursor) {
	// Backup the screen and palette
	backup.screen->blit(*_vm->_draw->_backSurface);
	backup.palette = _palette;

	// Backup the cursor

	backup.cursorVisible = isCursorVisible();

	backup.changedCursor = false;
	if (setDefaultCursor) {
		backup.changedCursor = true;

		addCursor();
		setGameCursor();
	}
}

void OnceUpon::restoreScreen(ScreenBackup &backup) {
	if (_vm->shouldQuit())
		return;

	// Restore the screen
	_vm->_draw->_backSurface->blit(*backup.screen);
	_vm->_draw->forceBlit();

	// Restore the palette
	if (backup.palette >= 0)
		setGamePalette(backup.palette);

	// Restore the cursor

	if (!backup.cursorVisible)
		hideCursor();

	if (backup.changedCursor)
		removeCursor();

	backup.changedCursor = false;
}

void OnceUpon::fixTXTStrings(TXTFile &txt) const {
	TXTFile::LineArray &lines = txt.getLines();
	for (uint i = 0; i < lines.size(); i++)
		lines[i].text = fixString(lines[i].text);
}

#include "gob/pregob/onceupon/brokenstrings.h"
Common::String OnceUpon::fixString(const Common::String &str) const {
	const BrokenStringLanguage &broken = kBrokenStrings[_vm->_global->_language];

	for (uint i = 0; i < broken.count; i++) {
		if (str == broken.strings[i].wrong)
			return broken.strings[i].correct;
	}

	return str;
}

enum ClownAnimation {
	kClownAnimationStand = 0,
	kClownAnimationCheer = 1,
	kClownAnimationCry   = 2
};

const PreGob::AnimProperties OnceUpon::kClownAnimations[] = {
	{ 1, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 0, 0, ANIObject::kModeOnce      , true, false, false, 0, 0},
	{ 6, 0, ANIObject::kModeOnce      , true, false, false, 0, 0}
};

enum CopyProtectionState {
	kCPStateSetup,     // Set up the screen
	kCPStateWaitUser,  // Waiting for the user to pick a shape
	kCPStateWaitClown, // Waiting for the clown animation to finish
	kCPStateFinish     // Finishing
};

bool OnceUpon::doCopyProtection(const uint8 colors[7], const uint8 shapes[7 * 20], const uint8 obfuscate[4]) {
	fadeOut();
	setPalette(kCopyProtectionPalette, kPaletteSize);

	// Load the copy protection sprites
	Surface sprites[2] = {Surface(320, 200, 1), Surface(320, 200, 1)};

	_vm->_video->drawPackedSprite("grille1.cmp", sprites[0]);
	_vm->_video->drawPackedSprite("grille2.cmp", sprites[1]);

	// Load the clown animation
	ANIFile ani  (_vm, "grille.ani", 320);
	ANIList anims;

	loadAnims(anims, ani, 1, &kClownAnimations[kClownAnimationStand]);

	// Set the copy protection cursor
	setCursor(sprites[1], 5, 110, 20, 134, 3, 0);

	// We start with 2 tries left, not having a correct answer and the copy protection not set up yet
	CopyProtectionState state = kCPStateSetup;

	uint8 triesLeft   =  2;
	int8  animalShape = -1;
	bool  hasCorrect  = false;

	while (!_vm->shouldQuit() && (state != kCPStateFinish)) {
		clearAnim(anims);

		// Set up the screen
		if (state == kCPStateSetup) {
			animalShape = cpSetup(colors, shapes, obfuscate, sprites);

			setAnim(*anims[0], kClownAnimations[kClownAnimationStand]);
			state = kCPStateWaitUser;
		}

		drawAnim(anims);

		// If we're waiting for the clown and he finished, evaluate if we're finished
		if (!anims[0]->isVisible() && (state == kCPStateWaitClown))
			state = (hasCorrect || (--triesLeft == 0)) ? kCPStateFinish : kCPStateSetup;

		showCursor();
		fadeIn();

		endFrame(true);

		int16 mouseX, mouseY;
		MouseButtons mouseButtons;

		checkInput(mouseX, mouseY, mouseButtons);

		if (state == kCPStateWaitUser) {
			// Look if we clicked a shaped and got it right

			int8 guessedShape = -1;
			if (mouseButtons == kMouseButtonsLeft)
				guessedShape = cpFindShape(mouseX, mouseY);

			if (guessedShape >= 0) {
				hasCorrect  = guessedShape == animalShape;
				animalShape = -1;

				setAnim(*anims[0], kClownAnimations[hasCorrect ? kClownAnimationCheer : kClownAnimationCry]);
				state = kCPStateWaitClown;
			}
		}
	}

	freeAnims(anims);

	fadeOut();
	hideCursor();
	clearScreen();

	// Display the "You are wrong" screen
	if (!hasCorrect)
		cpWrong();

	return hasCorrect;
}

int8 OnceUpon::cpSetup(const uint8 colors[7], const uint8 shapes[7 * 20], const uint8 obfuscate[4],
                       const Surface sprites[2]) {

	fadeOut();
	hideCursor();

	// Get a random animal and animal color
	int8 animalColor = _vm->_util->getRandom(7);
	while ((colors[animalColor] == 1) || (colors[animalColor] == 7) || (colors[animalColor] == 11))
		animalColor = _vm->_util->getRandom(7);

	int8 animal = _vm->_util->getRandom(20);

	int8 animalShape = shapes[animalColor * 20 + animal];
	if (animal < 4)
		animal = obfuscate[animal];

	// Get the position of the animal sprite
	int16 animalLeft = (animal % 4) * 80;
	int16 animalTop  = (animal / 4) * 50;

	uint8 sprite = 0;
	if (animalTop >= 200) {
		animalTop -= 200;
		sprite = 1;
	}

	int16 animalRight  = animalLeft + 80 - 1;
	int16 animalBottom = animalTop  + 50 - 1;

	// Fill with the animal color
	_vm->_draw->_backSurface->fill(colors[animalColor]);

	// Print the help line strings
	for (uint i = 0; i < kCopyProtectionHelpStringCount; i++) {
		const char * const helpString = kCopyProtectionHelpStrings[_vm->_global->_language][i];

		const int x = 160 - (strlen(helpString) * _plettre->getCharWidth()) / 2;
		const int y = i * 10 + 5;

		_plettre->drawString(helpString, x, y, 8, 0, true, *_vm->_draw->_backSurface);
	}

	// White rectangle with black border
	_vm->_draw->_backSurface->fillRect( 93, 43, 226, 134, 15);
	_vm->_draw->_backSurface->drawRect( 92, 42, 227, 135,  0);

	// Draw the animal in the animal color
	_vm->_draw->_backSurface->fillRect(120, 63, 199, 112, colors[animalColor]);
	_vm->_draw->_backSurface->blit(sprites[sprite], animalLeft, animalTop, animalRight, animalBottom, 120, 63, 0);

	// Draw the shapes
	for (uint i = 0; i < kCopyProtectionShapeCount; i++) {
		const int16 * const coords = kCopyProtectionShapeCoords[i];

		_vm->_draw->_backSurface->blit(sprites[1], coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], 0);
	}

	_vm->_draw->forceBlit();

	return animalShape;
}

int8 OnceUpon::cpFindShape(int16 x, int16 y) const {
	// Look through all shapes and check if the coordinates are inside one of them
	for (uint i = 0; i < kCopyProtectionShapeCount; i++) {
		const int16 * const coords = kCopyProtectionShapeCoords[i];

		const int16 left   = coords[4];
		const int16 top    = coords[5];
		const int16 right  = coords[4] + (coords[2] - coords[0] + 1) - 1;
		const int16 bottom = coords[5] + (coords[3] - coords[1] + 1) - 1;

		if ((x >= left) && (x <= right) && (y >= top) && (y <= bottom))
			return i;
	}

	return -1;
}

void OnceUpon::cpWrong() {
	// Display the "You are wrong" string, centered

	const char * const wrongString = kCopyProtectionWrongStrings[_vm->_global->_language];
	const int          wrongX      = 160 - (strlen(wrongString) * _plettre->getCharWidth()) / 2;

	_vm->_draw->_backSurface->clear();
	_plettre->drawString(wrongString, wrongX, 100, 15, 0, true, *_vm->_draw->_backSurface);

	_vm->_draw->forceBlit();

	fadeIn();

	waitInput();

	fadeOut();
	clearScreen();
}

void OnceUpon::showIntro() {
	// Show all intro parts

	// "Loading"
	showWait(10);
	if (_vm->shouldQuit())
		return;

	// Quote about fairy tales
	showQuote();
	if (_vm->shouldQuit())
		return;

	// Once Upon A Time title
	showTitle();
	if (_vm->shouldQuit())
		return;

	// Game title screen
	showChapter(0);
	if (_vm->shouldQuit())
		return;

	// "Loading"
	showWait(17);
}

void OnceUpon::showWait(uint palette) {
	// Show the loading floppy

	fadeOut();
	clearScreen();
	setGamePalette(palette);

	Surface wait(320, 43, 1);

	_vm->_video->drawPackedSprite("wait.cmp", wait);
	_vm->_draw->_backSurface->blit(wait, 0, 0, 72, 33, 122, 84);

	_vm->_draw->forceBlit();

	fadeIn();
}

void OnceUpon::showQuote() {
	// Show the quote about fairytales

	fadeOut();
	clearScreen();
	setGamePalette(11);

	static const Font *fonts[3] = { _plettre, _glettre, _plettre };

	TXTFile *quote = loadTXT(getLocFile("gene.tx"), TXTFile::kFormatStringPositionColorFont);
	quote->draw(*_vm->_draw->_backSurface, fonts, ARRAYSIZE(fonts));
	delete quote;

	_vm->_draw->forceBlit();

	fadeIn();

	waitInput();

	fadeOut();
}

const PreGob::AnimProperties OnceUpon::kTitleAnimation = {
	8, 0, ANIObject::kModeContinuous, true, false, false, 0, 0
};

void OnceUpon::showTitle() {
	fadeOut();
	setGamePalette(10);

	Title title(_vm);
	title.play();
}

void OnceUpon::showChapter(int chapter) {
	// Display the intro text to a chapter

	fadeOut();
	clearScreen();
	setGamePalette(11);

	// Parchment background
	_vm->_video->drawPackedSprite("parch.cmp", *_vm->_draw->_backSurface);

	static const Font *fonts[3] = { _plettre, _glettre, _plettre };

	const Common::String chapterFile = getLocFile(Common::String::format("gene%d.tx", chapter));

	TXTFile *gameTitle = loadTXT(chapterFile, TXTFile::kFormatStringPositionColorFont);
	gameTitle->draw(*_vm->_draw->_backSurface, fonts, ARRAYSIZE(fonts));
	delete gameTitle;

	_vm->_draw->forceBlit();

	fadeIn();

	waitInput();

	fadeOut();
}

void OnceUpon::showByeBye() {
	fadeOut();
	hideCursor();
	clearScreen();
	setGamePalette(1);

	_plettre->drawString("Bye Bye....", 140, 80, 2, 0, true, *_vm->_draw->_backSurface);
	_vm->_draw->forceBlit();

	fadeIn();

	_vm->_util->longDelay(1000);

	fadeOut();
}

void OnceUpon::doStartMenu(const MenuButton *animalsButton, uint animalCount,
                           const MenuButton *animalButtons, const char * const *animalNames) {
	clearScreen();

	// Wait until we clicked on of the difficulty buttons and are ready to start playing
	while (!_vm->shouldQuit()) {
		MenuAction action = handleStartMenu(animalsButton);
		if (action == kMenuActionPlay)
			break;

		// If we pressed the "listen to animal names" button, handle that screen
		if (action == kMenuActionAnimals)
			handleAnimalNames(animalCount, animalButtons, animalNames);
	}
}

OnceUpon::MenuAction OnceUpon::handleStartMenu(const MenuButton *animalsButton) {
	ScreenBackup screenBackup;
	backupScreen(screenBackup, true);

	fadeOut();
	setGamePalette(17);
	drawStartMenu(animalsButton);
	showCursor();
	fadeIn();

	MenuAction action = kMenuActionNone;
	while (!_vm->shouldQuit() && (action == kMenuActionNone)) {
		endFrame(true);

		// Check user input

		int16 mouseX, mouseY;
		MouseButtons mouseButtons;

		int16 key = checkInput(mouseX, mouseY, mouseButtons);
		if (key == kKeyEscape)
			// ESC -> Quit
			return kMenuActionQuit;

		if (mouseButtons != kMouseButtonsLeft)
			continue;

		playSound(kSoundClick);

		// If we clicked on a difficulty button, show the selected difficulty and start the game
		int diff = checkButton(kMainMenuDifficultyButton, ARRAYSIZE(kMainMenuDifficultyButton), mouseX, mouseY);
		if (diff >= 0) {
			_difficulty = (Difficulty)diff;
			action      = kMenuActionPlay;

			drawStartMenu(animalsButton);
			_vm->_util->longDelay(1000);
		}

		if (animalsButton && (checkButton(animalsButton, 1, mouseX, mouseY) != -1))
			action = kMenuActionAnimals;

	}

	fadeOut();
	restoreScreen(screenBackup);

	return action;
}

OnceUpon::MenuAction OnceUpon::handleMainMenu() {
	ScreenBackup screenBackup;
	backupScreen(screenBackup, true);

	fadeOut();
	setGamePalette(17);
	drawMainMenu();
	showCursor();
	fadeIn();

	MenuAction action = kMenuActionNone;
	while (!_vm->shouldQuit() && (action == kMenuActionNone)) {
		endFrame(true);

		// Check user input

		int16 mouseX, mouseY;
		MouseButtons mouseButtons;

		int16 key = checkInput(mouseX, mouseY, mouseButtons);
		if (key == kKeyEscape)
			// ESC -> Quit
			return kMenuActionQuit;

		if (mouseButtons != kMouseButtonsLeft)
			continue;

		playSound(kSoundClick);

		// If we clicked on a difficulty button, change the current difficulty level
		int diff = checkButton(kMainMenuDifficultyButton, ARRAYSIZE(kMainMenuDifficultyButton), mouseX, mouseY);
		if ((diff >= 0) && (diff != (int)_difficulty)) {
			_difficulty = (Difficulty)diff;

			drawMainMenu();
		}

		// If we clicked on a section button, restart the game from this section
		int section = checkButton(kSectionButtons, ARRAYSIZE(kSectionButtons), mouseX, mouseY);
		if ((section >= 0) && (section <= _section)) {
			_section = section;
			action   = kMenuActionRestart;
		}

	}

	fadeOut();
	restoreScreen(screenBackup);

	return action;
}

OnceUpon::MenuAction OnceUpon::handleIngameMenu() {
	ScreenBackup screenBackup;
	backupScreen(screenBackup, true);

	drawIngameMenu();
	showCursor();

	MenuAction action = kMenuActionNone;
	while (!_vm->shouldQuit() && (action == kMenuActionNone)) {
		endFrame(true);

		// Check user input

		int16 mouseX, mouseY;
		MouseButtons mouseButtons;

		int16 key = checkInput(mouseX, mouseY, mouseButtons);
		if ((key == kKeyEscape) || (mouseButtons == kMouseButtonsRight))
			// ESC or right mouse button -> Dismiss the menu
			action = kMenuActionPlay;

		if (mouseButtons != kMouseButtonsLeft)
			continue;

		int button = checkButton(kIngameButtons, ARRAYSIZE(kIngameButtons), mouseX, mouseY);
		if      (button == 0)
			action = kMenuActionQuit;
		else if (button == 1)
			action = kMenuActionMainMenu;
		else if (button == 2)
			action = kMenuActionPlay;

	}

	clearIngameMenu(*screenBackup.screen);
	restoreScreen(screenBackup);

	return action;
}

void OnceUpon::drawStartMenu(const MenuButton *animalsButton) {
	// Draw the background
	_vm->_video->drawPackedSprite("menu2.cmp", *_vm->_draw->_backSurface);

	// Draw the "Listen to animal names" button
	if (animalsButton) {
		Surface elements(320, 38, 1);
		_vm->_video->drawPackedSprite("elemenu.cmp", elements);
		_vm->_draw->_backSurface->fillRect(animalsButton->left , animalsButton->top,
		                                   animalsButton->right, animalsButton->bottom, 0);
		drawButton(*_vm->_draw->_backSurface, elements, *animalsButton);
	}

	// Highlight the current difficulty
	drawMenuDifficulty();

	_vm->_draw->forceBlit();
}

void OnceUpon::drawMainMenu() {
	// Draw the background
	_vm->_video->drawPackedSprite("menu.cmp", *_vm->_draw->_backSurface);

	// Highlight the current difficulty
	drawMenuDifficulty();

	// Draw the section buttons
	Surface elements(320, 200, 1);
	_vm->_video->drawPackedSprite("elemenu.cmp", elements);

	for (uint i = 0; i < ARRAYSIZE(kSectionButtons); i++) {
		const MenuButton &button = kSectionButtons[i];

		if (!button.needDraw)
			continue;

		if (_section >= (int)button.id)
			drawButton(*_vm->_draw->_backSurface, elements, button);
	}

	_vm->_draw->forceBlit();
}

void OnceUpon::drawIngameMenu() {
	Surface menu(320, 34, 1);
	_vm->_video->drawPackedSprite("icon.cmp", menu);

	// Draw the menu in a special way, button by button
	for (uint i = 0; i < ARRAYSIZE(kIngameButtons); i++) {
		const MenuButton &button = kIngameButtons[i];

		drawLineByLine(menu, button.srcLeft, button.srcTop, button.srcRight, button.srcBottom,
		               button.dstX, button.dstY);
	}

	_vm->_draw->forceBlit();
	_vm->_video->retrace();
}

void OnceUpon::drawMenuDifficulty() {
	if (_difficulty == kDifficultyCount)
		return;

	TXTFile *difficulties = loadTXT(getLocFile("diffic.tx"), TXTFile::kFormatStringPositionColor);

	// Draw the difficulty name
	difficulties->draw((uint) _difficulty, *_vm->_draw->_backSurface, &_plettre, 1);

	// Draw a border around the current difficulty
	drawButtonBorder(kMainMenuDifficultyButton[_difficulty], difficulties->getLines()[_difficulty].color);

	delete difficulties;
}

void OnceUpon::clearIngameMenu(const Surface &background) {
	if (_vm->shouldQuit())
		return;

	// Find the area encompassing the whole ingame menu

	int16 left   = 0x7FFF;
	int16 top    = 0x7FFF;
	int16 right  = 0x0000;
	int16 bottom = 0x0000;

	for (uint i = 0; i < ARRAYSIZE(kIngameButtons); i++) {
		const MenuButton &button = kIngameButtons[i];

		if (!button.needDraw)
			continue;

		left   = MIN<int16>(left  , button.dstX);
		top    = MIN<int16>(top   , button.dstY);
		right  = MAX<int16>(right , button.dstX + (button.srcRight  - button.srcLeft + 1) - 1);
		bottom = MAX<int16>(bottom, button.dstY + (button.srcBottom - button.srcTop  + 1) - 1);
	}

	if ((left > right) || (top > bottom))
		return;

	// Clear it line by line
	drawLineByLine(background, left, top, right, bottom, left, top);
}

OnceUpon::MenuAction OnceUpon::doIngameMenu() {
	// Show the ingame menu
	MenuAction action = handleIngameMenu();

	if ((action == kMenuActionQuit) || _vm->shouldQuit()) {

		// User pressed the quit button, or quit ScummVM
		_quit  = true;
		action = kMenuActionQuit;

	} else if (action == kMenuActionPlay) {

		// User pressed the return to game button
		action = kMenuActionPlay;

	} else if (action == kMenuActionMainMenu) {

		// User pressed the return to main menu button
		action = handleMainMenu();
	}

	return action;
}

OnceUpon::MenuAction OnceUpon::doIngameMenu(int16 &key, MouseButtons &mouseButtons) {
	if ((key != kKeyEscape) && (mouseButtons != kMouseButtonsRight))
		return kMenuActionNone;

	key = 0;
	mouseButtons = kMouseButtonsNone;

	MenuAction action = doIngameMenu();
	if (action == kMenuActionPlay)
		action = kMenuActionNone;

	return action;
}

int OnceUpon::checkButton(const MenuButton *buttons, uint count, int16 x, int16 y, int failValue) const {
	// Look through all buttons, and return the ID of the button we're in

	for (uint i = 0; i < count; i++) {
		const MenuButton &button = buttons[i];

		if ((x >= button.left) && (x <= button.right) && (y >= button.top) && (y <= button.bottom))
			return (int)button.id;
	}

	// We're in none of these buttons, return the fail value
	return failValue;
}

void OnceUpon::drawButton(Surface &dest, const Surface &src, const MenuButton &button, int transp) const {
	dest.blit(src, button.srcLeft, button.srcTop, button.srcRight, button.srcBottom, button.dstX, button.dstY, transp);
}

void OnceUpon::drawButtons(Surface &dest, const Surface &src, const MenuButton *buttons, uint count, int transp) const {
	for (uint i = 0; i < count; i++) {
		const MenuButton &button = buttons[i];

		if (!button.needDraw)
			continue;

		drawButton(dest, src, button, transp);
	}
}

void OnceUpon::drawButtonBorder(const MenuButton &button, uint8 color) {
	_vm->_draw->_backSurface->drawRect(button.left, button.top, button.right, button.bottom, color);
	_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, button.left, button.top, button.right, button.bottom);
}

enum AnimalNamesState {
	kANStateChoose, // We're in the animal chooser
	kANStateNames,  // We're in the language chooser
	kANStateFinish  // We're finished
};

void OnceUpon::handleAnimalNames(uint count, const MenuButton *buttons, const char * const *names) {
	fadeOut();
	clearScreen();
	setGamePalette(19);

	bool cursorVisible = isCursorVisible();

	// Set the cursor
	addCursor();
	setGameCursor();

	anSetupChooser();

	int8 _animal = -1;

	AnimalNamesState state = kANStateChoose;
	while (!_vm->shouldQuit() && (state != kANStateFinish)) {
		showCursor();
		fadeIn();

		endFrame(true);

		// Check user input

		int16 mouseX, mouseY;
		MouseButtons mouseButtons;

		checkInput(mouseX, mouseY, mouseButtons);

		// If we moused over an animal button, draw a border around it
		int animal = checkButton(buttons, count, mouseX, mouseY);
		if ((state == kANStateChoose) && (animal != _animal)) {
			// Erase the old border
			if (_animal >= 0)
				drawButtonBorder(buttons[_animal], 15);

			_animal = animal;

			// Draw the new border
			if (_animal >= 0)
				drawButtonBorder(buttons[_animal], 10);
		}

		if (mouseButtons != kMouseButtonsLeft)
			continue;

		playSound(kSoundClick);

		// We clicked on a language button, play the animal name
		int language = checkButton(kLanguageButtons, ARRAYSIZE(kLanguageButtons), mouseX, mouseY);
		if ((state == kANStateNames) && (language >= 0))
			anPlayAnimalName(names[_animal], language);

		// We clicked on an animal
		if ((state == kANStateChoose) && (_animal >= 0)) {
			anSetupNames(buttons[_animal]);

			state = kANStateNames;
		}

		// If we clicked on the back button, go back
		if (checkButton(&kAnimalNamesBack, 1, mouseX, mouseY) != -1) {
			if        (state == kANStateNames) {
				anSetupChooser();

				state = kANStateChoose;
			} else if (state == kANStateChoose)
				state = kANStateFinish;
		}
	}

	fadeOut();

	// Restore the cursor
	if (!cursorVisible)
		hideCursor();
	removeCursor();
}

void OnceUpon::anSetupChooser() {
	fadeOut();

	_vm->_video->drawPackedSprite("dico.cmp", *_vm->_draw->_backSurface);

	// Draw the back button
	Surface menu(320, 34, 1);
	_vm->_video->drawPackedSprite("icon.cmp", menu);
	drawButton(*_vm->_draw->_backSurface, menu, kAnimalNamesBack);

	// "Choose an animal"
	TXTFile *choose = loadTXT(getLocFile("choisi.tx"), TXTFile::kFormatStringPosition);
	choose->draw(*_vm->_draw->_backSurface, &_plettre, 1, 14);
	delete choose;

	_vm->_draw->forceBlit();
}

void OnceUpon::anSetupNames(const MenuButton &animal) {
	fadeOut();

	Surface background(320, 200, 1);

	_vm->_video->drawPackedSprite("dico.cmp", background);

	// Draw the background and clear what we don't need
	_vm->_draw->_backSurface->blit(background);
	_vm->_draw->_backSurface->fillRect(19, 19, 302, 186, 15);

	// Draw the back button
	Surface menu(320, 34, 1);
	_vm->_video->drawPackedSprite("icon.cmp", menu);
	drawButton(*_vm->_draw->_backSurface, menu, kAnimalNamesBack);

	// Draw the animal
	drawButton(*_vm->_draw->_backSurface, background, animal);

	// Draw the language buttons
	Surface elements(320, 200, 1);
	_vm->_video->drawPackedSprite("elemenu.cmp", elements);
	drawButtons(*_vm->_draw->_backSurface, elements, kLanguageButtons, ARRAYSIZE(kLanguageButtons));

	// Draw the language names
	_plettre->drawString("Fran\207ais",  43,  70, 10, 15, true, *_vm->_draw->_backSurface);
	_plettre->drawString("Deutsch"    , 136,  70, 10, 15, true, *_vm->_draw->_backSurface);
	_plettre->drawString("English"    , 238,  70, 10, 15, true, *_vm->_draw->_backSurface);
	_plettre->drawString("Italiano"   ,  43, 128, 10, 15, true, *_vm->_draw->_backSurface);
	_plettre->drawString("Espa\244ol" , 136, 128, 10, 15, true, *_vm->_draw->_backSurface);
	_plettre->drawString("English"    , 238, 128, 10, 15, true, *_vm->_draw->_backSurface);

	_vm->_draw->forceBlit();
}

void OnceUpon::anPlayAnimalName(const Common::String &animal, uint language) {
	// Sound file to play
	Common::String soundFile = animal + "_" + kLanguageSuffixLong[language] + ".snd";

	// Get the name of the animal
	TXTFile *names = loadTXT(animal + ".anm", TXTFile::kFormatString);
	Common::String name = names->getLines()[language].text;
	delete names;

	// It should be centered on the screen
	const int nameX = 160 - (name.size() * _plettre->getCharWidth()) / 2;

	// Backup the screen surface
	Surface backup(162, 23, 1);
	backup.blit(*_vm->_draw->_backSurface, 78, 123, 239, 145, 0, 0);

	// Draw the name border
	Surface nameBorder(162, 23, 1);
	_vm->_video->drawPackedSprite("mot.cmp", nameBorder);
	_vm->_draw->_backSurface->blit(nameBorder, 0, 0, 161, 22, 78, 123);

	// Print the animal name
	_plettre->drawString(name, nameX, 129, 10, 0, true, *_vm->_draw->_backSurface);
	_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 78, 123, 239, 145);

	playSoundFile(soundFile);

	// Restore the screen
	_vm->_draw->_backSurface->blit(backup, 0, 0, 161, 22, 78, 123);
	_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 78, 123, 239, 145);
}

void OnceUpon::playGame() {
	while (!_vm->shouldQuit() && !_quit) {
		// Play a section and advance to the next section if we finished it
		if (playSection())
			_section = MIN(_section + 1, kSectionCount - 1);
	}

	// If we quit through the game and not through ScummVM, show the "Bye Bye" screen
	if (!_vm->shouldQuit())
		showByeBye();
}

bool OnceUpon::playSection() {
	if ((_section < 0) || (_section >= ARRAYSIZE(kSectionFuncs))) {
		_quit = true;
		return false;
	}

	return (this->*kSectionFuncs[_section])();
}

const PreGob::AnimProperties OnceUpon::kSectionStorkAnimations[] = {
	{ 0, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 1, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 2, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 3, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 4, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 5, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 6, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 7, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 8, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{17, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{16, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{15, 0, ANIObject::kModeContinuous, true, false, false, 0, 0}
};

enum StorkState {
	kStorkStateWaitUser,
	kStorkStateWaitBundle,
	kStorkStateFinish
};

bool OnceUpon::sectionStork() {
	fadeOut();
	hideCursor();
	setGamePalette(0);
	setGameCursor();

	const StorkParam &param = getStorkParameters();

	Surface backdrop(320, 200, 1);

	// Draw the frame
	_vm->_video->drawPackedSprite("cadre.cmp", *_vm->_draw->_backSurface);

	// Draw the backdrop
	_vm->_video->drawPackedSprite(param.backdrop, backdrop);
	_vm->_draw->_backSurface->blit(backdrop, 0, 0, 288, 175, 16, 12);

	// "Where does the stork go?"
	TXTFile *whereStork = loadTXT(getLocFile("ouva.tx"), TXTFile::kFormatStringPositionColor);
	whereStork->draw(*_vm->_draw->_backSurface, &_plettre, 1);

	// Where the stork actually goes
	GCTFile *thereStork = loadGCT(getLocFile("choix.gc"));
	thereStork->setArea(17, 18, 303, 41);

	ANIFile ani(_vm, "present.ani", 320);
	ANIList anims;

	Stork *stork = new Stork(_vm, ani);

	loadAnims(anims, ani, ARRAYSIZE(kSectionStorkAnimations), kSectionStorkAnimations);
	anims.push_back(stork);

	drawAnim(anims);

	_vm->_draw->forceBlit();

	int8 storkSoundWait = 0;

	StorkState state  = kStorkStateWaitUser;
	MenuAction action = kMenuActionNone;
	while (!_vm->shouldQuit() && (state != kStorkStateFinish)) {
		// Play the stork sound
		if (--storkSoundWait == 0)
			playSound(kSoundStork);
		if (storkSoundWait <= 0)
			storkSoundWait = 50 - _vm->_util->getRandom(30);

		// Check if the bundle landed
		if ((state == kStorkStateWaitBundle) && stork->hasBundleLanded())
			state = kStorkStateFinish;

		// Check user input

		int16 mouseX, mouseY;
		MouseButtons mouseButtons;

		int16 key = checkInput(mouseX, mouseY, mouseButtons);

		action = doIngameMenu(key, mouseButtons);
		if (action != kMenuActionNone) {
			state = kStorkStateFinish;
			break;
		}

		clearAnim(anims);

		if (mouseButtons == kMouseButtonsLeft) {
			stopSound();
			playSound(kSoundClick);

			int house = checkButton(param.houses, param.houseCount, mouseX, mouseY);
			if ((state == kStorkStateWaitUser) && (house >= 0)) {

				_house = house;

				stork->dropBundle(param.drops[house]);
				state = kStorkStateWaitBundle;

				// Remove the "Where does the stork go?" text
				int16 left, top, right, bottom;
				if (whereStork->clear(*_vm->_draw->_backSurface, left, top, right, bottom))
					_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom);

				// Print the text where the stork actually goes
				thereStork->selectLine(3, house); // The house
				thereStork->selectLine(4, house); // The house's inhabitants
				if (thereStork->draw(*_vm->_draw->_backSurface, 2, *_plettre, 10, left, top, right, bottom))
					_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom);
			}
		}

		drawAnim(anims);
		showCursor();
		fadeIn();

		endFrame(true);
	}

	freeAnims(anims);
	delete thereStork;
	delete whereStork;

	fadeOut();
	hideCursor();

	// Didn't complete the section
	if (action != kMenuActionNone)
		return false;

	// Move on to the character generator

	CharGenAction charGenAction = kCharGenRestart;
	while (charGenAction == kCharGenRestart)
		charGenAction = characterGenerator();

	// Did we successfully create a character?
	return charGenAction == kCharGenDone;
}

const OnceUpon::MenuButton OnceUpon::kCharGenHeadButtons[] = {
	{true, 106, 146, 152, 180,   0,  0,  47, 34, 106, 146, 0},
	{true, 155, 146, 201, 180,  49,  0,  96, 34, 155, 146, 1},
	{true, 204, 146, 250, 180,  98,  0, 145, 34, 204, 146, 2},
	{true, 253, 146, 299, 180, 147,  0, 194, 34, 253, 146, 3}
};

const OnceUpon::MenuButton OnceUpon::kCharGenHeads[] = {
	{true,   0,   0,   0,   0,  29,  4,  68, 31,  40,  51, 0},
	{true,   0,   0,   0,   0,  83,  4, 113, 31,  45,  51, 1},
	{true,   0,   0,   0,   0, 132,  4, 162, 31,  45,  51, 2},
	{true,   0,   0,   0,   0, 182,  4, 211, 31,  45,  51, 3}
};

const OnceUpon::MenuButton OnceUpon::kCharGenHairButtons[] = {
	{true, 105,  55, 124,  70, 271,  1, 289, 15, 105,  55, 0x04},
	{true, 105,  74, 124,  89, 271, 20, 289, 34, 105,  74, 0x07}
};

const OnceUpon::MenuButton OnceUpon::kCharGenJacketButtons[] = {
	{true, 105,  90, 124, 105, 271, 39, 289, 53, 105,  90, 0x06},
	{true, 105, 109, 124, 124, 271, 58, 289, 72, 105, 109, 0x02}
};

const OnceUpon::MenuButton OnceUpon::kCharGenTrousersButtons[] = {
	{true, 105, 140, 124, 155, 271, 77, 289,  91, 105, 140, 0x01},
	{true, 105, 159, 124, 174, 271, 96, 289, 110, 105, 159, 0x03}
};

const OnceUpon::MenuButton OnceUpon::kCharGenNameEntry[] = {
	{true, 0, 0, 0, 0,   0,  38,  54,  48, 140, 145, 0},
	{true, 0, 0, 0, 0, 106,  38, 159,  48, 195, 145, 0},
	{true, 0, 0, 0, 0,   0, 105,  54, 121, 140, 156, 0},
	{true, 0, 0, 0, 0, 106, 105, 159, 121, 195, 156, 0}
};

enum CharGenState {
	kCharGenStateHead     = 0, // Choose a head
	kCharGenStateHair        , // Choose hair color
	kCharGenStateJacket      , // Choose jacket color
	kCharGenStateTrousers    , // Choose trousers color
	kCharGenStateName        , // Choose name
	kCharGenStateSure        , // "Are you sure?"
	kCharGenStateStoryName   , // "We're going to tell the story of $NAME"
	kCharGenStateFinish        // Finished
};

void OnceUpon::charGenSetup(uint stage) {
	Surface choix(320, 200, 1), elchoix(320, 200, 1), paperDoll(65, 137, 1);

	_vm->_video->drawPackedSprite("choix.cmp"  , choix);
	_vm->_video->drawPackedSprite("elchoix.cmp", elchoix);

	paperDoll.blit(choix, 200, 0, 264, 136, 0, 0);

	GCTFile *text = loadGCT(getLocFile("choix.gc"));
	text->setArea(17, 18, 303, 41);
	text->setText(9, _name);

	// Background
	_vm->_video->drawPackedSprite("cadre.cmp", *_vm->_draw->_backSurface);
	_vm->_draw->_backSurface->fillRect(16, 50, 303, 187, 5);

	// Character sprite frame
	_vm->_draw->_backSurface->blit(choix, 0, 38, 159, 121, 140, 54);

	// Recolor the paper doll parts
	if (_colorHair != 0xFF)
		elchoix.recolor(0x0C, _colorHair);

	if (_colorJacket != 0xFF)
		paperDoll.recolor(0x0A, _colorJacket);

	if (_colorTrousers != 0xFF)
		paperDoll.recolor(0x09, _colorTrousers);

	_vm->_draw->_backSurface->blit(paperDoll, 32, 51);

	// Paper doll head
	if (_head != 0xFF)
		drawButton(*_vm->_draw->_backSurface, elchoix, kCharGenHeads[_head], 0);

	if (stage == kCharGenStateHead) {
		// Head buttons
		drawButtons(*_vm->_draw->_backSurface, choix, kCharGenHeadButtons, ARRAYSIZE(kCharGenHeadButtons));

		// "Choose a head"
		int16 left, top, right, bottom;
		text->draw(*_vm->_draw->_backSurface, 5, *_plettre, 10, left, top, right, bottom);

	} else if (stage == kCharGenStateHair) {
		// Hair color buttons
		drawButtons(*_vm->_draw->_backSurface, choix, kCharGenHairButtons, ARRAYSIZE(kCharGenHairButtons));

		// "What color is the hair?"
		int16 left, top, right, bottom;
		text->draw(*_vm->_draw->_backSurface, 6, *_plettre, 10, left, top, right, bottom);

	} else if (stage == kCharGenStateJacket) {
		// Jacket color buttons
		drawButtons(*_vm->_draw->_backSurface, choix, kCharGenJacketButtons, ARRAYSIZE(kCharGenJacketButtons));

		// "What color is the jacket?"
		int16 left, top, right, bottom;
		text->draw(*_vm->_draw->_backSurface, 7, *_plettre, 10, left, top, right, bottom);

	} else if (stage == kCharGenStateTrousers) {
		// Trousers color buttons
		drawButtons(*_vm->_draw->_backSurface, choix, kCharGenTrousersButtons, ARRAYSIZE(kCharGenTrousersButtons));

		// "What color are the trousers?"
		int16 left, top, right, bottom;
		text->draw(*_vm->_draw->_backSurface, 8, *_plettre, 10, left, top, right, bottom);

	} else if (stage == kCharGenStateName) {
		// Name entry field
		drawButtons(*_vm->_draw->_backSurface, choix, kCharGenNameEntry, ARRAYSIZE(kCharGenNameEntry));

		// "Enter name"
		int16 left, top, right, bottom;
		text->draw(*_vm->_draw->_backSurface, 10, *_plettre, 10, left, top, right, bottom);

		charGenDrawName();
	} else if (stage == kCharGenStateSure) {
		// Name entry field
		drawButtons(*_vm->_draw->_backSurface, choix, kCharGenNameEntry, ARRAYSIZE(kCharGenNameEntry));

		// "Are you sure?"
		TXTFile *sure = loadTXT(getLocFile("estu.tx"), TXTFile::kFormatStringPositionColor);
		sure->draw(*_vm->_draw->_backSurface, &_plettre, 1);
		delete sure;

		charGenDrawName();
	} else if (stage == kCharGenStateStoryName) {

		// "We're going to tell the story of $NAME"
		int16 left, top, right, bottom;
		text->draw(*_vm->_draw->_backSurface, 11, *_plettre, 10, left, top, right, bottom);
	}

	delete text;
}

bool OnceUpon::enterString(Common::String &name, int16 key, uint maxLength, const Font &font) {
	if (key == 0)
		return true;

	if (key == kKeyBackspace) {
		name.deleteLastChar();
		return true;
	}

	if (key == kKeySpace)
		key = ' ';

	if ((key >= ' ') && (key <= 0xFF)) {
		if (name.size() >= maxLength)
			return false;

		if (!font.hasChar(key))
			return false;

		name += (char) key;
		return true;
	}

	return false;
}

void OnceUpon::charGenDrawName() {
	_vm->_draw->_backSurface->fillRect(147, 151, 243, 166, 1);

	const int16 nameY = 151 + ((166 - 151 + 1 -       _plettre->getCharHeight())  / 2);
	const int16 nameX = 147 + ((243 - 147 + 1 - (15 * _plettre->getCharWidth ())) / 2);

	_plettre->drawString(_name, nameX, nameY, 10, 0, true, *_vm->_draw->_backSurface);

	const int16 cursorLeft   = nameX + _name.size() * _plettre->getCharWidth();
	const int16 cursorTop    = nameY;
	const int16 cursorRight  = cursorLeft + _plettre->getCharWidth() - 1;
	const int16 cursorBottom = cursorTop + _plettre->getCharHeight() - 1;

	_vm->_draw->_backSurface->fillRect(cursorLeft, cursorTop, cursorRight, cursorBottom, 10);

	_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 147, 151, 243, 166);
}

OnceUpon::CharGenAction OnceUpon::characterGenerator() {
	fadeOut();
	hideCursor();
	setGameCursor();

	showWait(1);

	_name.clear();

	_head          = 0xFF;
	_colorHair     = 0xFF;
	_colorJacket   = 0xFF;
	_colorTrousers = 0xFF;

	CharGenState state = kCharGenStateHead;
	charGenSetup(state);

	ANIFile ani(_vm, "ba.ani", 320);

	ani.recolor(0x0F, 0x0C);
	ani.recolor(0x0E, 0x0A);
	ani.recolor(0x08, 0x09);

	CharGenChild *child = new CharGenChild(ani);

	ANIList anims;
	anims.push_back(child);

	fadeOut();
	_vm->_draw->forceBlit();

	CharGenAction action = kCharGenRestart;
	while (!_vm->shouldQuit() && (state != kCharGenStateFinish)) {
		// Check user input

		int16 mouseX, mouseY;
		MouseButtons mouseButtons;

		int16 key = checkInput(mouseX, mouseY, mouseButtons);

		MenuAction menuAction = doIngameMenu(key, mouseButtons);
		if (menuAction != kMenuActionNone) {
			state  = kCharGenStateFinish;
			action = kCharGenAbort;
			break;
		}

		clearAnim(anims);

		if (state == kCharGenStateStoryName) {
			if ((mouseButtons != kMouseButtonsNone) || (key != 0)) {
				state = kCharGenStateFinish;
				action = kCharGenDone;
				break;
			}
		}

		if (state == kCharGenStateSure) {
			// Not sure => restart
			if ((key == 'N') || (key == 'n')) { // No / Nein / Non
				state = kCharGenStateFinish;
				action = kCharGenRestart;
				break;
			}

			if ((key == 'Y') || (key == 'y') || // Yes
			    (key == 'J') || (key == 'j') || // Ja
			    (key == 'S') || (key == 's') || // Si
			    (key == 'O') || (key == 'o')) { // Oui

				state = kCharGenStateStoryName;
				charGenSetup(state);
				_vm->_draw->forceBlit();
			}
		}

		if (state == kCharGenStateName) {
			if (enterString(_name, key, 14, *_plettre)) {
				_vm->_draw->_backSurface->fillRect(147, 151, 243, 166, 1);

				const int16 nameY = 151 + ((166 - 151 + 1 -       _plettre->getCharHeight())  / 2);
				const int16 nameX = 147 + ((243 - 147 + 1 - (15 * _plettre->getCharWidth ())) / 2);

				_plettre->drawString(_name, nameX, nameY, 10, 0, true, *_vm->_draw->_backSurface);

				const int16 cursorLeft   = nameX + _name.size() * _plettre->getCharWidth();
				const int16 cursorTop    = nameY;
				const int16 cursorRight  = cursorLeft + _plettre->getCharWidth() - 1;
				const int16 cursorBottom = cursorTop + _plettre->getCharHeight() - 1;

				_vm->_draw->_backSurface->fillRect(cursorLeft, cursorTop, cursorRight, cursorBottom, 10);

				_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 147, 151, 243, 166);
			}

			if ((key == kKeyReturn) && !_name.empty()) {
				_name.trim();
				_name.setChar(Util::toCP850Upper(_name[0]), 0);

				state = kCharGenStateSure;
				charGenSetup(state);
				_vm->_draw->forceBlit();
			}
		}

		if (mouseButtons == kMouseButtonsLeft) {
			stopSound();
			playSound(kSoundClick);

			int trousers = checkButton(kCharGenTrousersButtons, ARRAYSIZE(kCharGenTrousersButtons), mouseX, mouseY);
			if ((state == kCharGenStateTrousers) && (trousers >= 0)) {
				_colorTrousers = trousers;

				ani.recolor(0x09, _colorTrousers);

				state = kCharGenStateName;
				charGenSetup(state);
				_vm->_draw->forceBlit();
			}

			int jacket = checkButton(kCharGenJacketButtons, ARRAYSIZE(kCharGenJacketButtons), mouseX, mouseY);
			if ((state == kCharGenStateJacket) && (jacket >= 0)) {
				_colorJacket = jacket;

				ani.recolor(0x0A, _colorJacket);

				state = kCharGenStateTrousers;
				charGenSetup(state);
				_vm->_draw->forceBlit();
			}

			int hair = checkButton(kCharGenHairButtons, ARRAYSIZE(kCharGenHairButtons), mouseX, mouseY);
			if ((state == kCharGenStateHair) && (hair >= 0)) {
				_colorHair = hair;

				ani.recolor(0x0C, _colorHair);

				state = kCharGenStateJacket;
				charGenSetup(state);
				_vm->_draw->forceBlit();
			}

			int head = checkButton(kCharGenHeadButtons, ARRAYSIZE(kCharGenHeadButtons), mouseX, mouseY);
			if ((state == kCharGenStateHead) && (head >= 0)) {
				_head = head;

				state = kCharGenStateHair;
				charGenSetup(state);
				_vm->_draw->forceBlit();
			}
		}

		drawAnim(anims);

		// Play the child sounds
		CharGenChild::Sound childSound = child->shouldPlaySound();
		if        (childSound == CharGenChild::kSoundWalk) {
			beep(50, 10);
		} else if (childSound == CharGenChild::kSoundJump) {
			stopSound();
			playSound(kSoundJump);
		}

		showCursor();
		fadeIn();

		endFrame(true);
	}

	fadeOut();
	hideCursor();

	freeAnims(anims);

	if (_vm->shouldQuit())
		return kCharGenAbort;

	return action;
}

bool OnceUpon::sectionChapter1() {
	showChapter(1);
	return true;
}

bool OnceUpon::sectionParents() {
	fadeOut();
	setGamePalette(14);
	clearScreen();

	const Common::String seq = ((_house == 1) || (_house == 2)) ? "parents.seq" : "parents2.seq";
	const Common::String gct = getLocFile("mefait.gc");

	Parents parents(_vm, seq, gct, _name, _house, *_plettre, kGamePalettes[14], kGamePalettes[13], kPaletteSize);
	parents.play();

	warning("OnceUpon::sectionParents(): TODO: Item search");
	return true;
}

bool OnceUpon::sectionChapter2() {
	showChapter(2);
	return true;
}

bool OnceUpon::sectionForest0() {
	warning("OnceUpon::sectionForest0(): TODO");
	return true;
}

bool OnceUpon::sectionChapter3() {
	showChapter(3);
	return true;
}

bool OnceUpon::sectionEvilCastle() {
	warning("OnceUpon::sectionEvilCastle(): TODO");
	return true;
}

bool OnceUpon::sectionChapter4() {
	showChapter(4);
	return true;
}

bool OnceUpon::sectionForest1() {
	warning("OnceUpon::sectionForest1(): TODO");
	return true;
}

bool OnceUpon::sectionChapter5() {
	showChapter(5);
	return true;
}

bool OnceUpon::sectionBossFight() {
	warning("OnceUpon::sectionBossFight(): TODO");
	return true;
}

bool OnceUpon::sectionChapter6() {
	showChapter(6);
	return true;
}

bool OnceUpon::sectionForest2() {
	warning("OnceUpon::sectionForest2(): TODO");
	return true;
}

bool OnceUpon::sectionChapter7() {
	showChapter(7);
	return true;
}

const PreGob::AnimProperties OnceUpon::kSectionEndAnimations[] = {
	{ 0, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 6, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{ 9, 0, ANIObject::kModeContinuous, true, false, false, 0, 0},
	{11, 0, ANIObject::kModeContinuous, true, false, false, 0, 0}
};

bool OnceUpon::sectionEnd() {
	fadeOut();
	setGamePalette(9);

	_vm->_video->drawPackedSprite("cadre.cmp", *_vm->_draw->_backSurface);

	Surface endBackground(320, 200, 1);
	_vm->_video->drawPackedSprite("fin.cmp", endBackground);

	_vm->_draw->_backSurface->blit(endBackground, 0, 0, 288, 137, 16, 50);

	GCTFile *endText = loadGCT(getLocFile("final.gc"));
	endText->setArea(17, 18, 303, 41);
	endText->setText(1, _name);

	ANIFile ani(_vm, "fin.ani", 320);
	ANIList anims;

	loadAnims(anims, ani, ARRAYSIZE(kSectionEndAnimations), kSectionEndAnimations);
	drawAnim(anims);

	_vm->_draw->forceBlit();

	uint32 textStartTime = 0;

	MenuAction action = kMenuActionNone;
	while (!_vm->shouldQuit() && (action == kMenuActionNone)) {
		// Check user input

		int16 mouseX, mouseY;
		MouseButtons mouseButtons;

		int16 key = checkInput(mouseX, mouseY, mouseButtons);

		action = doIngameMenu(key, mouseButtons);
		if (action != kMenuActionNone)
			break;

		clearAnim(anims);

		// Pressed a key or mouse button => Skip to next area-full of text
		if ((mouseButtons == kMouseButtonsLeft) || (key != 0))
			textStartTime = 0;

		// Draw the next area-full of text
		uint32 now = _vm->_util->getTimeKey();
		if (!endText->finished() && ((textStartTime == 0) || (now >= (textStartTime + kGCTDelay)))) {
			textStartTime = now;

			int16 left, top, right, bottom;
			if (endText->clear(*_vm->_draw->_backSurface, left, top, right, bottom))
				_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom);

			if (endText->draw(*_vm->_draw->_backSurface, 0, *_plettre, 10, left, top, right, bottom))
				_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom);
		}

		drawAnim(anims);
		fadeIn();

		endFrame(true);
	}

	freeAnims(anims);
	delete endText;

	// Restart requested
	if (action == kMenuActionRestart)
		return false;

	// Last scene. Even if we didn't explicitly request a quit, the game ends here
	_quit = true;
	return false;
}

} // End of namespace OnceUpon

} // End of namespace Gob