/* 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 "dreamweb/sound.h"
#include "dreamweb/dreamweb.h"

namespace DreamWeb {

struct MonitorKeyEntry {
	uint8 keyAssigned;
	char  username[12];
	char  password[12];
};

// New monitor key list
static MonitorKeyEntry monitorKeyEntries[4] = {
	{ 1, "PUBLIC",  "PUBLIC"      },
	{ 0, "RYAN",    "BLACKDRAGON" },
	{ 0, "LOUIS",   "HENDRIX"     },
	{ 0, "BECKETT", "SEPTIMUS"    }
};

void DreamWebEngine::useMon() {
	_vars._lastTrigger = 0;
	_currentFile[0] = 34;
	memset(_currentFile+1, ' ', 12);
	_currentFile[13] = 0;

	monitorKeyEntries[0].keyAssigned = 1;
	monitorKeyEntries[1].keyAssigned = 0;
	monitorKeyEntries[2].keyAssigned = 0;
	monitorKeyEntries[3].keyAssigned = 0;

	createPanel();
	showPanel();
	showIcon();
	drawFloor();
	getRidOfAll();
	loadGraphicsFile(_monitorGraphics, "G03"); // mon. graphic name
	loadPersonal();
	loadNews();
	loadCart();
	loadGraphicsFile(_monitorCharset, "C01"); // character set 2
	printOuterMon();
	initialMonCols();
	printLogo();
	workToScreen();
	turnOnPower();
	fadeUpYellows();
	fadeUpMonFirst();
	_monAdX = 76;
	_monAdY = 141;
	monMessage(1);
	hangOnCurs(120);
	monMessage(2);
	randomAccess(60);
	monMessage(3);
	hangOnCurs(100);
	printLogo();
	scrollMonitor();
	_bufferIn = 0;
	_bufferOut = 0;
	bool stop = false;
	do {
		uint16 oldMonadx = _monAdX;
		uint16 oldMonady = _monAdY;
		input();
		_monAdX = oldMonadx;
		_monAdY = oldMonady;
		stop = execCommand();
		if (_quitRequested) //TODO : Check why it crashes when put before the execcommand
			break;
	} while (!stop);
	_monitorGraphics.clear();
	_monitorCharset.clear();

	_textFile1.clear();
	_textFile2.clear();
	_textFile3.clear();

	_getBack = 1;
	_sound->playChannel1(26);
	_manIsOffScreen = 0;
	restoreAll();
	redrawMainScrn();
	workToScreenM();
}

int DreamWebEngine::findCommand(const char *const cmdList[]) {
	// Loop over all commands in the list and see if we get a match
	int cmd = 0;
	while (cmdList[cmd] != NULL) {
		const char *cmdStr = cmdList[cmd];
		const char *inputStr = _inputLine;
		// Compare the command, char by char, to see if we get a match.
		// We only care about the prefix matching, though.
		char inputChar, cmdChar;
		do {
			inputChar = *inputStr; inputStr += 2;
			cmdChar = *cmdStr++;
			if (cmdChar == 0)
				return cmd;
		} while (inputChar == cmdChar);
		++cmd;
	}
	return -1;
}

bool DreamWebEngine::execCommand() {
	static const char *const comlist[] = {
		"EXIT",
		"HELP",
		"LIST",
		"READ",
		"LOGON",
		"KEYS",
		NULL
	};

	static const char *const comlistFR[] = {
		"SORTIR",
		"AIDE",
		"LISTE",
		"LIRE",
		"CONNEXION",
		"TOUCHES", // should be CLES but it is translated as TOUCHES in the game...
		NULL
	};

	static const char *const comlistDE[] = {
		"ENDE",
		"HILF",
		"LISTE",
		"LIES",
		"ZUGRIFF",
		"DATEN",
		NULL
	};

	static const char *const comlistIT[] = {
		"ESCI",
		"AIUTO",
		"ELENCA",
		"LEGGI",
		"ACCEDI",
		"CHIAVI",
		NULL
	};

	static const char *const comlistES[] = {
		"SALIR",
		"AYUDA",
		"LISTA",
		"LEER",
		"ACCESO",
		"CLAVES",
		NULL
	};

	if (_inputLine[0] == 0) {
		// No input
		scrollMonitor();
		return false;
	}

	int cmd = findCommand(comlist);
	if (cmd == -1) {
		// This did not match an english command. Try to find a localized one.
		switch (getLanguage()) {
		case Common::FR_FRA:
			cmd = findCommand(comlistFR);
			break;
		case Common::DE_DEU:
			cmd = findCommand(comlistDE);
			break;
		case Common::IT_ITA:
			cmd = findCommand(comlistIT);
			break;
		case Common::ES_ESP:
			cmd = findCommand(comlistES);
			break;
		default:
			break;
		}
	}

	// Execute the selected command
	switch (cmd) {
	case 0:
		return true;
	case 1:
		monMessage(6);
		// An extra addition in ScummVM: available commands.
		// Since the reference to the game manual is a form of copy protection,
		// this extra text is wrapped around the common copy protection check,
		// to keep it faithful to the original, if requested.
		if (!_copyProtection) {
			switch (getLanguage()) {
			case Common::FR_FRA:
				monPrint("LES COMMANDES VALIDES SONT SORTIR, AIDE, LISTE, LIRE, CONNEXION, TOUCHES");
				break;
			case Common::DE_DEU:
				monPrint("G\232LTIGE BEFEHLE SIND ENDE, HILFE, LISTE, LIES, ZUGRIFF, DATEN");
				break;
			case Common::IT_ITA:
				monPrint("I COMANDI VALIDI SONO ESCI, AIUTO, ELENCA, LEGGI, ACCEDI, CHIAVI");
				break;
			case Common::ES_ESP:
			default:
				monPrint("VALID COMMANDS ARE EXIT, HELP, LIST, READ, LOGON, KEYS");
				break;
			}
		}
		break;
	case 2:
		dirCom();
		break;
	case 3:
		read();
		break;
	case 4:
		signOn();
		break;
	case 5:
		showKeys();
		break;
	default:
		netError();
		break;
	}
	return false;
}


void DreamWebEngine::monitorLogo() {
	if (_logoNum != _oldLogoNum) {
		_oldLogoNum = _logoNum;
		//fadeDownMon(); // FIXME: Commented out in ASM
		printLogo();
		printUnderMonitor();
		workToScreen();
		printLogo();
		//fadeUpMon(); // FIXME: Commented out in ASM
		printLogo();
		_sound->playChannel1(26);
		randomAccess(20);
	} else {
		printLogo();
	}
}

void DreamWebEngine::printLogo() {
	showFrame(_monitorGraphics, 56, 32, 0, 0);
	showCurrentFile();
}

void DreamWebEngine::input() {
	memset(_inputLine, 0, sizeof(_inputLine));
	_curPos = 0;
	printChar(_monitorCharset, _monAdX, _monAdY, '>', 0, NULL, NULL);
	multiDump(_monAdX, _monAdY, 6, 8);
	_monAdX += 6;
	_cursLocX = _monAdX;
	_cursLocY = _monAdY;
	while (true) {
		printCurs();
		waitForVSync();
		delCurs();
		readKey();
		if (_quitRequested)
			return;
		uint8 currentKey = _currentKey;
		if (currentKey == 0)
			continue;
		if (currentKey == 13)
			return;
		if (currentKey == 8) {
			if (_curPos > 0)
				delChar();
			continue;
		}
		if (_curPos == 28)
			continue;
		if ((currentKey == 32) && (_curPos == 0))
			continue;
		currentKey = makeCaps(currentKey);
		_inputLine[_curPos * 2 + 0] = currentKey;
		if (currentKey > 'Z')
			continue;
		multiGet(_mapStore + _curPos * 256, _monAdX, _monAdY, 8, 8);
		uint8 charWidth;
		printChar(_monitorCharset, _monAdX, _monAdY, currentKey, 0, &charWidth, NULL);
		_inputLine[_curPos * 2 + 1] = charWidth;
		_monAdX += charWidth;
		++_curPos;
		_cursLocX += charWidth;
	}
}

byte DreamWebEngine::makeCaps(byte c) {
	// TODO: Replace calls to this by toupper() ?
	if (c >= 'a')
		c -= 'a' - 'A'; // = 32
	return c;
}

void DreamWebEngine::delChar() {
	--_curPos;
	_inputLine[_curPos * 2] = 0;
	uint8 width = _inputLine[_curPos * 2 + 1];
	_monAdX -= width;
	_cursLocX -= width;
	uint16 offset = _curPos;
	offset = ((offset & 0x00ff) << 8) | ((offset & 0xff00) >> 8);
	multiPut(_mapStore + offset, _monAdX, _monAdY, 8, 8);
	multiDump(_monAdX, _monAdY, 8, 8);
}

void DreamWebEngine::printCurs() {
	uint16 x = _cursLocX;
	uint16 y = _cursLocY;
	uint16 height;
	if (_foreignRelease) {
		y -= 3;
		height = 11;
	} else
		height = 8;
	multiGet(_textUnder, x, y, 6, height);
	++_mainTimer;
	if ((_mainTimer & 16) == 0)
		showFrame(_monitorCharset, x, y, '/' - 32, 0);
	multiDump(x - 6, y, 12, height);
}

void DreamWebEngine::delCurs() {
	uint16 x = _cursLocX;
	uint16 y = _cursLocY;
	uint16 width = 6;
	uint16 height;
	if (_foreignRelease) {
		y -= 3;
		height = 11;
	} else
		height = 8;
	multiPut(_textUnder, x, y, width, height);
	multiDump(x, y, width, height);
}

void DreamWebEngine::scrollMonitor() {
	printLogo();
	printUnderMonitor();
	workToScreen();
	_sound->playChannel1(25);
}

void DreamWebEngine::showCurrentFile() {
	uint16 x;
	// Monitor Frame position differs between Floppy and CD version
	if (isCD())
		x = 178;
	else
		x = 199;
	const char *currentFile = _currentFile + 1;
	while (*currentFile) {
		char c = *currentFile++;
		c = modifyChar(c);
		printChar(_monitorCharset, &x, 37, c, 0, NULL, NULL);
	}
}

void DreamWebEngine::accessLightOn() {
	showFrame(_monitorGraphics, 74, 182, 8, 0);
	multiDump(74, 182, 12, 8);
}

void DreamWebEngine::accessLightOff() {
	showFrame(_monitorGraphics, 74, 182, 7, 0);
	multiDump(74, 182, 12, 8);
}

void DreamWebEngine::randomAccess(uint16 count) {
	for (uint16 i = 0; i < count; ++i) {
		waitForVSync();
		waitForVSync();
		uint16 v = _rnd.getRandomNumber(15);
		if (v < 10)
			accessLightOff();
		else
			accessLightOn();
	}
	accessLightOff();
}

void DreamWebEngine::monMessage(uint8 index) {
	assert(index > 0);
	const char *string = _textFile1._text;
	for (uint8 i = 0; i < index; ++i) {
		while (*string++ != '+') {
		}
	}
	monPrint(string);
}

void DreamWebEngine::netError() {
	monMessage(5);
	scrollMonitor();
}

void DreamWebEngine::powerLightOn() {
	showFrame(_monitorGraphics, 257+4, 182, 6, 0);
	multiDump(257+4, 182, 12, 8);
}

void DreamWebEngine::powerLightOff() {
	showFrame(_monitorGraphics, 257+4, 182, 5, 0);
	multiDump(257+4, 182, 12, 8);
}

void DreamWebEngine::lockLightOn() {
	showFrame(_monitorGraphics, 56, 182, 10, 0);
	multiDump(58, 182, 12, 8);
}

void DreamWebEngine::lockLightOff() {
	showFrame(_monitorGraphics, 56, 182, 9, 0);
	multiDump(58, 182, 12, 8);
}

void DreamWebEngine::turnOnPower() {
	for (uint i = 0; i < 3; ++i) {
		powerLightOn();
		hangOn(30);
		powerLightOff();
		hangOn(30);
	}
	powerLightOn();
}

void DreamWebEngine::printOuterMon() {
	showFrame(_monitorGraphics, 40, 32, 1, 0);
	showFrame(_monitorGraphics, 264, 32, 2, 0);
	showFrame(_monitorGraphics, 40, 12, 3, 0);
	showFrame(_monitorGraphics, 40, 164, 4, 0);
}

void DreamWebEngine::loadPersonal() {
	if (_vars._location == 0 || _vars._location == 42)
		loadTextFile(_textFile1, "T01"); // monitor file 1
	else
		loadTextFile(_textFile1, "T02"); // monitor file 2
}

void DreamWebEngine::loadNews() {
	// textfile2 holds information accessible by anyone
	if (_vars._newsItem == 0)
		loadTextFile(_textFile2, "T10"); // monitor file 10
	else if (_vars._newsItem == 1)
		loadTextFile(_textFile2, "T11"); // monitor file 11
	else if (_vars._newsItem == 2)
		loadTextFile(_textFile2, "T12"); // monitor file 12
	else
		loadTextFile(_textFile2, "T13"); // monitor file 13
}

void DreamWebEngine::loadCart() {
	byte cartridgeId = 0;
	uint16 objectIndex = findSetObject("INTF");
	uint16 cartridgeIndex = checkInside(objectIndex, 1);
	if (cartridgeIndex != kNumexobjects)
		cartridgeId = getExAd(cartridgeIndex)->objId[3] + 1;

	if (cartridgeId == 0)
		loadTextFile(_textFile3, "T20"); // monitor file 20
	else if (cartridgeId == 1)
		loadTextFile(_textFile3, "T21"); // monitor file 21
	else if (cartridgeId == 2)
		loadTextFile(_textFile3, "T22"); // monitor file 22
	else if (cartridgeId == 3)
		loadTextFile(_textFile3, "T23"); // monitor file 23
	else
		loadTextFile(_textFile3, "T24"); // monitor file 24
}

void DreamWebEngine::showKeys() {
	randomAccess(10);
	scrollMonitor();
	monMessage(18);

	for (int i = 0; i < 4; i++) {
		if (monitorKeyEntries[i].keyAssigned)
			monPrint(monitorKeyEntries[i].username);
	}

	scrollMonitor();
}

const char *DreamWebEngine::getKeyAndLogo(const char *foundString) {
	byte newLogo = foundString[1] - 48;
	byte keyNum = foundString[3] - 48;

	if (monitorKeyEntries[keyNum].keyAssigned == 1) {
		// Key OK
		_logoNum = newLogo;
		return foundString + 4;
	} else {
		monMessage(12);	// "Access denied, key required -"
		monPrint(monitorKeyEntries[keyNum].username);
		scrollMonitor();
		return 0;
	}
}

const char *DreamWebEngine::searchForString(const char *topic, const char *text) {
	char delim = *topic;

	while (true) {
		const char *s = topic;
		int delimCount = 0;

		char c;
		do {
			c = makeCaps(*text++);

			if (c == '*' || (delim == '=' && c == 34))
				return 0;

			if (c == delim) {
				delimCount++;
				if (delimCount == 2)
					return text;
			}

		} while (c == *s++);
	}
}

void DreamWebEngine::dirCom() {
	randomAccess(30);

	const char *dirname = parser();
	if (dirname[1]) {
		dirFile(dirname);
		return;
	}

	_logoNum = 0;
	memcpy(_currentFile+1, "ROOT        ", 12);
	monitorLogo();
	scrollMonitor();
	monMessage(9);
	searchForFiles(_textFile1._text);
	searchForFiles(_textFile2._text);
	searchForFiles(_textFile3._text);
	scrollMonitor();
}

void DreamWebEngine::dirFile(const char *dirName) {
	char topic[14];

	memcpy(topic, dirName, 14);
	topic[0] = 34;

	const char *text = _textFile1._text;
	const char *found = searchForString(topic, text);
	if (!found) {
		text = _textFile2._text;
		found = searchForString(topic, text);
		if (!found) {
			text = _textFile3._text;
			found = searchForString(topic, text);
		}
	}

	if (found) {
		found = getKeyAndLogo(found);
		if (!found)
			return; // not logged in
	} else {
		monMessage(7);
		return;
	}

	// "keyok2"
	memcpy(_currentFile+1, dirName+1, 12);
	monitorLogo();
	scrollMonitor();
	monMessage(10);

	while (true) {
		byte curChar = *found++;
		if (curChar == 34 || curChar == '*') {
			// "endofdir2"
			scrollMonitor();
			return;
		}

		if (curChar == '=')
			found = monPrint(found);
	}
}

void DreamWebEngine::read() {
	randomAccess(40);
	const char *name = parser();
	if (name[1] == 0) {
		netError();
		return;
	}

	const char *topic = _currentFile;

	const char *text = _textFile1._text;
	const char *found = searchForString(topic, text);
	if (!found) {
		text = _textFile2._text;
		found = searchForString(topic, text);
		if (!found) {
			text = _textFile3._text;
			found = searchForString(topic, text);
		}
	}

	if (found) {
		if (!getKeyAndLogo(found))
			return;
	} else {
		monMessage(7);
		return;
	}

	// "keyok1"
	found = searchForString(name, found);
	if (!found) {
		_logoNum = _oldLogoNum;
		monMessage(11);
		return;
	}

	// "findtopictext"
	monitorLogo();
	scrollMonitor();

	found++;

	while (true) {
		found = monPrint(found);
		if (found[0] == 34 || found[0] == '=' || found[0] == '*') {
			// "endoftopic"
			scrollMonitor();
			return;
		}

		processTrigger();
		randomAccess(24);
	}
}

void DreamWebEngine::signOn() {
	const char *name = parser();

	int8 foundIndex = -1;
	Common::String inputLine = name + 1;
	inputLine.trim();

	for (byte i = 0; i < 4; i++) {
		if (inputLine.equalsIgnoreCase(monitorKeyEntries[i].username)) {
			// Check if the key has already been assigned
			if (monitorKeyEntries[i].keyAssigned) {
				monMessage(17);
				return;
			} else {
				foundIndex = i;
				break;
			}
		}
	}

	if (foundIndex == -1) {
		monMessage(13);
		return;
	}

	monMessage(15);

	uint16 prevX = _monAdX;
	uint16 prevY = _monAdY;
	input();	// password input
	_monAdX = prevX;
	_monAdY = prevY;

	// The entered line has zeroes in-between each character
	uint32 len = strlen(monitorKeyEntries[foundIndex].password);
	bool found = true;

	for (uint32 i = 0; i < len; i++) {
		if (monitorKeyEntries[foundIndex].password[i] != _inputLine[i * 2]) {
			found = false;
			break;
		}
	}

	if (!found) {
		scrollMonitor();
		monMessage(16);
	} else {
		monMessage(14);
		monPrint(monitorKeyEntries[foundIndex].username);
		scrollMonitor();
		monitorKeyEntries[foundIndex].keyAssigned = 1;
	}
}

void DreamWebEngine::searchForFiles(const char *filesString) {
	byte curChar;

	while (true) {
		curChar = filesString[0];
		filesString++;
		if (curChar == '*')
			return; // "endofdir"
		if (curChar == 34)
			filesString = monPrint(filesString);
	}
}

const char *DreamWebEngine::parser() {
	char *output = _operand1;

	memset(output, 0, sizeof(_operand1));

	*output++ = '=';

	const char *in = _inputLine;

	uint8 c;

	// skip command
	do {
		c = *in++;
		in++;

		if (!c)
			return output;
	} while (c != 32);

	// skip spaces between command and operand
	do {
		c = *in++;
		in++;
	} while (c == 32);

	// copy first operand
	do {
		*output++ = c;
		c = *in++;
		in++;
		if (!c)
			return _operand1;
	} while (c != 32);

	return _operand1;
}

} // End of namespace DreamWeb
