/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "immortal/room.h"
#include "immortal/immortal.h"

namespace Immortal {

int ImmortalEngine::logicFreeze() {
	// Very silly way of checking if the level is over and/or the game is over
	int g = _gameOverFlag | _levelOver;
	return (g ^ 1) >> 1;
}

void ImmortalEngine::logicInit() {
	_titlesShown = 0;
	_time = 0;
	_promoting = 0;
	_restart = true;
	levelInitAtStartOfGameOnly();
	_lastCertLen = 0;
}

void ImmortalEngine::restartLogic() {
	_singleStep = false;
	_levelOver = false;
	_gameFlags = kSavedNone;

	// Here's where the majority of the game actually gets initialized
	miscInit();
	cycleFreeAll();
	levelInit();
	//roomInit();       <-- will be run in constructor of room
	//monstInit();      <-- room.initMonsters()     -|
	//objectInit();     <-- room.initObjects()       |
	//doorInit();       <-- room.initDoors()         |- probably all get run from room constructor
	//sparkInit();      <-- room.initSparks()        |
	//bulletInit();     <-- room.initProjectiles()  -|
	//objectInit();     <-- again? Odd...
	//genericSpriteInit();  <-- room.initGenSprites()

	if (fromOldGame() == false) {
		_level = 0;
		levelNew(_level);
	}

	_rooms[_currentRoom]->flameInit();

	if (_level != 7) {
		_themePaused = true;    // and #-1-2 = set both flags for themePaused
	}
}

void ImmortalEngine::logic() {
	trapKeys();                         // First thing in any gameloop is to check if we should restart/toggle sound
	_time += 1;

	/* This is actually the main game state loop. I think the best way to translate it
	 * is as a do-while loop. As in, check if the gamestate says we need to restart,
	 * and if so, restart the logic and check again
	 * Personally, I think this should have been a jump table for the different
	 * game state routines, indexed by a single game state variable.
	 * Ie. LDX _gameState : JMP (gameStates),X
	 * Much cleaner I think. Regardless, this will probably be a switch statement eventually.
	 */
	do {

		if (_restart == true) {
			restartLogic();
			_restart = false;
		}

		// This is the original logic, but I think it makes more sense if this were an else if statement
		if (_gameOverFlag == true) {
			gameOver();
			_gameOverFlag = false;
			_restart = true;

		} else if (_levelOver == true) {
			_themePaused = true;
			_levelOver = false;

			if (_level == (_maxLevels - 1)) {
				textPrint(kStrYouWin, 0);

			} else {
				makeCertificate();
				printCertificate();
				_promoting = 1;
			}
			_restart = true;

		} else {

			// Here's where the gameplay sequence actually happens!
			doSingleStep();             // Debug step function
			//monstRunAll();
			//objectRunAll();
			//doInfiniteHallways();
			levelDrawAll();
			updateHitGauge();

			_dim = false;
			if ((_level == 0) && (/*_currentLevel.getShowRoom()*/0 == 0) && (_rooms[_currentRoom]->roomLighted() == false) && (/*getNumBullets()*/ 0 == 0)) {
				_dim = true;
			}

			if (_level == 7) {
				doGroan();
			}

			if (/*monstIsCombat(kPlayerID)*/false == true) {
				if (getPlaying() != kSongCombat) {
					playCombatSong();
				}
			} else {
				if (getPlaying() != kSongMaze) {
					playMazeSong();
				}
			}
		}

	} while (_restart == true);
}

void ImmortalEngine::trapKeys() {
	/* Weird name for a normal routine. It simply checks for the
	 * restart key (or button on the nes), or the sound toggle,
	 * (if debug mode is active it also checks for the
	 * _singleStep key), and then performs a high level action
	 * (on the NES it only checks restart, and it opens a dialog to do it)
	 */
	getInput();
	switch (_pressedAction) {
	case kActionDBGStep:
		_singleStep = true;
		break;
	case kActionRestart:
		gameOver();
		break;
	case kActionSound:
		toggleSound();
	// fall through
	default:
		break;
	}
}

int ImmortalEngine::keyOrButton() {
	// Returns a key if a key was pressed, or 13 if a button was pressed

	int button = 0;
	while (button == 0) {
		getInput();
		switch (_pressedAction) {
		case kActionKey:
			button = _pressedAction;
			break;
		case kActionFire:
		// fall through
		case kActionButton:
			button = 13;
		// fall through
		default:
			break;
		}
	}
	return button;
}

void ImmortalEngine::doSingleStep() {
	/* This is a very cool debug routine. If you press the _singleStep key,
	 * the engine enters this debug mode where it waits for another key press.
	 * If the key is anything other than the _singleStep key, it will advance
	 * the engine one frame (or rather, until we hit this routine again, which
	 * should be one frame). If you hit the _singleStep key, it will exit the mode
	 * and advance normally again.
	 */
	if (_singleStep == true) {
		// If singleStep mode is active, stop the engine until we get input
		waitKey();
		// If the input is anything other than DGBStep, advance one frame
		if (_pressedAction == kActionDBGStep) {
			// Otherwise, we want to exit the mode
			_singleStep = false;
		}
	}
}

void ImmortalEngine::updateHitGauge() {
	/* This HUD (essentially) drawing routine is a bit weird because
	 * the game was originally meant to have multiple player characters
	 * in the room at once. So the engine sees the player as a 'monster'
	 * in the same way it sees enemies (and presumably would have seen other players).
	 * As such, this routine asks the room to ask the monster called player,
	 * what their health is. If the game considered the player unique, this would
	 * probably just check a global 'health' variable instead.
	 */
	//int hits = _rooms[_currentRoom]._monsters[kPlayerID]._getHits();
	int hits = 15;
	if (hits != _lastGauge) {
		// Update the mirror value if the health has changed since last frame
		_lastGauge = hits;
		drawGauge(hits);
	}
}

void ImmortalEngine::drawGauge(int h) {
	/* Draw the health bar:
	 * We have two variables, the current health (number of hits remaining),
	 * and the difference betweeen the current health and max health (16).
	 * We then do some silly branching logic that is functionally the same
	 * as a for loop for the available health, and then another for unavailable health.
	 * But we could also write it much more efficiently like this:
	 * sta tmp : lda #$16 : tay : dey : sub tmp : tax
	 * -
	 * txa : beq +
	 * lda #$1 : dex
	 * +
	 * jsr draw
	 * dey : bpl -
	 * Ie. Loop over the entire bar, and once you run out of one icon to draw, that 0 becomes
	 * the index of the chr for the other icons.
	 */
	int r = 16 - h;
	setPen(kGaugeX, kGaugeY);
	h--;
	if (h >= 0) {
		// This could be written as a regular for loop, but the game thinks of start/stop as different from on
		printChr(kGaugeStart);
		h--;
		for (; h >= 0; h--) {
			if (h == 0) {
				// Redundant code is redundant
				printChr(kGaugeStop);
			} else {
				printChr(kGaugeOn);
			}
		}

	} else {
		// Oh hey, this one is indeed a normal for loop
		for (; r >= 0; r--) {
			printChr(kGaugeOff);
		}
	}
}

bool ImmortalEngine::printAnd(Str s) {
	// Only ever used by fromOldGame()
	// Just prints what it's given and then checks for input
	textPrint(s, 0);
	getInput();
	if (_heldAction != kActionNothing) {
		return true;
	}
	return false;
}

bool ImmortalEngine::fromOldGame() {
	/* This is the basic load game routine (and also title display).
	 * It lets the user enter a password, or start a new game.
	 * Either way it sets up the inventory for the level, and also
	 * various object related things for the specific level.
	 */
	if (_titlesShown == 0) {
		_titlesShown++;
		_dontResetColors = 1;
		printAnd(kStrTitle0);
		printAnd(kStrTitle4);
		getInput();
		return false;
	}

	_dontResetColors = 0;
	if (_promoting == 1) {
		_promoting = 0;

	} else {

		do {
			if (!textPrint(kStrOldGame, 0)) {
				// They choose not to load an old game
				return false;
			}
		} while (getCertificate() == true);

		if (_lastCertLen == 0) {
			return false;
		}
	}

	// Set game flags
	_level = _certificate[kCertLevel];

	setGameFlags((_certificate[kCertHiGameFlags] << 4) | _certificate[kCertLoGameFlags]);

	// Create the player

	//uint8 hits  = _certificate[kCertHits];
	//uint8 quick = _certificate[kCertQuickness];
	//uint8 gold  = (_certificate[kCertGoldHi] << 4) | _certificate[kCertGoldLo];
	// monstMakePlayer(hits, quick, gold);  <- will become room.makePlayer();

	// Create the inventory
	// room.makeObject(3, kObjIsRunning, 0, goldType);

	// Hi bits of inventory
	int certInv = _certificate[kCertInvHi];

	if ((certInv & 1) != 0) {
		if (_level < 2) {
			//room.makeObject(3, 0, 0, waterType);
		}
	}

	if ((certInv & 2) != 0) {
		//room.makeObject(3, 0, kRingFrame, dunRingType);
	}

	if ((certInv & 4) != 0) {
		if (_level < 6) {
			//room.makeObject(3, 0, kSporesFrame, wormFoodType);
		}
	}

	if ((certInv & 8) != 0) {
		//room.makeObject(3, 0, 0 (?), coinType);
	}


	// Low bits of inventory
	certInv = _certificate[kCertInvLo];

	// This would have been much more clean as a set of tables instead of a long branching tree
	switch (_certificate[kCertLevel]) {
	case 1:
		if ((certInv & 2) != 0) {
			//room.makeObject(3, 0, kSporesFrame, sporesType);
		}

		if ((certInv & 4) != 0) {
			//room.makeObject(3, 0, kSporesFrame, wowCharmType);
		}

		break;
	case 4:
		if ((certInv & 2) != 0) {
			//room.makeObject(3, kIsInvisible, kSporesFrame, coffeeType);
		}

		break;
	case 3:
		if ((certInv & 1) != 0) {
			//room.makeObject(3, kIsRunning, kRingFrame, faceRingType);
		}

		break;
	case 7:
		if ((certInv & 1) != 0) {
			//room.makeObject(6, kUsesFireButton, kSporesFrame, bronzeType);
		}

		if ((certInv & 2) != 0) {
			//room.makeObject(3, 0, kSporesFrame, tractorType);
		}

		if ((certInv & 4) != 0) {
			//room.makeObject(3, 0, kSporesFrame, antiType);
		}
	// fall through
	default:
		break;
	}
	levelNew(_level);
	return true;
}

void ImmortalEngine::makeCertificate() {
	/* The code for this bit doesn't really make sense,
	 * so I will write it as it is, but I am noting here
	 * that it should be:
	 * jsr monst_getGold : ... sta certificate+certgoldhi
	 * jsr monst_getQuickness : sta certificate+certquickness
	 * instead of getquickness : get gold : sta gold : sta quickness
	 * also no need to ldx 0 since this is player only ram right?
	 */

	//uint8 q = room._playerQuickness
	//uint16 g = room._playerGold
	uint16 g = 0;

	_certificate[kCertGoldLo] = g & 0xf;
	_certificate[kCertGoldHi] = g >> 4;
	_certificate[kCertQuickness] = g >> 4; // Should actually be = q, but this is what the game does

	_certificate[kCertHits] = 0; //room._playerHits
	_certificate[kCertLoGameFlags] = getGameFlags() & 0xf;
	_certificate[kCertLoGameFlags] = getGameFlags() >> 4;

	_certificate[kCertLevel] = _level + 1;
	_certificate[kCertInvLo] = 0;
	_certificate[kCertInvHi] = 0;

	if (true/*room.monster[kPlayerID].hasObject(waterType)*/) {
		_certificate[kCertInvHi] |= 1;
	}

	if (true/*room.monster[kPlayerID].hasObject(dunRingType)*/) {
		_certificate[kCertInvHi] |= 2;
	}

	if (true/*room.monster[kPlayerID].hasObject(wormFoodType)*/) {
		_certificate[kCertInvHi] |= 4;
	}

	if (true/*room.monster[kPlayerID].hasObject(coinType)*/) {
		_certificate[kCertInvHi] |= 8;
	}

	// The lo byte of the inventory is used for items that only exist on a specific level, and are removed after
	switch (_certificate[kCertLevel]) {
	case 1:
		if (true/*room.monster[kPlayerID].hasObject(sporesType)*/) {
			_certificate[kCertInvLo] |= 2;
		}

		if (true/*room.monster[kPlayerID].hasObject(wowCharmType)*/) {
			_certificate[kCertInvLo] |= 4;
		}
	// fall through
	case 3:
		if (true/*room.monster[kPlayerID].hasObject(faceRingType)*/) {
			_certificate[kCertInvLo] |= 1;
		}
	// fall through
	case 4:
		if (true/*room.monster[kPlayerID].hasObject(coffeeType)*/) {
			_certificate[kCertInvLo] |= 2;
		}
	// fall through
	case 7:
		if (true/*room.monster[kPlayerID].hasObject(bronzeType)*/) {
			_certificate[kCertInvLo] |= 1;
		}

		if (true/*room.monster[kPlayerID].hasObject(tractorType)*/) {
			_certificate[kCertInvLo] |= 2;
		}

		if (true/*room.monster[kPlayerID].hasObject(antiType)*/) {
			_certificate[kCertInvLo] |= 4;
		}
	// fall through
	default:
		_lastCertLen = 13;
		uint8 checksum[4];
		calcCheckSum(_lastCertLen, checksum);
		_certificate[0] = checksum[0];
		_certificate[1] = checksum[1];
		_certificate[2] = checksum[2];
		_certificate[3] = checksum[3];
	}
}

void ImmortalEngine::calcCheckSum(int l, uint8 checksum[]) {
	checksum[0] = 4;
	checksum[1] = 0xa5;

	/* The game logic seems to allow a len of 4 (cmp 4 : bcc),
	 * but the checksum iny before it checks if the sizes are the same,
	 * so shouldn't a cert of len 4 cause it to loop 0xfffc times?
	 */
	for (int i = 4; i <= l; i++) {
		checksum[0] = (_certificate[i] + checksum[0]) ^ checksum[1];
		checksum[1] = (_certificate[i] << 1) + checksum[1];
	}

	checksum[3] = checksum[1] >> 4;
	checksum[2] = checksum[1] & 0xf;
	checksum[1] = checksum[0] >> 4;
	checksum[0] = checksum[0] & 0xf;
}

bool ImmortalEngine::getCertificate() {
	textPrint(kStrCert, 0);
	int certLen = 0;
	bool entered = false;
	int k = 0;

	// My goodness the logic for this is a mess.
	while (entered == false) {
		k = keyOrButton();
		if (k == 13) {
			entered = true;

		} else if (k == 0x7f) {
			// The input was a backspace
			if (certLen != 0) {
				certLen--;              // Length is one smaller now
				backspace();            // move the drawing position back and reprint the '-' char
				backspace();
				printChr('-');
			}

		} else {
			// The input was a key
			if (certLen != kMaxCertificate) {
				if ((k >= 'a') && (k < '{')) {
					k -= 0x20;
				}

				if (k >= '0') {
					if (k < ('9' + 1)) {
						k -= '0';
					}

					else {
						if (k < 'A') {
							continue;
						}

						if (k < ('F' + 1)) {
							k -= ('A' - 10);
						}
					}

					int certK = k;
					if ((k < ('Z' + 1)) && (k >= 'A')) {
						k += ('a' - 'A');
					}
					backspace();
					printChr(k);
					printChr('-');
					_certificate[certLen] = certK;
					certLen++;
				}
			}
		}
	}

	// Input of certificate is finished
	if (certLen == 0) {
		certLen = _lastCertLen;
	}
	if (certLen != 0) {
		if (certLen < 4) {
			textPrint(kStrBadCertificate, 0);
			return false;
		}
		uint8 checksum[4];
		calcCheckSum(certLen, checksum);
		for (int i = 0; i < 4; i++) {
			if (checksum[i] != _certificate[i]) {
				textPrint(kStrBadCertificate, 0);
				return false;
			}
		}
	}

	// Cert is good
	_lastCertLen = certLen;
	return true;
}

void ImmortalEngine::printCertificate() {
	/* In contrast to the other certificate routines,
	 * this one is nice and simple. You could also
	 * just add the appropriate offset for the letters,
	 * but grabbing it from a table is faster and doesn't
	 * use a lot of space (especially if it's used anywhere else)
	 */
	char toHex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

	textBeginning(kStrCert, 0);
	for (int i = 0; i < _lastCertLen; i++) {
		printChr(toHex[_certificate[i]]);
	}
	textEnd(kStrCert2, 0);
}

bool ImmortalEngine::isSavedKing() {
	if ((_gameFlags & kSavedKing) == kSavedKing) {
		return true;
	} else {
		return false;
	}
}

bool ImmortalEngine::isSavedAna() {
	if ((_gameFlags & kSavedAna) == kSavedAna) {
		return true;
	} else {
		return false;
	}
}


/*
 * These functions don't really need to be functions
 */

void ImmortalEngine::setGameFlags(uint16 f) {
	_gameFlags = f;
}

uint16 ImmortalEngine::getGameFlags() {
	return _gameFlags;
}

int ImmortalEngine::getLevel() {
	return _level;
}

void ImmortalEngine::gameOverDisplay() {
	_themePaused = true;
	textPrint(kStrGameOver, 0);
}

void ImmortalEngine::gameOver() {
	_gameOverFlag = 1;
}

void ImmortalEngine::levelOver() {
	_levelOver = 1;
}

void ImmortalEngine::setSavedKing() {
	_gameFlags |= kSavedKing;
}

void ImmortalEngine::setSavedAna() {
	_gameFlags |= kSavedAna;
}


/*
 * Not relevant yet (music)
 */

void ImmortalEngine::doGroan() {
	//getRandom();
}


} // namespace Immortal
