/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/config-manager.h"

#include "agi/agi.h"
#include "agi/sprite.h"
#include "agi/graphics.h"
#include "agi/inv.h"
#include "agi/text.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
#include "agi/systemui.h"
#include "agi/appleIIgs_timedelay_overwrite.h"

namespace Agi {

/**
 * Set up new room.
 * This function is called when ego enters a new room.
 * @param n room number
 */
void AgiEngine::newRoom(int16 newRoomNr) {
	ScreenObjEntry *screenObj;
	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
	int i;

	// Loading trigger
	artificialDelayTrigger_NewRoom(newRoomNr);

	debugC(4, kDebugLevelMain, "*** room %d ***", newRoomNr);
	_sound->stopSound();

	i = 0;
	for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
		screenObj->objectNr = i++;
		screenObj->flags &= ~(fAnimated | fDrawn);
		screenObj->flags |= fUpdate;
		screenObj->stepTime = 1;
		screenObj->stepTimeCount = 1;
		screenObj->cycleTime = 1;
		screenObj->cycleTimeCount = 1;
		screenObj->stepSize = 1;
	}
	agiUnloadResources();

	_game.playerControl = true;
	_game.block.active = false;
	_game.horizon = 36;
	setVar(VM_VAR_PREVIOUS_ROOM, getVar(VM_VAR_CURRENT_ROOM));
	setVar(VM_VAR_CURRENT_ROOM, newRoomNr);
	setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
	setVar(VM_VAR_BORDER_CODE, 0);
	setVar(VM_VAR_EGO_VIEW_RESOURCE, screenObjEgo->currentViewNr);

	agiLoadResource(RESOURCETYPE_LOGIC, newRoomNr);

	// Reposition ego in the new room
	switch (getVar(VM_VAR_BORDER_TOUCH_EGO)) {
	case 1:
		screenObjEgo->yPos = SCRIPT_HEIGHT - 1;
		break;
	case 2:
		screenObjEgo->xPos = 0;
		break;
	case 3:
		screenObjEgo->yPos = _game.horizon + 1;
		break;
	case 4:
		screenObjEgo->xPos = SCRIPT_WIDTH - screenObjEgo->xSize;
		break;
	}

	uint16 agiVersion = getVersion();

	if (agiVersion < 0x2000) {
		warning("STUB: NewRoom(%d)", newRoomNr);

		screenObjEgo->flags &= ~fDidntMove;
		// animateObject(0);
		agiLoadResource(RESOURCETYPE_VIEW, screenObjEgo->currentViewNr);
		setView(screenObjEgo, screenObjEgo->currentViewNr);

	} else {
		if (agiVersion >= 0x3000) {
			// this was only done in AGI3
			if (screenObjEgo->motionType == kMotionEgo) {
				screenObjEgo->motionType = kMotionNormal;
				setVar(VM_VAR_EGO_DIRECTION, 0);
			}
		}

		setVar(VM_VAR_BORDER_TOUCH_EGO, 0);
		setFlag(VM_FLAG_NEW_ROOM_EXEC, true);

		_game.exitAllLogics = true;

		_game._vm->_text->statusDraw();
		_game._vm->_text->promptRedraw();
	}
}

void AgiEngine::resetControllers() {
	int i;

	for (i = 0; i < MAX_CONTROLLERS; i++) {
		_game.controllerOccured[i] = false;
	}
}

void AgiEngine::interpretCycle() {
	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
	bool oldSound;
	byte oldScore;

	if (!_game.playerControl)
		setVar(VM_VAR_EGO_DIRECTION, screenObjEgo->direction);
	else
		screenObjEgo->direction = getVar(VM_VAR_EGO_DIRECTION);

	checkAllMotions();

	oldScore = getVar(VM_VAR_SCORE);
	oldSound = getFlag(VM_FLAG_SOUND_ON);

	// Reset script heuristic here
	resetGetVarSecondsHeuristic();

	_game.exitAllLogics = false;
	while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) {
		setVar(VM_VAR_WORD_NOT_FOUND, 0);
		setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
		setVar(VM_VAR_BORDER_CODE, 0);
		oldScore = getVar(VM_VAR_SCORE);
		setFlag(VM_FLAG_ENTERED_CLI, false);
		_game.exitAllLogics = false;
		_veryFirstInitialCycle = false;
		artificialDelay_CycleDone();
		resetControllers();
	}
	_veryFirstInitialCycle = false;
	artificialDelay_CycleDone();
	resetControllers();

	screenObjEgo->direction = getVar(VM_VAR_EGO_DIRECTION);

	if (getVar(VM_VAR_SCORE) != oldScore || getFlag(VM_FLAG_SOUND_ON) != oldSound)
		_game._vm->_text->statusDraw();

	setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
	setVar(VM_VAR_BORDER_CODE, 0);
	setFlag(VM_FLAG_NEW_ROOM_EXEC, false);
	setFlag(VM_FLAG_RESTART_GAME, false);
	setFlag(VM_FLAG_RESTORE_JUST_RAN, false);

	if (_game.gfxMode) {
		updateScreenObjTable();
	}
	_gfx->updateScreen();
	//_gfx->doUpdate();
}

// We return the current key, or 0 if no key was pressed
uint16 AgiEngine::processAGIEvents() {
	uint16 key;
	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];

	wait(10);
	key = doPollKeyboard();

	// In AGI Mouse emulation mode we must update the mouse-related
	// vars in every interpreter cycle.
	//
	// We run AGIMOUSE always as a side effect
	setVar(VM_VAR_MOUSE_X, _mouse.pos.x / 2);
	setVar(VM_VAR_MOUSE_Y, _mouse.pos.y);

	if (!cycleInnerLoopIsActive()) {
		// Click-to-walk mouse interface
		if (_game.playerControl && (screenObjEgo->flags & fAdjEgoXY)) {
			int toX = screenObjEgo->move_x;
			int toY = screenObjEgo->move_y;

			// AGI Mouse games use ego's sprite's bottom left corner for mouse walking target.
			// Amiga games use ego's sprite's bottom center for mouse walking target.
			// Atari ST and Apple II GS seem to use the bottom left
			if (getPlatform() == Common::kPlatformAmiga)
				toX -= (screenObjEgo->xSize / 2); // Center ego's sprite horizontally

			// Adjust ego's sprite's mouse walking target position (These parameters are
			// controlled with the adj.ego.move.to.x.y-command). Note that these values rely
			// on the horizontal centering of the ego's sprite at least on the Amiga platform.
			toX += _game.adjMouseX;
			toY += _game.adjMouseY;

			screenObjEgo->direction = getDirection(screenObjEgo->xPos, screenObjEgo->yPos, toX, toY, screenObjEgo->stepSize);

			if (screenObjEgo->direction == 0)
				inDestination(screenObjEgo);
		}
	}

	handleMouseClicks(key);

	if (!cycleInnerLoopIsActive()) {
		// no inner loop active at the moment, regular processing

		if (key) {
			if (!handleController(key)) {
				if (key) {
					// Only set VAR_KEY, when no controller/direction was detected
					setVar(VM_VAR_KEY, key & 0xFF);
					if (_text->promptIsEnabled()) {
						_text->promptKeyPress(key);
					}
				}
			}
		}

		if (_menu->delayedExecuteActive()) {
			_menu->execute();
		}

	} else {
		// inner loop active
		// call specific workers
		switch (_game.cycleInnerLoopType) {
		case CYCLE_INNERLOOP_GETSTRING: // loop called from TextMgr::stringEdit()
		case CYCLE_INNERLOOP_GETNUMBER:
			if (key) {
				_text->stringKeyPress(key);
			}
			break;

		case CYCLE_INNERLOOP_INVENTORY: // loop called from InventoryMgr::show()
			if (key) {
				_inventory->keyPress(key);
			}
			break;

		case CYCLE_INNERLOOP_MENU_VIA_KEYBOARD:
			if (key) {
				_menu->keyPress(key);
			}
			break;

		case CYCLE_INNERLOOP_MENU_VIA_MOUSE:
			_menu->mouseEvent(key);
			break;

		case CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT:
			if (key) {
				_systemUI->savedGameSlot_KeyPress(key);
			}
			break;

		case CYCLE_INNERLOOP_SYSTEMUI_VERIFICATION:
			_systemUI->askForVerificationKeyPress(key);
			break;

		case CYCLE_INNERLOOP_MESSAGEBOX:
			if (key) {
				_text->messageBox_KeyPress(key);
			}
			break;

		default:
			break;
		}
	}

	_gfx->updateScreen();

	return key;
}

int AgiEngine::playGame() {
	int ec = errOK;
	const AgiAppleIIgsDelayOverwriteGameEntry *appleIIgsDelayOverwrite = nullptr;
	const AgiAppleIIgsDelayOverwriteRoomEntry *appleIIgsDelayRoomOverwrite = nullptr;

	debugC(2, kDebugLevelMain, "initializing...");
	debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());

	_sound->stopSound();

	// We need to do this accurately and reset the AGI priorityscreen to 4
	// otherwise at least the fan game Nick's Quest will go into an endless
	// loop, because the game draws views before it draws the first background picture.
	// For further study see bug #3451122
	_gfx->clear(0, 4);

	_game.horizon = 36;
	_game.playerControl = false;

	setFlag(VM_FLAG_LOGIC_ZERO_FIRST_TIME, true); // not in 2.917
	setFlag(VM_FLAG_NEW_ROOM_EXEC, true);         // needed for MUMG and SQ2!
	setFlag(VM_FLAG_SOUND_ON, true);              // enable sound
	// do not set VM_VAR_TIME_DELAY, original AGI did not do it (in the data segment it was simply set to 0)

	_game.gfxMode = true;
	_text->promptRow_Set(22);

	// We run AGIMOUSE always as a side effect
	//if (getFeatures() & GF_AGIMOUSE)
		debug(1, "Using AGI Mouse 1.0 protocol");

	if (getFeatures() & GF_AGIPAL)
		debug(1, "Running AGIPAL game");

	debug(0, "Running AGI script.\n");

	setFlag(VM_FLAG_ENTERED_CLI, false);
	setFlag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
	setVar(VM_VAR_WORD_NOT_FOUND, 0);
	setVar(VM_VAR_KEY, 0);

	debugC(2, kDebugLevelMain, "Entering main loop");
	bool firstLoop = !getFlag(VM_FLAG_RESTART_GAME); // Do not restore on game restart

	if (firstLoop) {
		if (ConfMan.hasKey("save_slot")) {
			// quick restore enabled
			_game.automaticRestoreGame = true;
		}
	}

	artificialDelay_Reset();

	if (getPlatform() == Common::kPlatformApple2GS) {
		// Look up, if there is a time delay overwrite table for the current game
		appleIIgsDelayOverwrite = appleIIgsDelayOverwriteGameTable;
		while (appleIIgsDelayOverwrite->gameId != GID_AGIDEMO) {
			if (appleIIgsDelayOverwrite->gameId == getGameID())
				break; // game found
			appleIIgsDelayOverwrite++;
		}
	}

	do {
		processAGIEvents();

		inGameTimerUpdate();

		uint16 timeDelay = getVar(VM_VAR_TIME_DELAY);

		if (getPlatform() == Common::kPlatformApple2GS) {
			timeDelay++;
			// It seems that either Apple IIgs ran very slowly or that the delay in its interpreter was not working as everywhere else
			// Most games on that platform set the delay to 0, which means no delay in DOS
			// Gold Rush! even "optimizes" itself when larger sprites are on the screen it sets TIME_DELAY to 0.
			// Normally that game runs at TIME_DELAY 1.
			// Maybe a script patch for this game would make sense.
			// TODO: needs further investigation

			int16 timeDelayOverwrite = -99;

			// Now check, if we got a time delay overwrite entry for current room
			if (appleIIgsDelayOverwrite->roomTable) {
				byte curRoom = getVar(VM_VAR_CURRENT_ROOM);
				int16 curPictureNr = _picture->getResourceNr();

				appleIIgsDelayRoomOverwrite = appleIIgsDelayOverwrite->roomTable;
				while (appleIIgsDelayRoomOverwrite->fromRoom >= 0) {
					if ((appleIIgsDelayRoomOverwrite->fromRoom <= curRoom) && (appleIIgsDelayRoomOverwrite->toRoom >= curRoom)) {
						if ((appleIIgsDelayRoomOverwrite->activePictureNr == curPictureNr) || (appleIIgsDelayRoomOverwrite->activePictureNr == -1)) {
							if (appleIIgsDelayRoomOverwrite->onlyWhenPlayerNotInControl) {
								if (_game.playerControl) {
									// Player is actually currently in control? -> then skip this entry
									appleIIgsDelayRoomOverwrite++;
									continue;
								}
							}
							timeDelayOverwrite = appleIIgsDelayRoomOverwrite->timeDelayOverwrite;
							break;
						}
					}
					appleIIgsDelayRoomOverwrite++;
				}

				if (timeDelayOverwrite == -99) {
					// use default time delay in case no room specific one was found
					timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite;
				}
			} else {
				timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite;
			}

			if (timeDelayOverwrite >= 0) {
				if (timeDelayOverwrite != timeDelay) {
					// delayOverwrite is not the same as the delay taken from the scripts? overwrite it
					//warning("AppleIIgs: time delay overwrite from %d to %d", timeDelay, timeDelayOverwrite);

					setVar(VM_VAR_TIME_DELAY, timeDelayOverwrite - 1); // adjust for Apple IIgs
					timeDelay = timeDelayOverwrite;
				}
			}
		}

		// Increment the delay value by one, so that we wait for at least 1 cycle
		// In Original AGI 1 cycle was 50 milliseconds, so 20 frames per second
		// So TIME_DELAY 1 resulted in around 20 frames per second
		//               2 resulted in around 10 frames per second
		//               0 however resulted in no limits at all, so the game ran as fast as possible
		// We obviously do not want the game to run as fast as possible, so we will use 40 frames per second instead.
		timeDelay = timeDelay * 2;
		if (!timeDelay)
			timeDelay = 1;

		// Our cycle counter runs at 25 milliseconds.
		// So time delay has to be 1 for the originally unlimited speed - for our 40 fps
		//                         2 for 20 frames per second
		//                         4 for 10 frames per second
		//                         and so on.

		if (_passedPlayTimeCycles >= timeDelay) {
			// code to check for executed cycles
			// TimeDate time;
			// g_system->getTimeAndDate(time);
			// warning("cycle %d", time.tm_sec);
			inGameTimerResetPassedCycles();

			interpretCycle();

			// Check if the user has asked to load a game from the command line
			// or the launcher
			if (_game.automaticRestoreGame) {
				_game.automaticRestoreGame = false;
				checkQuickLoad();
			}

			setFlag(VM_FLAG_ENTERED_CLI, false);
			setFlag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
			setVar(VM_VAR_WORD_NOT_FOUND, 0);
			setVar(VM_VAR_KEY, 0);
		}

		if (shouldPerformAutoSave(_lastSaveTime)) {
			saveGame(getSavegameFilename(0), "Autosave");
		}

	} while (!(shouldQuit() || _restartGame));

	_sound->stopSound();

	return ec;
}

int AgiEngine::runGame() {
	int ec = errOK;

	// Execute the game
	do {
		debugC(2, kDebugLevelMain, "game loop");
		debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());

		if (agiInit() != errOK)
			break;

		if (_restartGame) {
			setFlag(VM_FLAG_RESTART_GAME, true);
			// do not set VM_VAR_TIME_DELAY, original AGI did not do it

			// Reset in-game timer
			inGameTimerReset();

			_restartGame = false;
		}

		// Set computer type (v20 i.e. vComputer) and sound type
		switch (getPlatform()) {
		case Common::kPlatformAtariST:
			setVar(VM_VAR_COMPUTER, kAgiComputerAtariST);
			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);
			break;
		case Common::kPlatformAmiga:
			if (getFeatures() & GF_OLDAMIGAV20)
				setVar(VM_VAR_COMPUTER, kAgiComputerAmigaOld);
			else
				setVar(VM_VAR_COMPUTER, kAgiComputerAmiga);
			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);
			break;
		case Common::kPlatformApple2GS:
			setVar(VM_VAR_COMPUTER, kAgiComputerApple2GS);
			if (getFeatures() & GF_2GSOLDSOUND)
				setVar(VM_VAR_SOUNDGENERATOR, kAgiSound2GSOld);
			else
				setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);
			break;
		case Common::kPlatformDOS:
		default:
			setVar(VM_VAR_COMPUTER, kAgiComputerPC);
			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);
			break;
		}

		// Set monitor type (v26 i.e. vMonitor)
		switch (_renderMode) {
		case Common::kRenderCGA:
			setVar(VM_VAR_MONITOR, kAgiMonitorCga);
			break;
		case Common::kRenderHercA:
		case Common::kRenderHercG:
			// Set EGA for now. Some games place text differently, when this is set to kAgiMonitorHercules.
			// Text placement was different for Hercules rendering (16x12 instead of 16x16). There also was
			// not enough space left for the prompt at the bottom. This was caused by the Hercules resolution.
			// We don't have this restriction and we also support the regular prompt for Hercules mode.
			// In theory Sierra could have had special Hercules code inside their games.
			// TODO: check this.
			setVar(VM_VAR_MONITOR, kAgiMonitorEga);
			break;
		// Don't know if Amiga AGI games use a different value than kAgiMonitorEga
		// for vMonitor so I just use kAgiMonitorEga for them (As was done before too).
		case Common::kRenderAmiga:
		case Common::kRenderApple2GS:
		case Common::kRenderAtariST:
		case Common::kRenderEGA:
		case Common::kRenderVGA:
		default:
			setVar(VM_VAR_MONITOR, kAgiMonitorEga);
			break;
		}

		setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value
		setVar(VM_VAR_MAX_INPUT_CHARACTERS, 38);
		_text->promptDisable();

		ec = playGame();
		agiDeinit();
	} while (_restartGame);

	delete _menu;
	_menu = NULL;

	releaseImageStack();

	return ec;
}

} // End of namespace Agi
