/* 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 "hopkins/computer.h"

#include "hopkins/font.h"
#include "hopkins/files.h"
#include "hopkins/globals.h"
#include "hopkins/graphics.h"
#include "hopkins/hopkins.h"
#include "hopkins/objects.h"

#include "common/system.h"
#include "common/file.h"
#include "common/textconsole.h"

namespace Hopkins {

ComputerManager::ComputerManager(HopkinsEngine *vm) {
	_vm = vm;

	for (int i = 0; i < 50; i++) {
		_menuText[i]._actvFl = false;
		_menuText[i]._lineSize = 0;
		memset(_menuText[i]._line, 0, 90);
	}
	Common::fill(&_inputBuf[0], &_inputBuf[200], '\0');
	_breakoutSpr = NULL;
	_textColor = 0;
	_breakoutLevel = (int16 *)NULL;
	_breakoutBrickNbr = 0;
	_breakoutScore = 0;
	_breakoutLives = 0;
	_breakoutSpeed = 0;
	_ballRightFl = false;
	_ballUpFl = false;
	_breakoutLevelNbr = 0;
	_padPositionX = 0;
	_minBreakoutMoveSpeed = 0;
	_maxBreakoutMoveSpeed = 0;
	_lastBreakoutMoveSpeed = 0;
	_lowestHiScore = 0;
}

/**
 * Sets up textual entry mode. Used by the code for Hopkins computer.
 */
void ComputerManager::setVideoMode() {
	setTextMode();
}

/**
 * Sets up Textual entry mode
 */
void ComputerManager::setTextMode() {
	_vm->_graphicsMan->clearPalette();
	_vm->_graphicsMan->clearScreen();

	_vm->_graphicsMan->_lineNbr = SCREEN_WIDTH;
	_vm->_fontMan->_font = _vm->_globals->freeMemory(_vm->_fontMan->_font);

	Common::String filename = "STFONT.SPR";
	Common::File f;
	if (!f.exists(filename))
		filename = "FONTE.SPR"; // Used by the BeOS and OS/2 versions as an alternative
	_vm->_fontMan->_font = _vm->_fileIO->loadFile(filename);
	_vm->_fontMan->_fontFixedWidth = 8;
	_vm->_fontMan->_fontFixedHeight = 8;

	_vm->_graphicsMan->loadImage("WINTEXT");
	_vm->_graphicsMan->fadeInLong();
	loadMenu();
	_vm->_events->_mouseFl = false;
}

/**
 * Clear the screen
 */
void ComputerManager::clearScreen() {
	_vm->_graphicsMan->loadImage("WINTEXT");
	_vm->_graphicsMan->fadeInLong();
}

/**
 * Sets the text mode color
 */
void ComputerManager::setTextColor(int col) {
	_textColor = col;
}

/**
 * Sets the text position.
 * @param yp		Y position
 * @param xp		X position
 * @remarks		Yes, the reverse co-ordinate pair is really like that in the original game.
 */
void ComputerManager::setTextPosition(int yp, int xp) {
	_textPosition.x = xp << 3;
	_textPosition.y = yp << 4;
}

/**
 * Show a computer in the FBI office
 * @param mode		Which computer to display
 */
void ComputerManager::showComputer(ComputerEnum mode) {
	_vm->_events->_escKeyFl = false;
	_vm->_graphicsMan->resetDirtyRects();
	setVideoMode();
	setTextColor(4);
	setTextPosition(2, 4);
	if (mode == COMPUTER_HOPKINS)
		outText(Common::String(_menuText[0]._line));
	else if (mode == COMPUTER_SAMANTHA)
		outText(Common::String(_menuText[1]._line));
	else // COMPUTER_PUBLIC
		outText(Common::String(_menuText[2]._line));

	setTextColor(1);
	if (mode == COMPUTER_PUBLIC) {
		setTextPosition(10, 8);
		outText(Common::String(_menuText[3]._line));
	}
	setTextPosition(12, 28);
	outText(Common::String(_menuText[4]._line));
	setTextPosition(14, 35);

	displayMessage(280, 224, 8);
	bool passwordMatch = false;
	if ((mode == COMPUTER_HOPKINS) && !strcmp(_inputBuf, "HOPKINS"))
		passwordMatch = true;
	else if ((mode == COMPUTER_SAMANTHA) && !strcmp(_inputBuf, "328MHZA"))
		passwordMatch = true;
	else if ((mode == COMPUTER_PUBLIC) && !strcmp(_inputBuf, "ALLFREE"))
		passwordMatch = true;

	if (passwordMatch) {
		while (!_vm->shouldQuit()) {
			_vm->_events->_escKeyFl = false;
			clearScreen();
			setTextColor(4);
			setTextPosition(2, 4);
			if (mode == COMPUTER_HOPKINS)
				outText(Common::String(_menuText[0]._line));
			else if (mode == COMPUTER_SAMANTHA)
				outText(Common::String(_menuText[1]._line));
			else if (mode == COMPUTER_PUBLIC)
				outText(Common::String(_menuText[2]._line));
			setTextColor(15);
			setTextPosition(8, 25);
			setTextColor(15);
			outText2(Common::String(_menuText[6]._line));
			setTextPosition(20, 25);
			outText2(Common::String(_menuText[7]._line));
			if (mode == COMPUTER_HOPKINS) {
				setTextPosition(10, 25);
				outText2(Common::String(_menuText[8]._line));
				setTextPosition(12, 25);
				outText2(Common::String(_menuText[9]._line));
				setTextPosition(14, 25);
				outText2(Common::String(_menuText[10]._line));
				setTextPosition(16, 25);
				outText2(Common::String(_menuText[11]._line));
			} else if (mode == COMPUTER_SAMANTHA) {
				setTextPosition(10, 25);
//				outText2(Common::String(_menuText[0x95A])); <=== CHECKME: Unexpected value! replaced by the following line, for consistancy
				outText2(Common::String(_menuText[12]._line));
				setTextPosition(12, 25);
				outText2(Common::String(_menuText[13]._line));
				setTextPosition(14, 25);
				outText2(Common::String(_menuText[14]._line));
				setTextPosition(16, 25);
				outText2(Common::String(_menuText[15]._line));
				setTextPosition(18, 25);
				outText2(Common::String(_menuText[16]._line));
			}

			bool numericFlag = false;
			char keyPressed;
			do {
				keyPressed = _vm->_events->waitKeyPress();
				if (_vm->shouldQuit())
					return;

				if ((keyPressed >= '0') && (keyPressed <= '9'))
					numericFlag = true;
			} while (!numericFlag);

			// 0 - Quit
			if (keyPressed == '0')
				break;
			// 1 - Games
			if (keyPressed == '1') {
				displayGamesSubMenu();
			} else if (mode == COMPUTER_HOPKINS) {
				clearScreen();
				setTextColor(4);
				setTextPosition(2, 4);
				outText(Common::String(_menuText[0]._line));
				setTextColor(15);
				switch (keyPressed) {
				case '2':
					readText(1);
					break;
				case '3':
					readText(2);
					break;
				case '4':
					readText(3);
					break;
				case '5':
					readText(4);
					break;
				}
			} else if (mode == COMPUTER_SAMANTHA) {
				clearScreen();
				setTextColor(4);
				setTextPosition(2, 4);
				outText(Common::String(_menuText[1]._line));
				setTextColor(15);
				switch (keyPressed) {
				case '2':
					readText(6);
					break;
				case '3':
					readText(7);
					break;
				case '4':
					readText(8);
					break;
				case '5':
					readText(9);
					break;
				case '6':
					readText(10);
					_vm->_globals->_saveData->_data[svField270] = 4;
					break;
				}
			}
		}
		_vm->_graphicsMan->clearScreen();
		_vm->_graphicsMan->updateScreen();
		restoreFBIRoom();
	} else {
		// Password doesn't match - Access Denied
		setTextColor(4);
		setTextPosition(16, 25);
		outText(Common::String(_menuText[5]._line));
		_vm->_events->refreshScreenAndEvents();
		_vm->_events->delay(1000);

		memset(_vm->_graphicsMan->_frontBuffer, 0, 307199);
		_vm->_graphicsMan->clearScreen();
		_vm->_graphicsMan->updateScreen();
		restoreFBIRoom();
		_vm->_events->mouseOff();
	}

	if (mode == COMPUTER_HOPKINS)
		_vm->_globals->_exitId = 13;
	else // Free access or Samantha
		_vm->_globals->_exitId = 14;

	_vm->_graphicsMan->resetDirtyRects();
}

static const char _englishText[] =
"% ****** FBI COMPUTER NUMBER 4985 ****** J.HOPKINS COMPUTER ******\n"
"% ****** FBI COMPUTER NUMBER 4998 ****** S.COLLINS COMPUTER ******\n"
"% ****** FBI COMPUTER NUMBER 4997 ****** ACCES FREE COMPUTER ******\n"
"% PASSWORD IS: ALLFREE\n% ENTER CURRENT PASSWORD\n"
"% ****** ACCES DENIED ******\n"
"% 1) *** GAME ***\n"
"% 0) QUIT COMPUTER\n"
"% 2) STRANGE CADAVER\n"
"% 3) STRANGE CADAVER\n"
"% 4) SENATOR FERGUSSON\n"
"% 5) DOG KILLER\n"
"% 2) SCIENTIST KIDNAPPED.\n"
"% 3) SCIENTIST KIDNAPPED (next).\n"
"% 4) SCIENTIST KIDNAPPED (next).\n"
"% 5) SCIENTIST KIDNAPPED (next).\n"
"% 6) SCIENTIST KIDNAPPED (next).\n"
"%% fin\n";

static const char _frenchText[] =
"% ****** FBI COMPUTER NUMBER 4985 ****** J.HOPKINS COMPUTER ******\n"
"% ****** FBI COMPUTER NUMBER 4998 ****** S.COLLINS COMPUTER ******\n"
"% ****** FBI COMPUTER NUMBER 4997 ****** ACCES FREE COMPUTER ******\n"
"% PASSWORD IS: ALLFREE\n"
"% ENTER CURRENT PASSWORD\n"
"% ****** ACCES DENIED ******\n"
"% 1) *** CASSE BRIQUE ***\n"
"% 0) QUITTER L'ORDINATEUR\n"
"% 2) CADAVRE SANS TETE\n"
"% 3) CADAVRE SANS TETE\n"
"% 4) AGRESSION DU SENATEUR\n"
"% 5) LES CHIENS TUEURS\n"
"% 2) DISPARITIONS DE CHERCHEURS.\n"
"% 3) DISPARITIONS (suite).\n"
"% 4) DISPARITIONS (suite).\n"
"% 5) DISPARITIONS (suite).\n"
"% 6) DISPARITIONS (suite).\n"
"%% fin\n";

static const char _spanishText[] =
"% **** ORDENADOR DEL FBI NUMERO 4985 **** ORDENADOR J.HOPKINS *****\n"
"% **** ORDENADOR DEL FBI NUMERO 4998 **** ORDENADOR S.COLLINS *****\n"
"% *** ORDENADOR DEL FBI NUMERO 4997 *** ORDENADOR DE ACCESO LIBRE ***\n"
"% LA CONTRASE\0245A ES: ALLFREE\n"
"% ESCRIBE CONTRASE\0245A ACTUAL\n"
"% **** ACCESO DENEGADO ****\n"
"% 1) *** JUEGO ***\n"
"% 0) SALIR DEL ORDENADOR\n"
"% 2) CADAVER EXTRA\0245O\n"
"% 3) CADAVER EXTRA\0245O\n"
"% 4) SENADOR FERGUSSON\n"
"% 5) MATAPERROS\n"
"% 2) CIENTIFICO SECUESTRADO.\n"
"% 3) CIENTIFICO SECUESTRADO (siguiente).\n"
"% 4) CIENTIFICO SECUESTRADO (siguiente).\n"
"% 5) CIENTIFICO SECUESTRADO (siguiente).\n"
"% 6) CIENTIFICO SECUESTRADO (siguiente).\n"
"%% fin\n";

/**
 * Load Menu data
 */
void ComputerManager::loadMenu() {
	char *ptr;
	if (_vm->_fileIO->fileExists("COMPUTAN.TXT")) {
		ptr = (char *)_vm->_fileIO->loadFile("COMPUTAN.TXT");
	} else if (_vm->_globals->_language == LANG_FR) {
		ptr = (char *)_vm->_globals->allocMemory(sizeof(_frenchText));
		strcpy(ptr, _frenchText);
	} else if (_vm->_globals->_language == LANG_SP) {
		ptr = (char *)_vm->_globals->allocMemory(sizeof(_spanishText));
		strcpy(ptr, _spanishText);
	} else {
		ptr = (char *)_vm->_globals->allocMemory(sizeof(_englishText));
		strcpy(ptr, _englishText);
	}

	char *tmpPtr = ptr;
	int lineNum = 0;
	int strPos;
	bool loopCond = false;

	do {
		if (tmpPtr[0] == '%') {
			if (tmpPtr[1] == '%') {
				loopCond = true;
				break;
			}
			_menuText[lineNum]._actvFl = 1;
			strPos = 0;
			while (strPos <= 89) {
				char curChar = tmpPtr[strPos + 2];
				if (curChar == '%' || curChar == 10)
					break;
				_menuText[lineNum]._line[strPos++] = curChar;
			}
			if (strPos <= 89) {
				_menuText[lineNum]._line[strPos] = 0;
				_menuText[lineNum]._lineSize = strPos - 1;
			}
			++lineNum;
		}
		++tmpPtr;
	} while (!loopCond);
	_vm->_globals->freeMemory((byte *)ptr);
}

void ComputerManager::displayMessage(int xp, int yp, int textIdx) {
	char curChar;

	int x1 = xp;
	int x2 = 0;

	int textIndex = 0;
	bool oldMouseFlag = _vm->_events->_mouseFl;
	_vm->_events->_mouseFl = false;

	_vm->_fontMan->displayTextVesa(xp, yp, "_", 252);
	do {
		curChar = _vm->_events->waitKeyPress();
		if (_vm->shouldQuit())
			return;

		char mappedChar = '*';

		if ((curChar == '-') || ((curChar >= '0') && (curChar <= '9')) || ((curChar >= 'A') && (curChar <= 'Z')))
			mappedChar = curChar;
		else if ((curChar >= 'a') && (curChar <= 'z'))
			mappedChar = curChar - 32;

		// BackSpace
		if (curChar == 8 && textIndex > 0) {
			_inputBuf[textIndex--] = 0;
			x1 -= _vm->_fontMan->_fontFixedWidth;
			x2 = x1 + 2 * _vm->_fontMan->_fontFixedWidth;
			_vm->_graphicsMan->copyRect(_vm->_graphicsMan->_backBuffer, x1, yp, 3 * _vm->_fontMan->_fontFixedWidth, 12, _vm->_graphicsMan->_frontBuffer, x1, yp);
			_vm->_graphicsMan->addDirtyRect(x1, yp, x2, yp + 12);
			_vm->_fontMan->displayTextVesa(x1, yp, "_", 252);
		}
		if (mappedChar != '*') {
			char newChar = mappedChar;
			_vm->_graphicsMan->copyRect(_vm->_graphicsMan->_backBuffer, x1, yp, _vm->_fontMan->_fontFixedWidth, 12, _vm->_graphicsMan->_frontBuffer, x1, yp);
			_vm->_graphicsMan->addDirtyRect(x1, yp, _vm->_fontMan->_fontFixedWidth + x1, yp + 12);
			_inputBuf[textIndex] = newChar;

			Common::String charString = Common::String::format("%c_", newChar);
			_vm->_fontMan->displayTextVesa(x1, yp, charString, 252);
			++textIndex;
			x1 += _vm->_fontMan->_fontFixedWidth;
		}
		_vm->_events->refreshScreenAndEvents();
	} while (textIndex != textIdx && curChar != 13);

	_vm->_graphicsMan->copyRect(_vm->_graphicsMan->_backBuffer, x1, yp, _vm->_fontMan->_fontFixedWidth, 12, _vm->_graphicsMan->_frontBuffer, x1, yp);
	_vm->_graphicsMan->addDirtyRect(x1, yp, _vm->_fontMan->_fontFixedWidth + x1, yp + 12);

	_vm->_events->refreshScreenAndEvents();
	_inputBuf[textIndex] = 0;
	_vm->_events->_mouseFl = oldMouseFlag;
}

/**
 * Outputs a text string
 */
void ComputerManager::outText(const Common::String &msg) {
	_vm->_fontMan->renderTextDisplay(_textPosition.x, _textPosition.y, msg, _textColor);
}

/**
 * Outputs a text string
 */
void ComputerManager::outText2(const Common::String &msg) {
	_vm->_fontMan->displayTextVesa(_textPosition.x, _textPosition.y, msg, _textColor);
}

/**
 * Restores the scene for the FBI headquarters room
 */
void ComputerManager::restoreFBIRoom() {
	_vm->_graphicsMan->fadeOutShort();

	_vm->_globals->freeMemory(_vm->_fontMan->_font);
	_vm->_fontMan->_font = _vm->_fileIO->loadFile("FONTE3.SPR");
	_vm->_fontMan->_fontFixedWidth = 12;
	_vm->_fontMan->_fontFixedHeight = 21;

	_vm->_events->_mouseFl = true;
}

/**
 * Display texts for the given menu entry
 */
void ComputerManager::readText(int idx) {
	_vm->_events->_escKeyFl = false;

	Common::String filename;
	if (_vm->_globals->_language == LANG_EN)
		filename = "THOPKAN.TXT";
	else if (_vm->_globals->_language == LANG_FR)
		filename = "THOPK.TXT";
	else if (_vm->_globals->_language == LANG_SP)
		filename = "THOPKES.TXT";

	byte *ptr = _vm->_fileIO->loadFile(filename);
	uint16 fileSize = _vm->_fileIO->fileSize(filename);
	int pos;
	for (pos = 0; pos < fileSize; pos++) {
		if (ptr[pos] == '%') {
			Common::String numStr = Common::String::format("%c%c", ptr[pos + 1], ptr[pos + 2]);
			if (idx == atol(numStr.c_str()))
				break;
		}
	}
	if (pos > fileSize - 3)
		error("Error with Hopkins computer file");

	pos += 3;
	int lineNum = 5;
	Common::String curStr = "";
	byte curChar;
	do {
		curChar = ptr[pos];
		if (curChar == 13) {
			setTextPosition(lineNum, 1);
			outText(curStr);

			++lineNum;
			_vm->_events->refreshScreenAndEvents();
			curStr = "";
		} else if (curChar != '%') {
			curStr += curChar;
		}
		++pos;
		assert(pos <= fileSize);
	} while (curChar != '%');

	_vm->_events->waitKeyPress();
	ptr = _vm->_globals->freeMemory(ptr);
}

/**
 * Display breakout when Games sub-menu is selected
 */
void ComputerManager::displayGamesSubMenu() {
	const byte *oldSpriteData = _vm->_objectsMan->_sprite[0]._spriteData;
	uint oldSpeed = _vm->_globals->_speed;

	_vm->_globals->_speed = 1;
	_vm->_events->changeMouseCursor(0);
	_breakoutSpr = NULL;
	_vm->_events->_breakoutFl = true;
	_breakoutLevel = (int16 *)NULL;
	_breakoutBrickNbr = 0;
	_breakoutScore = 0;
	_breakoutLives = 5;
	_breakoutSpeed = 1;
	_ballRightFl = false;
	_ballUpFl = false;
	_breakoutLevelNbr = 0;
	_vm->_graphicsMan->_minY = 0;
	_vm->_graphicsMan->_maxX = 320;
	_vm->_graphicsMan->_maxY = 200;
	_vm->_soundMan->loadSample(1, "SOUND37.WAV");
	_vm->_soundMan->loadSample(2, "SOUND38.WAV");
	_vm->_soundMan->loadSample(3, "SOUND39.WAV");
	_breakoutSpr = _vm->_fileIO->loadFile("CASSE.SPR");
	loadHiscore();
	setModeVGA256();

	newLevel();
	_vm->_graphicsMan->updateScreen();

	playBreakout();
	_vm->_graphicsMan->resetDirtyRects();
	_breakoutSpr = _vm->_globals->freeMemory(_breakoutSpr);
	_breakoutLevel = (int16 *)_vm->_globals->freeMemory((byte *)_breakoutLevel);
	_vm->_objectsMan->_sprite[0]._spriteData = oldSpriteData;

	_vm->_soundMan->removeSample(1);
	_vm->_soundMan->removeSample(2);
	_vm->_soundMan->removeSample(3);
	_vm->_globals->_speed = oldSpeed;
	_vm->_events->_breakoutFl = false;
	setVideoMode();
	setTextColor(15);
	clearScreen();
	_vm->_graphicsMan->_maxX = 680;
	_vm->_graphicsMan->_minY = 0;
	_vm->_graphicsMan->_maxY = 460;
}

/**
 * Load Highscore from file
 */
void ComputerManager::loadHiscore() {
	const byte *ptr = _vm->_globals->_highScoreData;

	for (int scoreIndex = 0; scoreIndex < 6; ++scoreIndex) {
		_score[scoreIndex]._name = "      ";
		_score[scoreIndex]._score = "         ";

		for (int i = 0; i < 6; ++i) {
			char nextChar = ptr[(16 * scoreIndex) + i];
			if (!nextChar)
				nextChar = ' ';
			_score[scoreIndex]._name.setChar(nextChar, i);
		}

		for (int i = 0; i < 9; ++i) {
			char nextChar = ptr[(scoreIndex * 16) + 6 + i];
			if (!nextChar)
				nextChar = '0';
			_score[scoreIndex]._score.setChar(nextChar, i);
		}
	}

	_lowestHiScore = atol(_score[5]._score.c_str());
}

/**
 * VGA 256 col
 */
void ComputerManager::setModeVGA256() {
	_vm->_graphicsMan->clearScreen();
	_vm->_graphicsMan->clearPalette();
	_vm->_graphicsMan->setScreenWidth(320);
}

/**
 * Load new level
 */
void ComputerManager::newLevel() {
	_vm->_objectsMan->removeSprite(0);
	_vm->_objectsMan->removeSprite(1);
	++_breakoutLives;
	if (_breakoutLives > 11)
		_breakoutLives = 11;
	_vm->_graphicsMan->loadVgaImage("CASSEF.PCX");
	displayLives();
	_breakoutLevel = (int16 *)_vm->_globals->freeMemory((byte *)_breakoutLevel);

	++_breakoutLevelNbr;
	Common::String file;
	Common::File f;
	while (!_vm->shouldQuit()) {
		file = Common::String::format("TAB%d.TAB", _breakoutLevelNbr);
		if (f.open(file))
			break;

		_breakoutLevelNbr = 1;
	}
	f.close();

	_breakoutLevel = (int16 *)_vm->_fileIO->loadFile(file);
	displayBricks();

	_vm->_objectsMan->addStaticSprite(_breakoutSpr, Common::Point(150, 192), 0, 13, 0, false, 0, 0);
	_vm->_objectsMan->addStaticSprite(_breakoutSpr, Common::Point(164, 187), 1, 14, 0, false, 0, 0);

	_ballPosition = Common::Point(164, 187);
	_padPositionX = 150;
	_vm->_objectsMan->animateSprite(0);
	_vm->_objectsMan->animateSprite(1);

	_vm->_events->mouseOn();
	_vm->_soundMan->playSample(3, 5);
}

/**
 * Display bricks in breakout game
 */
void ComputerManager::displayBricks() {
	_breakoutBrickNbr = 0;
	_breakoutSpeed = 1;
	int16 *level = _breakoutLevel;

	int cellLeft;
	int cellTop;
	int cellType;
	for (int levelIdx = 0; ; levelIdx += 6) {
		cellLeft = (int16)FROM_LE_16(level[levelIdx]);
		if (cellLeft == -1)
			break;
		cellTop = FROM_LE_16(level[levelIdx + 1]);
		cellType = FROM_LE_16(level[levelIdx + 4]);

		if (cellType <= 6)
			++_breakoutBrickNbr;

		switch (cellType) {
		case 1:
			_vm->_graphicsMan->fastDisplay2(_breakoutSpr, cellLeft, cellTop, 21);
			break;
		case 2:
			_vm->_graphicsMan->fastDisplay2(_breakoutSpr, cellLeft, cellTop, 22);
			break;
		case 3:
			_vm->_graphicsMan->fastDisplay2(_breakoutSpr, cellLeft, cellTop, 17);
			break;
		case 4:
			_vm->_graphicsMan->fastDisplay2(_breakoutSpr, cellLeft, cellTop, 20);
			break;
		case 5:
			_vm->_graphicsMan->fastDisplay2(_breakoutSpr, cellLeft, cellTop, 19);
			break;
		case 6:
			_vm->_graphicsMan->fastDisplay2(_breakoutSpr, cellLeft, cellTop, 18);
			break;
		case 31:
			_vm->_graphicsMan->fastDisplay2(_breakoutSpr, cellLeft, cellTop, 23);
			break;
		}
	}

	displayScore();

	// Refresh the entire screen
	_vm->_graphicsMan->addRefreshRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
	_vm->_graphicsMan->updateScreen();
}

/**
 * Display Lives in breakout game
 */
void ComputerManager::displayLives() {
	for (int i = 0, xp = 10; i <= 11; i++, xp += 7)
		_vm->_graphicsMan->fastDisplay2(_breakoutSpr, xp, 10, 15);

	for (int i = 0, xp = 10; i < _breakoutLives - 1; i++, xp += 7)
		_vm->_graphicsMan->fastDisplay2(_breakoutSpr, xp, 10, 14);

	_vm->_graphicsMan->updateScreen();
}

/**
 * Main function for breakout game
 */
void ComputerManager::playBreakout() {
	int lastBreakoutEvent = 0;
	while (!_vm->shouldQuit()) {
		while (!_vm->shouldQuit()) {
			// Set up the racket and ball
			_vm->_events->mouseOff();
			_ballPosition = Common::Point(_padPositionX + 14, 187);
			_vm->_objectsMan->setSpriteY(1, 187);
			_vm->_objectsMan->setSpriteX(1, _ballPosition.x);

			_vm->_graphicsMan->resetDirtyRects();
			_vm->_events->refreshScreenAndEvents();
			_vm->_graphicsMan->fadeInBreakout();

			// Wait for mouse press to start playing
			do {
				_padPositionX = _vm->_events->getMouseX();
				if (_vm->_events->_mousePos.x <= 4)
					_padPositionX = 5;
				if (_padPositionX > 282)
					_padPositionX = 282;
				_vm->_objectsMan->setSpriteX(0, _padPositionX);
				_vm->_objectsMan->setSpriteX(1, _padPositionX + 14);
				_vm->_objectsMan->setSpriteY(1, 187);
				_vm->_events->refreshScreenAndEvents();
			} while (!_vm->shouldQuit() && _vm->_events->getMouseButton() != 1);

			_breakoutSpeed = 1;
			_ballPosition = Common::Point(_padPositionX + 14, 187);
			_ballRightFl = (_padPositionX > 135);
			_ballUpFl = false;

			// Play loop
			do {
				_vm->_soundMan->checkSounds();

				_padPositionX = _vm->_events->getMouseX();
				if (_vm->_events->_mousePos.x <= 4)
					_padPositionX = 5;
				if (_padPositionX > 282)
					_padPositionX = 282;
				_vm->_objectsMan->setSpriteX(0, _padPositionX);
				lastBreakoutEvent = moveBall();
				_vm->_events->refreshScreenAndEvents();
			} while (!_vm->shouldQuit() && !lastBreakoutEvent);
			if (lastBreakoutEvent != 1)
				break;

			--_breakoutLives;

			if (_breakoutLives) {
				displayLives();
				if (_breakoutLives)
					continue;
			}

			_vm->_graphicsMan->fadeOutBreakout();
			_vm->_events->mouseOn();
			_vm->_objectsMan->removeSprite(0);
			_vm->_objectsMan->removeSprite(1);
			if (_breakoutScore > _lowestHiScore)
				getScoreName();
			if (displayHiscores() != 1)
				break;

			_breakoutBrickNbr = 0;
			_breakoutScore = 0;
			_breakoutLives = 4;
			_breakoutSpeed = 1;
			_ballRightFl = false;
			_ballUpFl = false;
			_breakoutLevelNbr = 0;
			loadHiscore();
			newLevel();
		}
		if (lastBreakoutEvent != 2)
			return;
		_vm->_graphicsMan->fadeOutBreakout();
		newLevel();
	}
}

/**
 * Show the high scores for the Breakout game
 * @return		The selected button index: 1 = Game, 2 = Quit
 */
int ComputerManager::displayHiscores() {
	_vm->_graphicsMan->resetDirtyRects();
	loadHiscore();
	_vm->_graphicsMan->loadVgaImage("HISCORE.PCX");
	byte *ptr = _vm->_fileIO->loadFile("ALPHA.SPR");
	_vm->_graphicsMan->setColorPercentage(252, 100, 100, 100);
	_vm->_graphicsMan->setColorPercentage(253, 100, 100, 100);
	_vm->_graphicsMan->setColorPercentage(251, 100, 100, 100);
	_vm->_graphicsMan->setColorPercentage(254, 0, 0, 0);

	int yp;
	int xp;
	// Loop for displaying the scores
	for (int scoreIndex = 0; scoreIndex <= 5; scoreIndex++) {
		yp = 19 * scoreIndex;
		yp += 46;

		// Display the characters of the name
		for (int i = 0; i < 6; i++)
			displayHiscoreLine(ptr, 9 * i + 69, yp, _score[scoreIndex]._name[i]);

		// Display the digits of the score
		for (int i = 0; i < 9; i++)
			displayHiscoreLine(ptr, 9 * i + 199, yp, _score[scoreIndex]._score[i]);
	}

	_vm->_graphicsMan->fadeInBreakout();
	_vm->_graphicsMan->resetDirtyRects();
	int buttonIndex = 0;
	do {
		_vm->_events->refreshEvents();
		xp = _vm->_events->getMouseX();
		yp = _vm->_events->getMouseY();

		if (_vm->_events->getMouseButton() == 1 && ABS(xp - 79) <= 33 && ABS(yp - 396) <= 13)
			buttonIndex = 1;
		else if (_vm->_events->getMouseButton() == 1 && ABS(xp - 583) <= 32 && ABS(yp - 396) <= 13)
			buttonIndex = 2;

		_vm->_events->refreshScreenAndEvents();
	} while (!buttonIndex && !_vm->shouldQuit());

	_vm->_events->mouseOff();
	_vm->_graphicsMan->fadeOutBreakout();
	_vm->_globals->freeMemory(ptr);
	return buttonIndex;
}

/**
 * Display a screen to enter player name in the case of a new hiscore
 */
void ComputerManager::getScoreName() {
	_vm->_graphicsMan->loadVgaImage("NAME.PCX");
	_vm->_graphicsMan->setColorPercentage(252, 100, 100, 100);
	_vm->_graphicsMan->setColorPercentage(253, 100, 100, 100);
	_vm->_graphicsMan->setColorPercentage(251, 100, 100, 100);
	_vm->_graphicsMan->setColorPercentage(254, 0, 0, 0);
	byte *ptr = _vm->_fileIO->loadFile("ALPHA.SPR");
	_vm->_graphicsMan->fadeInBreakout();
	
	// Figure out the line to put the new high score on
	int scoreLine = 0;
	while (scoreLine < 5 && _breakoutScore < atol(_score[scoreLine]._score.c_str()))
		++scoreLine;

	// If it's not the lasat line, move the lines down
	for (int line = 5; line > scoreLine; --line) {
		_score[line]._name = _score[line - 1]._name;
		_score[line]._score = _score[line - 1]._score;
	}

	// Get the name for the new high score
	for (int strPos = 0; strPos <= 4; strPos++) {
		displayHiscoreLine(ptr, 9 * strPos + 140, 78, 1);

		char curChar = toupper(_vm->_events->waitKeyPress());
		if ((curChar < '0') || (curChar > 'Z'))
			curChar = ' ';
		if ((curChar > '9') && (curChar < 'A'))
			curChar = ' ';

		_score[scoreLine]._name.setChar(curChar, strPos);
		displayHiscoreLine(ptr, 9 * strPos + 140, 78, curChar);

		for (int idx = 0; idx < 12; ++idx)
			_vm->_events->refreshScreenAndEvents();
	}

	// Set up the new score
	_score[scoreLine]._score = "         ";

	char score[16];
	sprintf(score, "%d", _breakoutScore);
	int scoreLen = 0;
	do
		++scoreLen;
	while (score[scoreLen]);

	for (int i = scoreLen - 1, scorePos = 8; i >= 0; i--) {
		_score[scoreLine]._score.setChar(score[i], scorePos--);
	}
	_vm->_graphicsMan->fadeOutBreakout();
	_vm->_globals->freeMemory(ptr);
	saveScore();
}

/**
 * Display current score
 */
void ComputerManager::displayScore() {
	Common::String scoreStr = Common::String::format("%d", _breakoutScore);
	int strSize = scoreStr.size();
	for (int i = strSize - 1, idx = 0; i >= 0; i--) {
		displayScoreChar(idx++, scoreStr[i]);
	}
}

/**
 * Display a character of the score
 */
void ComputerManager::displayScoreChar(int charPos, int charDisp) {
	int xp;
	switch (charPos) {
	case 1:
		xp = 190;
		break;
	case 2:
		xp = 180;
		break;
	case 3:
		xp = 167;
		break;
	case 4:
		xp = 157;
		break;
	case 5:
		xp = 147;
		break;
	case 9:
		xp = 134;
		break;
	default:
		xp = 200;
		break;
	}

	int idx = 3;
	if (charDisp >= '0' && charDisp <= '9')
		idx = charDisp - 45;

	_vm->_graphicsMan->fastDisplay2(_breakoutSpr, xp, 11, idx);
}

/**
 * Save Hiscore in file
 */
void ComputerManager::saveScore() {
	int scores[6];
	// Load high scores in an array
	for (int i = 0; i <= 5; i++) {
		scores[i] = atol(_score[i]._score.c_str());
		if (!scores[i])
			scores[i] = 5;
	}

	int scorePlace[6];
	// order high scores
	for (int scorePlaceIdx = 0; scorePlaceIdx <= 5; scorePlaceIdx++) {
		for(int i = 0;;i++) {
			int curScore = scores[i];
			if (curScore && scores[0] <= curScore && scores[1] <= curScore && scores[2] <= curScore && scores[3] <= curScore
				&& scores[4] <= curScore && scores[5] <= curScore) {
				scorePlace[scorePlaceIdx] = i;
				scores[i] = 0;
				break;
			}
		}
	}

	byte *ptr = _vm->_globals->_highScoreData;
	memset(ptr, 0, 99);
	for (int scorePlaceIdx = 0; scorePlaceIdx <= 5; scorePlaceIdx++) {
		int curBufPtr = 16 * scorePlaceIdx;
		for (int namePos = 0; namePos < 6; namePos++) {
			char curChar = _score[scorePlace[scorePlaceIdx]]._name[namePos];
			if (!curChar)
				curChar = ' ';
			ptr[curBufPtr + namePos] = curChar;
		};

		ptr[curBufPtr + 5] = 0;

		for (int scorePos = 0; scorePos <= 8; scorePos++) {
			char curChar = _score[scorePlace[scorePlaceIdx]]._score[scorePos];
			if (!curChar)
				curChar = '0';
			ptr[curBufPtr + 6 + scorePos] = curChar;
		};
		ptr[curBufPtr + 15] = 0;
	}
}

/**
 * Display parts of the hiscore line
 */
void ComputerManager::displayHiscoreLine(const byte *objectData, int x, int y, int curChar) {
	int idx = 36;

	if (curChar == 100)
		idx = 0;
	else if (curChar >= '0' && curChar <= '9')
		idx = curChar - '0';
	else if (curChar >= 'A' && curChar <= 'Z')
		idx = curChar - 'A' + 10;
	else if (curChar == 1)
		idx = 37;
	_vm->_graphicsMan->fastDisplay2(objectData, x, y, idx);
}

/**
 * Handle ball moves
 */
int ComputerManager::moveBall() {
	//(signed int)(6.0 * (long double)_vm->getRandomNumber( rand() / 2147483648.0) + 1;
	// TODO: Figure out random number
	int randVal = _vm->getRandomNumber(6);
	switch (_breakoutSpeed) {
	case 1:
		_minBreakoutMoveSpeed = 1;
		_maxBreakoutMoveSpeed = 1;
		break;
	case 2:
		_minBreakoutMoveSpeed = 1;
		_maxBreakoutMoveSpeed = 2;
		break;
	case 3:
		_minBreakoutMoveSpeed = 2;
		_maxBreakoutMoveSpeed = 2;
		break;
	case 4:
		_minBreakoutMoveSpeed = 3;
		_maxBreakoutMoveSpeed = 2;
		break;
	}

	int moveSpeed = _minBreakoutMoveSpeed;
	if (_lastBreakoutMoveSpeed == _minBreakoutMoveSpeed)
		moveSpeed = _maxBreakoutMoveSpeed;

	if (_ballUpFl)
		_ballPosition.y += moveSpeed;
	else
		_ballPosition.y -= moveSpeed;

	if (_ballRightFl)
		_ballPosition.x += moveSpeed;
	else
		_ballPosition.x -= moveSpeed;

	_lastBreakoutMoveSpeed = moveSpeed;
	if (_ballPosition.x <= 6) {
		_vm->_soundMan->playSample(2, 6);
		_ballPosition.x = randVal + 6;
		_ballRightFl = !_ballRightFl;
	} else if (_ballPosition.x > 307) {
		_vm->_soundMan->playSample(2, 6);
		_ballPosition.x = 307 - randVal;
		_ballRightFl = !_ballRightFl;
	}

	if (_ballPosition.y <= 6) {
		_vm->_soundMan->playSample(2, 6);
		_ballPosition.y = randVal + 7;
		_ballUpFl = !_ballUpFl;
	} else if (_ballPosition.y >= 186 && _ballPosition.y <= 194) {
		_vm->_soundMan->playSample(2, 6);
		int ballPosXRight = _ballPosition.x + 6;
		if ((_ballPosition.x > _padPositionX - 2) && (ballPosXRight < _padPositionX + 36)) {
			_ballUpFl = false;
			if (ballPosXRight <= _padPositionX + 15) {
				_ballRightFl = false;
				if (_ballPosition.x >= _padPositionX && ballPosXRight <= _padPositionX + 5)
					_ballPosition.x -= 4;
				if (_ballPosition.x >= _padPositionX + 5 && _ballPosition.x + 6 <= _padPositionX + 10)
					_ballPosition.x -= 2;
			}
			if (_ballPosition.x >= _padPositionX + 19 && _ballPosition.x + 6 <= _padPositionX + 36) {
				_ballRightFl = true;
				if (_ballPosition.x >= _padPositionX + 29)
					_ballPosition.x += 4;
				if (_ballPosition.x >= _padPositionX + 24 && _ballPosition.x + 6 <= _padPositionX + 29)
					_ballPosition.x += 2;
			}
		}
	}

	int retVal = 0;
	if (_ballPosition.y > 194)
		retVal = 1;
	checkBallCollisions();
	_vm->_objectsMan->setSpriteX(1, _ballPosition.x);
	_vm->_objectsMan->setSpriteY(1, _ballPosition.y);
	if (!_breakoutBrickNbr)
		retVal = 2;
	return retVal;
}

/**
 * Check ball collision with bricks
 */
void ComputerManager::checkBallCollisions() {
	int cellLeft;

	bool brickDestroyedFl = false;
	// TODO: Check if correct
	int randVal = _vm->getRandomNumber(6) + 1;
	int ballLeft = _ballPosition.x;
	int ballTop = _ballPosition.y;
	int ballRight = _ballPosition.x + 6;
	int ballBottom = _ballPosition.y + 6;
	int16 *level = _breakoutLevel;
	uint16 levelIdx = 0;
	do {
		cellLeft = level[levelIdx];
		int cellUp = level[levelIdx + 1];
		int cellRight = level[levelIdx + 2];
		int cellBottom = level[levelIdx + 3];
		int cellType = level[levelIdx + 4];
		if (level[levelIdx + 5] == 1 && cellLeft != -1) {
			bool collisionFl = false;
			if (ballTop <= cellBottom && ballBottom >= cellBottom) {
				if (ballLeft >= cellLeft && ballRight <= cellRight) {
					collisionFl = true;
					_ballUpFl = true;
				}
				if ((ballRight >= cellLeft) && (ballLeft <= cellLeft)) {
					collisionFl = true;
					_ballUpFl = true;
					_ballRightFl = false;
					if (cellType == 31)
						_ballPosition.x -= randVal;
				}
				if ((ballLeft <= cellRight) && (ballRight >= cellRight)) {
					collisionFl = true;
					_ballUpFl = true;
					_ballRightFl = true;
					if (cellType == 31)
						_ballPosition.x += randVal;
				}
			}
			if (ballBottom >= cellUp && ballTop <= cellUp) {
				if (ballLeft >= cellLeft && ballRight <= cellRight) {
					collisionFl = true;
					_ballUpFl = false;
				}
				if ((ballRight >= cellLeft) && (ballLeft <= cellLeft)) {
					collisionFl = true;
					_ballUpFl = false;
					_ballRightFl = false;
					if (cellType == 31)
						_ballPosition.x -= 2;
				}
				if ((ballLeft <= cellRight) && (ballRight >= cellRight)) {
					collisionFl = true;
					_ballUpFl = false;
					_ballRightFl = true;
					if (cellType == 31)
						_ballPosition.x += randVal;
				}
			}
			if ((ballTop >= cellUp) && (ballBottom <= cellBottom)) {
				if ((ballRight >= cellLeft) && (ballLeft <= cellLeft)) {
					collisionFl = true;
					_ballRightFl = false;
					if (cellType == 31)
						_ballPosition.x -= randVal;
				}
				if ((ballLeft <= cellRight) && (ballRight >= cellRight)) {
					collisionFl = true;
					_ballRightFl = true;
					if (cellType == 31)
						_ballPosition.x += randVal;
				}
			}
			if (collisionFl) {
				if (cellType == 31) {
					_vm->_soundMan->playSample(2, 6);
				} else {
					_vm->_soundMan->playSample(1, 5);
					_vm->_graphicsMan->fastDisplay2(_breakoutSpr, cellLeft, cellUp, 16);
					switch (cellType) {
					case 1:
						_breakoutScore += 10;
						break;
					case 2:
						_breakoutScore += 5;
						break;
					case 3:
						_breakoutScore += 50;
						if (_breakoutSpeed <= 1)
							_breakoutSpeed = 2;
						if (_breakoutBrickNbr <= 19)
							_breakoutSpeed = 3;
						break;
					case 4:
						_breakoutScore += 20;
						break;
					case 5:
						_breakoutScore += 30;
						if (_breakoutSpeed <= 1)
							_breakoutSpeed = 2;
						break;
					case 6:
						_breakoutScore += 40;
						break;
					}
					displayScore();
					--_breakoutBrickNbr;
					level[levelIdx + 5] = 0;
					brickDestroyedFl = true;
				}
			}
		}

		if (brickDestroyedFl)
			cellLeft = -1;
		levelIdx += 6;
	} while (cellLeft != -1);
}

} // End of namespace Hopkins