/* 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 "base/plugins.h"

#include "common/archive.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "audio/mixer.h"

#include "engines/util.h"
#include "graphics/surface.h"

#include "fullpipe/fullpipe.h"
#include "fullpipe/gameloader.h"
#include "fullpipe/messages.h"
#include "fullpipe/behavior.h"
#include "fullpipe/modal.h"
#include "fullpipe/input.h"
#include "fullpipe/motion.h"
#include "fullpipe/statics.h"
#include "fullpipe/scenes.h"
#include "fullpipe/floaters.h"
#include "fullpipe/console.h"
#include "fullpipe/constants.h"

namespace Fullpipe {

FullpipeEngine *g_fp = nullptr;
Vars *g_vars = nullptr;

FullpipeEngine::FullpipeEngine(OSystem *syst, const ADGameDescription *gameDesc) :
	Engine(syst),
	_gameDescription(gameDesc),
	_console(this),
	_rnd("fullpipe"),
	_gameProject(nullptr),
	_modalObject(nullptr),
	_currSoundList1(),
	_mapTable() {
	DebugMan.addDebugChannel(kDebugPathfinding, "path", "Pathfinding");
	DebugMan.addDebugChannel(kDebugDrawing, "drawing", "Drawing");
	DebugMan.addDebugChannel(kDebugLoading, "loading", "Scene loading");
	DebugMan.addDebugChannel(kDebugAnimation, "animation", "Animation");
	DebugMan.addDebugChannel(kDebugBehavior, "behavior", "Behavior");
	DebugMan.addDebugChannel(kDebugMemory, "memory", "Memory management");
	DebugMan.addDebugChannel(kDebugEvents, "events", "Event handling");
	DebugMan.addDebugChannel(kDebugInventory, "inventory", "Inventory");
	DebugMan.addDebugChannel(kDebugSceneLogic, "scenelogic", "Scene Logic");
	DebugMan.addDebugChannel(kDebugInteractions, "interactions", "Interactions");
	DebugMan.addDebugChannel(kDebugXML, "xml", "XML");

	// Setup mixer
	if (!_mixer->isReady()) {
		warning("Sound initialization failed.");
	}

	syncSoundSettings();
	_sfxVolume = ConfMan.getInt("sfx_volume") * 39 - 10000;
	_musicVolume = ConfMan.getInt("music_volume");

	_gameProjectVersion = 0;
	_pictureScale = 8;
	_scrollSpeed = 0;
	_currSoundListCount = 0;

	_updateTicks = 0;
	_lastInputTicks = 0;
	_lastButtonUpTicks = 0;

	_currArchive = 0;

	_soundEnabled = true;
	_flgSoundList = true;

	_inputController = 0;
	_inputDisabled = false;

	_normalSpeed = true;

	_currentCheat = -1;
	_currentCheatPos = 0;

	_liftEnterMQ = 0;
	_liftExitMQ = 0;
	_lift = 0;
	_lastLiftButton = nullptr;
	_liftX = 0;
	_liftY = 0;

	_gameContinue = true;
	_needRestart = false;
	_flgPlayIntro = true;
	_gamePaused = false;
	_inputArFlag = false;
	_recordEvents = false;
	_mainMenu_debugEnabled = false;

	_flgGameIsRunning = true;

	_isProcessingMessages = false;

	_musicAllowed = -1;
	_musicGameVar = 0;
	_musicMinDelay = 0;
	_musicMaxDelay = 0;
	_musicLocal = 0;
	_trackStartDelay = 0;

	_stream2playing = false;

	_numSceneTracks = 0;
	_sceneTrackHasSequence = false;
	_sceneTrackIsPlaying = false;

	_aniMan = nullptr;
	_aniMan2 = nullptr;
	_currentScene = nullptr;
	_loaderScene = nullptr;
	_scene2 = nullptr;
	_scene3 = nullptr;
	_messageHandlers = nullptr;

	_updateScreenCallback = nullptr;
	_updateCursorCallback = nullptr;

	_msgX = 0;
	_msgY = 0;
	_msgObjectId2 = 0;
	_msgId = 0;
	_mouseVirtX = 0;
	_mouseVirtY = 0;

	_currSelectedInventoryItemId = 0;

	_cursorId = 0;

	_keyState = Common::KEYCODE_INVALID;
	_buttonState = 0;

	_updateFlag = true;
	_flgCanOpenMap = true;

	_sceneWidth = 1;
	_sceneHeight = 1;

	_inventoryScene = nullptr;
	_inventory = nullptr;

	_minCursorId = 0xffff;
	_maxCursorId = 0;
	_objectAtCursor = 0;
	_objectIdAtCursor = 0;

	_arcadeOverlay = nullptr;
	_arcadeOverlayHelper = nullptr;
	_arcadeOverlayX = 0;
	_arcadeOverlayY = 0;
	_arcadeOverlayMidX = 0;
	_arcadeOverlayMidY = 0;

	_isSaveAllowed = true;

	g_fp = this;
	g_vars = new Vars;
}

FullpipeEngine::~FullpipeEngine() {
	g_fp = nullptr;
	delete g_vars;
	g_vars = nullptr;
}

void FullpipeEngine::restartGame() {
	_floaters->stopAll();

	clearGlobalMessageQueueList();
	clearMessages();

	initObjectStates();

	if (_scene2) {
		_scene2->getAniMan();
		_scene2 = nullptr;
	}

	if (_currentScene) {
		_gameLoader->unloadScene(_currentScene->_sceneId);

		_currentScene = nullptr;
	}

	_gameLoader->restoreDefPicAniInfos();

	getGameLoaderInventory()->clear();
	getGameLoaderInventory()->addItem(ANI_INV_MAP, 1);
	getGameLoaderInventory()->rebuildItemRects();

	initMap();

	if (_flgPlayIntro) {
		_gameLoader->loadScene(SC_INTRO1);
		_gameLoader->gotoScene(SC_INTRO1, TrubaUp);
	} else {
		_gameLoader->loadScene(SC_1);
		_gameLoader->gotoScene(SC_1, TrubaLeft);
	}
}

bool FullpipeEngine::shouldQuit() {
	return !_gameContinue || Engine::shouldQuit();
}

Common::Error FullpipeEngine::loadGameState(int slot) {
	deleteModalObject();

	if (_gameLoader->readSavegame(getSavegameFile(slot)))
		return Common::kNoError;
	else
		return Common::kUnknownError;
}

Common::Error FullpipeEngine::saveGameState(int slot, const Common::String &description) {
	if (_gameLoader->writeSavegame(_currentScene, getSavegameFile(slot), description))
		return Common::kNoError;
	else
		return Common::kUnknownError;
}


Common::Error FullpipeEngine::run() {
	const Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0);
	// Initialize backend
	initGraphics(800, 600, &format);

	_backgroundSurface.create(800, 600, format);
	_origFormat = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);

	_globalMessageQueueList.reset(new GlobalMessageQueueList);
	_behaviorManager.reset(new BehaviorManager);

	_sceneRect.left = 0;
	_sceneRect.top = 0;
	_sceneRect.right = 799;
	_sceneRect.bottom = 599;

	_floaters.reset(new Floaters);
	_aniHandler.reset(new AniHandler);
	_globalPalette = &_defaultPalette;

	_isSaveAllowed = false;

	if (debugChannelSet(-1, kDebugXML))
		loadGameObjH();

	int scene = 0;
	if (ConfMan.hasKey("boot_param"))
		scene = convertScene(ConfMan.getInt("boot_param"));

	if (ConfMan.hasKey("save_slot"))
		scene = -1;

	if (!loadGam("fullpipe.gam", scene))
		return Common::kNoGameDataFoundError;

	if (ConfMan.hasKey("save_slot")) {
		loadGameState(ConfMan.getInt("save_slot"));
	}

#if 0
	loadAllScenes();
#endif

	int time1 = g_fp->_system->getMillis();

	// Center mouse
	_system->warpMouse(400, 300);

	for (;;) {
		updateEvents();
		if (shouldQuit()) {
			break;
		}

		int time2 = g_fp->_system->getMillis();

		// 30fps
		if (time2 - time1 >= 33 || !_normalSpeed) {
			time1 = time2;
			updateScreen();
		}

		if (_needRestart) {
			delete _modalObject;
			freeGameLoader();
			_currentScene = nullptr;
			_updateTicks = 0;
			_globalPalette = &_defaultPalette;

			loadGam("fullpipe.gam");
			_needRestart = false;
		}

		_system->delayMillis(5);
		_system->updateScreen();
	}

	delete _modalObject;
	freeGameLoader();

	cleanup();
	_backgroundSurface.free();

	return Common::kNoError;
}

void FullpipeEngine::updateEvents() {
	Common::Event event;
	Common::EventManager *eventMan = _system->getEventManager();
	ExCommand *ex;

	while (eventMan->pollEvent(event)) {
		switch (event.type) {
		case Common::EVENT_KEYDOWN:
			_keyState = event.kbd.keycode;

			switch (event.kbd.keycode) {
			case Common::KEYCODE_SPACE:
				if (_gamePaused) {
					if (_modalObject) {
						if (_modalObject->init(42)) {
							_modalObject->update();
						} else {
							deleteModalObject();
						}
					} else {
						_gameLoader->updateSystems(42);
					}
					return;
				}

				ex = new ExCommand(0, 17, 36, 0, 0, 0, 1, 0, 0, 0);
				ex->_param = 32;
				ex->_excFlags |= 3;
				ex->handle();
				break;
			case Common::KEYCODE_s:
				if (_gamePaused) {
					_gamePaused = 0;
					_flgGameIsRunning = true;
					return;
				}

				ex = new ExCommand(0, 17, 36, 0, 0, 0, 1, 0, 0, 0);
				ex->_param = event.kbd.keycode;
				ex->_excFlags |= 3;
				ex->handle();
				break;
			case Common::KEYCODE_q:
				return;
				break;
			default:
				if (event.kbd.keycode == Common::KEYCODE_d && event.kbd.hasFlags(Common::KBD_CTRL)) {
					// Start the debugger
					getDebugger()->attach();
					getDebugger()->onFrame();
				}
				ex = new ExCommand(0, 17, 36, 0, 0, 0, 1, 0, 0, 0);
				ex->_param = event.kbd.keycode;
				ex->_excFlags |= 3;
				ex->handle();
				break;
			}
			break;
		case Common::EVENT_KEYUP:
			if (!_inputArFlag) {
				ex = new ExCommand(0, 17, 37, 0, 0, 0, 1, 0, 0, 0);
				ex->_excFlags |= 3;
				ex->handle();
			}
			_keyState = Common::KEYCODE_INVALID;
			break;
		case Common::EVENT_MOUSEMOVE:
			if (_recordEvents) {
				ex = new ExCommand(0, 17, 31, event.mouse.x, event.mouse.y, 0, 1, 0, 0, 0);
				ex->_excFlags |= 3;
				ex->handle();
			}

			_mouseScreenPos = event.mouse;
			break;
		case Common::EVENT_QUIT:
			return;
		case Common::EVENT_RBUTTONDOWN:
			if (!_inputArFlag && (_updateTicks - _lastInputTicks) >= 2) {
				ex = new ExCommand(0, 17, 107, event.mouse.x, event.mouse.y, 0, 1, 0, 0, 0);
				ex->_excFlags |= 3;
				_lastInputTicks = _updateTicks;
				ex->handle();
			}
			_mouseScreenPos = event.mouse;
			break;
		case Common::EVENT_LBUTTONDOWN:
			if (!_inputArFlag && (_updateTicks - _lastInputTicks) >= 2) {
				ex = new ExCommand(0, 17, 29, event.mouse.x, event.mouse.y, 0, 1, 0, 0, 0);

				ex->_sceneClickX = _sceneRect.left + ex->_x;
				ex->_sceneClickY = _sceneRect.top + ex->_y;
				ex->_param = getGameLoaderInventory()->getSelectedItemId();
				ex->_excFlags |= 3;
				_lastInputTicks = _updateTicks;
				ex->handle();
			}
			_mouseScreenPos = event.mouse;
			break;
		case Common::EVENT_LBUTTONUP:
			if (!_inputArFlag && (_updateTicks - _lastButtonUpTicks) >= 2) {
				ex = new ExCommand(0, 17, 30, 0, 0, 0, 1, 0, 0, 0);
				ex->_excFlags |= 3;
				_lastButtonUpTicks = _updateTicks;
				ex->handle();
			}
			_mouseScreenPos = event.mouse;
			break;
		default:
			break;
		}
	}

	// pollEvent() is implemented only for video player. So skip it.
	//if (event.kbd.keycode == MSG_SC11_SHOWSWING && _modalObject) {
	//	_modalObject->pollEvent();
	//}
}

void FullpipeEngine::freeGameLoader() {
	setCursor(0);
	_floaters->stopAll();
	_gameLoader.reset();
	_currentScene = 0;
	_scene2 = 0;
	_loaderScene = 0;
}

void FullpipeEngine::cleanup() {
	//cleanRecorder();
	clearMessageHandlers();
	clearMessages();
	_globalMessageQueueList->compact();

	for (uint i = 0; i < _globalMessageQueueList->size(); i++)
		delete (*_globalMessageQueueList)[i];

	stopAllSoundStreams();
}

void FullpipeEngine::deleteModalObject() {
	if (!_modalObject)
		return;

	_modalObject->saveload();
	BaseModalObject *tmp = _modalObject->_parentObj;

	delete _modalObject;

	_modalObject = tmp;
}

void FullpipeEngine::updateScreen() {
	debugC(4, kDebugDrawing, "FullpipeEngine::updateScreen()");

	_mouseVirtX = _mouseScreenPos.x + _sceneRect.left;
	_mouseVirtY = _mouseScreenPos.y + _sceneRect.top;

	//if (inputArFlag)
	//	updateGame_inputArFlag();

	if (_modalObject || (_flgGameIsRunning && (_gameLoader->updateSystems(42), _modalObject != 0))) {
		if (_flgGameIsRunning) {
			if (_modalObject->init(42)) {
				_modalObject->update();
			} else {
				deleteModalObject();
			}
		}
	} else if (_currentScene) {
		_currentScene->draw();

		if (_inventoryScene)
			_inventory->draw();

		if (_updateScreenCallback)
			_updateScreenCallback();

		//if (inputArFlag && _currentScene) {
		//	vrtTextOut(*(_DWORD *)g_vrtHandle, smallNftData, "DEMO", 4, 380, 580);
		//	vrtTextOut(*(_DWORD *)g_vrtHandle, smallNftData, "Alt+F4 - exit", 14, 695, 580);
		//}
	} else {
		//vrtRectangle(*(_DWORD *)g_vrtHandle, 0, 0, 0, 800, 600);
	}
	_inputController->drawCursor(_mouseScreenPos.x, _mouseScreenPos.y);

	++_updateTicks;
}

int FullpipeEngine::getObjectEnumState(const Common::String &name, const char *state) {
	GameVar *var = _gameLoader->_gameVar->getSubVarByName("OBJSTATES");

	if (!var) {
		var = _gameLoader->_gameVar->addSubVarAsInt("OBJSTATES", 0);
	}

	var = var->getSubVarByName(name);
	if (var) {
		var = var->getSubVarByName("ENUMSTATES");
		if (var)
			return var->getSubVarAsInt(state);
	}

	return 0;
}

int FullpipeEngine::getObjectState(const Common::String &objname) {
	GameVar *var = _gameLoader->_gameVar->getSubVarByName("OBJSTATES");

	if (var)
		return var->getSubVarAsInt(objname);

  return 0;
}

void FullpipeEngine::setObjectState(const Common::String &name, int state) {
	GameVar *var = _gameLoader->_gameVar->getSubVarByName("OBJSTATES");

	if (!var) {
		var = _gameLoader->_gameVar->addSubVarAsInt("OBJSTATES", 0);
	}

	var->setSubVarAsInt(name, state);
}

void FullpipeEngine::disableSaves(ExCommand *ex) {
	if (_isSaveAllowed) {
		_isSaveAllowed = false;

		if (_globalMessageQueueList->size() && (*_globalMessageQueueList)[0] != 0) {
			for (uint i = 0; i < _globalMessageQueueList->size(); i++) {
				if ((*_globalMessageQueueList)[i]->_flags & 1)
					if ((*_globalMessageQueueList)[i]->_id != ex->_parId && !(*_globalMessageQueueList)[i]->_isFinished)
						return;
			}
		}

		// Original was makeing a save on every room entering
		if (_currentScene) {
			_gameLoader->saveScenePicAniInfos(_currentScene->_sceneId);
			//	_gameLoader->writeSavegame(_currentScene, "savetmp.sav");
		}
	}
}

bool FullpipeEngine::isSaveAllowed() {
	if (!g_fp->_isSaveAllowed)
		return false;

	bool allowed = true;

	for (Common::Array<MessageQueue *>::iterator s = g_fp->_globalMessageQueueList->begin(); s != g_fp->_globalMessageQueueList->end(); ++s) {
		if (!(*s)->_isFinished && ((*s)->getFlags() & 1))
			allowed = false;
	}

	return allowed;
}


} // End of namespace Fullpipe