/* 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 "titanic/main_game_window.h"
#include "titanic/continue_save_dialog.h"
#include "titanic/debugger.h"
#include "titanic/game_manager.h"
#include "titanic/game_view.h"
#include "titanic/messages/messages.h"
#include "titanic/pet_control/pet_control.h"
#include "titanic/support/files_manager.h"
#include "titanic/titanic.h"
#include "common/config-manager.h"
#include "graphics/screen.h"

namespace Titanic {

CMainGameWindow::CMainGameWindow(TitanicEngine *vm): _vm(vm),
		_priorLeftDownTime(0), _priorMiddleDownTime(0) {
	_gameView = nullptr;
	_gameManager = nullptr;
	_project = nullptr;
	_inputAllowed = false;
	_image = nullptr;
	_cursor = nullptr;
	_pendingLoadSlot = -1;

	// Set the window as an event target
	vm->_events->addTarget(this);
}

CMainGameWindow::~CMainGameWindow() {
	delete _gameView;
	delete _gameManager;
	delete _project;
}

void CMainGameWindow::applicationStarting() {
	// Set the video mode
	CScreenManager *screenManager = CScreenManager::setCurrent();
	screenManager->setMode(640, 480, 16, 0, true);

	// Show the initial copyright & info screen for the game
	if (!isLoadingFromLauncher()) {
		Image image;
		image.load("Bitmap/TITANIC");
		_vm->_screen->blitFrom(image, Point(
			SCREEN_WIDTH / 2 - image.w / 2,
			SCREEN_HEIGHT / 2 - image.h / 2
			));

		// Delay for 5 seconds
		const int NUM_STEPS = 20;
		for (int idx = 0; idx < NUM_STEPS; ++idx) {
			_vm->_events->sleep(5000 / NUM_STEPS);
			if (_vm->_loadSaveSlot >= 0)
				break;
		}
	}

	// Set up the game project, and get game slot
	int saveSlot = getSavegameSlot();
	if (saveSlot == -2)
		return;

	// Create game view and manager
	_gameView = new CSTGameView(this);
	_gameManager = new CGameManager(_project, _gameView, g_vm->_mixer);
	_gameView->setGameManager(_gameManager);

	// Load either a new game or selected existing save
	_project->loadGame(saveSlot);
	_inputAllowed = true;
	_gameManager->_gameState.setMode(GSMODE_INTERACTIVE);

	// Generate starting messages for entering the view, node, and room.
	// Note the old fields are nullptr, since there's no previous view/node/room
	CViewItem *view = _gameManager->_gameState._gameLocation.getView();
	CEnterViewMsg enterViewMsg(nullptr, view);
	enterViewMsg.execute(view, nullptr, MSGFLAG_SCAN);

	CNodeItem *node = view->findNode();
	CEnterNodeMsg enterNodeMsg(nullptr, node);
	enterNodeMsg.execute(node, nullptr, MSGFLAG_SCAN);

	CRoomItem *room = view->findRoom();
	CEnterRoomMsg enterRoomMsg(nullptr, room);
	enterRoomMsg.execute(room, nullptr, MSGFLAG_SCAN);

	_gameManager->markAllDirty();
}

int CMainGameWindow::getSavegameSlot() {
	_project = new CProjectItem();
	_project->setFilename("starship.prj");

	return selectSavegame();
}

bool CMainGameWindow::isLoadingFromLauncher() const {
	return ConfMan.hasKey("save_slot");
}

int CMainGameWindow::selectSavegame() {
	// If a savegame was selected from GMM during the startup, return it
	if (g_vm->_loadSaveSlot != -1)
		return g_vm->_loadSaveSlot;

	// If the user selected a savegame from the launcher, return it
	if (ConfMan.hasKey("save_slot"))
		return ConfMan.getInt("save_slot");

	CContinueSaveDialog dialog;
	bool hasSavegames = false;

	// Loop through save slots to find any existing save slots
	for (int idx = 0; idx <= MAX_SAVES; ++idx) {
		CString saveName = g_vm->getSavegameName(idx);
		if (!saveName.empty()) {
			dialog.addSavegame(idx, saveName);
			hasSavegames = true;
		}
	}

	// If there are savegames, show the select dialog and get a choice.
	// If there aren't, return -1 to indicate starting a new game
	return hasSavegames ? dialog.show() : -1;
}

void CMainGameWindow::setActiveView(CViewItem *viewItem) {
	_gameManager->_gameState._gameLocation.setView(viewItem);

	CResourceKey key;
	if (viewItem->getResourceKey(&key)) {
		// Create a surface based on the key
		_gameView->createSurface(key);
	}
}

void CMainGameWindow::draw() {
	if (_gameManager) {
		if (!_gameView->_surface) {
			CViewItem *view = _gameManager->getView();
			if (view)
				setActiveView(view);
		}

		CScreenManager *scrManager = CScreenManager::setCurrent();
		scrManager->clearSurface(SURFACE_BACKBUFFER, &_gameManager->_bounds);

		switch (_gameManager->_gameState._mode) {
		case GSMODE_PENDING_LOAD:
			// Pending savegame to load
			_gameManager->_gameState.setMode(GSMODE_INTERACTIVE);
			_project->loadGame(_pendingLoadSlot);
			_pendingLoadSlot = -1;

			_gameManager->markAllDirty();
			scrManager->setSurfaceBounds(SURFACE_PRIMARY, Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));

			// Intentional fall-through
			// to draw loaded game

		case GSMODE_INTERACTIVE:
		case GSMODE_CUTSCENE:
			if (_gameManager->_gameState._petActive)
				drawPet(scrManager);

			drawView();
			drawViewContents(scrManager);
			scrManager->drawCursors();
			break;

		case GSMODE_INSERT_CD:
			scrManager->drawCursors();
			_vm->_filesManager->insertCD(scrManager);
			break;

		default:
			break;
		}
	}
}

void CMainGameWindow::drawPet(CScreenManager *screenManager) {
	if (_gameView && _gameView->_surface) {
		CPetControl *petControl = _gameManager->_project->getPetControl();
		if (petControl)
			petControl->draw(screenManager);
	}
}

void CMainGameWindow::drawView() {
	if (_gameView->_surface)
		_gameView->drawView();
}

void CMainGameWindow::drawViewContents(CScreenManager *screenManager) {
	// Get a reference to the room, validating that it's present
	if (!screenManager)
		return;
	CViewItem *view = _gameManager->getView();
	if (!view)
		return;
	CNodeItem *node = view->findNode();
	if (!node)
		return;
	CRoomItem *room = node->findRoom();
	if (!room)
		return;

	double xVal = 0.0, yVal = 0.0;
	room->calcNodePosition(node->_nodePos, xVal, yVal);

	// Iterate through drawing all the items in the scene except any item
	// that's currently being dragged
	for (CTreeItem *treeItem = view; treeItem; treeItem = treeItem->scan(view)) {
		if (treeItem != _gameManager->_dragItem)
			treeItem->draw(screenManager);
	}

	// Finally draw the drag item if there is one
	if (_gameManager->_dragItem)
		_gameManager->_dragItem->draw(screenManager);
}

void CMainGameWindow::mouseChanged() {
	if (_gameManager->_gameState._mode != GSMODE_INSERT_CD)
		_gameManager->update();
}

void CMainGameWindow::loadGame(int slotId) {
	_pendingLoadSlot = slotId;
	_gameManager->_gameState.setMode(GSMODE_PENDING_LOAD);
}

void CMainGameWindow::onIdle() {
	if (!_inputAllowed)
		return;
	CGameManager *gameManager = _gameManager;
	if (!gameManager)
		return;

	// Let the game manager perform any game updates
	gameManager->update();

	if (gameManager->_gameState._quitGame) {
		// Game needs to shut down
		_vm->quitGame();
	}
}

#define HANDLE_MESSAGE(METHOD) 	if (_inputAllowed) { \
	_gameManager->_inputTranslator.METHOD(g_vm->_events->getSpecialButtons(), mousePos); \
	mouseChanged(); \
	}


void CMainGameWindow::mouseMove(const Point &mousePos) {
	if (!isMouseControlEnabled())
		return;

	HANDLE_MESSAGE(mouseMove)
}

void CMainGameWindow::leftButtonDown(const Point &mousePos) {
	if (!isMouseControlEnabled())
		return;

	if ((_vm->_events->getTicksCount() - _priorLeftDownTime) < DOUBLE_CLICK_TIME) {
		_priorLeftDownTime = 0;
		leftButtonDoubleClick(mousePos);
	} else {
		_priorLeftDownTime = _vm->_events->getTicksCount();
		HANDLE_MESSAGE(leftButtonDown)
	}
}

void CMainGameWindow::leftButtonUp(const Point &mousePos) {
	if (!isMouseControlEnabled())
		return;

	HANDLE_MESSAGE(leftButtonUp)
}

void CMainGameWindow::leftButtonDoubleClick(const Point &mousePos) {
	if (!isMouseControlEnabled())
		return;

	HANDLE_MESSAGE(leftButtonDoubleClick)
}

void CMainGameWindow::middleButtonDown(const Point &mousePos) {
	if (!isMouseControlEnabled())
		return;

	if ((_vm->_events->getTicksCount() - _priorMiddleDownTime) < DOUBLE_CLICK_TIME) {
		_priorMiddleDownTime = 0;
		middleButtonDoubleClick(mousePos);
	} else {
		_priorMiddleDownTime = _vm->_events->getTicksCount();
		HANDLE_MESSAGE(middleButtonDown)
	}
}

void CMainGameWindow::middleButtonUp(const Point &mousePos) {
	if (!isMouseControlEnabled())
		return;

	HANDLE_MESSAGE(middleButtonUp)
}

void CMainGameWindow::middleButtonDoubleClick(const Point &mousePos) {
	if (!isMouseControlEnabled())
		return;

	HANDLE_MESSAGE(middleButtonDoubleClick)
}

void CMainGameWindow::mouseWheel(const Point &mousePos, bool wheelUp) {
	if (!isMouseControlEnabled())
		return;

	_gameManager->_inputTranslator.mouseWheel(wheelUp, mousePos);
	mouseChanged();
}

void CMainGameWindow::keyDown(Common::KeyState keyState) {
	if (keyState.keycode == Common::KEYCODE_d && (keyState.flags & Common::KBD_CTRL)) {
		// Attach to the debugger
		_vm->_debugger->attach();
		_vm->_debugger->onFrame();

	} else if (keyState.keycode == Common::KEYCODE_c && (keyState.flags & Common::KBD_CTRL)) {
		// Cheat action
		if (_project && g_vm->canLoadGameStateCurrently()) {
			CViewItem *newView = _project->parseView("Cheat Room.Node 1.Cheat Rooms View");
			_gameManager->_gameState.changeView(newView, nullptr);
		}

	} else if (keyState.keycode == Common::KEYCODE_F5) {
		// Show the GMM save dialog
		g_vm->showScummVMSaveDialog();
	} else if (keyState.keycode == Common::KEYCODE_F7) {
		// Show the GMM load dialog
		g_vm->showScummVMRestoreDialog();
	} else if (_inputAllowed) {
		_gameManager->_inputTranslator.keyDown(keyState);
	}
}

bool CMainGameWindow::isMouseControlEnabled() const {
	if (!_gameManager)
		return false;

	CScreenManager *screenMan = CScreenManager::_screenManagerPtr;
	if (!screenMan || !screenMan->_mouseCursor)
		return true;

	return screenMan->_mouseCursor->_inputEnabled;
}

} // End of namespace Titanic
