/* 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.
 *
 */

/*
 * This code is based on original Hugo Trilogy source code
 *
 * Copyright (c) 1989-1995 David P. Gray
 *
 */

// mouse.cpp : Handle all mouse activity

#include "common/debug.h"
#include "common/system.h"

#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/mouse.h"
#include "hugo/schedule.h"
#include "hugo/display.h"
#include "hugo/inventory.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/object.h"
#include "hugo/text.h"

namespace Hugo {

MouseHandler::MouseHandler(HugoEngine *vm) : _vm(vm) {
	_hotspots = nullptr;
	_leftButtonFl  = false;
	_rightButtonFl = false;
	_jumpExitFl = false;                            // Can't jump to a screen exit
	_mouseX = kXPix / 2;
	_mouseY = kYPix / 2;
}

void MouseHandler::resetLeftButton() {
	_leftButtonFl = false;
}

void MouseHandler::resetRightButton() {
	_rightButtonFl = false;
}

void MouseHandler::setLeftButton() {
	_leftButtonFl = true;
}

void MouseHandler::setRightButton() {
	_rightButtonFl = true;
}

void MouseHandler::setJumpExitFl(bool fl) {
	_jumpExitFl = fl;
}

void MouseHandler::setMouseX(int x) {
	_mouseX = x;
}

void MouseHandler::setMouseY(int y) {
	_mouseY = y;
}

void MouseHandler::freeHotspots() {
	free(_hotspots);
	_hotspots = nullptr;
}

bool MouseHandler::getJumpExitFl() const {
	return _jumpExitFl;
}

int MouseHandler::getMouseX() const {
	return _mouseX;
}

int MouseHandler::getMouseY() const {
	return _mouseY;
}

int16 MouseHandler::getDirection(const int16 hotspotId) const {
	return _hotspots[hotspotId]._direction;
}

int16 MouseHandler::getHotspotActIndex(const int16 hotspotId) const {
	return _hotspots[hotspotId]._actIndex;
}

/**
 * Shadow-blit supplied string into dib_a at cx,cy and add to display list
 */
void MouseHandler::cursorText(const char *buffer, const int16 cx, const int16 cy, const Uif fontId, const int16 color) {
	debugC(1, kDebugMouse, "cursorText(%s, %d, %d, %d, %d)", buffer, cx, cy, fontId, color);

	_vm->_screen->loadFont(fontId);

	// Find bounding rect for string
	int16 sdx = _vm->_screen->stringLength(buffer);
	int16 sdy = _vm->_screen->fontHeight() + 1;     // + 1 for shadow
	int16 sx, sy;
	if (cx < kXPix / 2) {
		sx = cx + kCursorNameOffX;
		if (_vm->_inventory->getInventoryObjId() == -1) {
			sy = cy + kCursorNameOffY;
		} else {
			sy = cy + kCursorNameOffY - (_vm->_screen->fontHeight() + 1);
			if (sy < 0) {
				sx = cx + kCursorNameOffX + 25;
				sy = cy + kCursorNameOffY;
			}
		}
	} else {
		sx = cx - sdx - kCursorNameOffX / 2;
		sy = cy + kCursorNameOffY;
	}

	if (sy < 0) {
		sy = 0;
	}

	// Display the string and add rect to display list
	_vm->_screen->shadowStr(sx, sy, buffer, _TBRIGHTWHITE);
	_vm->_screen->displayList(kDisplayAdd, sx, sy, sdx, sdy);
}

/**
 * Find the exit hotspot containing cx, cy.
 * Return hotspot index or -1 if not found.
 */
int16 MouseHandler::findExit(const int16 cx, const int16 cy, byte screenId) {
	debugC(2, kDebugMouse, "findExit(%d, %d, %d)", cx, cy, screenId);

	for (int i = 0; _hotspots[i]._screenIndex >= 0; i++) {
		if (_hotspots[i]._screenIndex == screenId) {
			if (cx >= _hotspots[i]._x1 && cx <= _hotspots[i]._x2 && cy >= _hotspots[i]._y1 && cy <= _hotspots[i]._y2)
				return i;
		}
	}
	return -1;
}

/**
 * Process a mouse right click at coord cx, cy over object objid
 */
void MouseHandler::processRightClick(const int16 objId, const int16 cx, const int16 cy) {
	debugC(1, kDebugMouse, "ProcessRightClick(%d, %d, %d)", objId, cx, cy);

	Status &gameStatus = _vm->getGameStatus();

	if (gameStatus._storyModeFl || _vm->_hero->_pathType == kPathQuiet) // Make sure user has control
		return;

	int16 inventObjId = _vm->_inventory->getInventoryObjId();
	// Check if this was over iconbar
	if ((_vm->_inventory->getInventoryState() == kInventoryActive) && (cy < kInvDy + kDibOffY)) { // Clicked over iconbar object
		if (inventObjId == -1)
			_vm->_screen->selectInventoryObjId(objId);
		else if (inventObjId == objId)
			_vm->_screen->resetInventoryObjId();
		else
			_vm->_object->useObject(objId);         // Use status.objid on object
	} else {                                        // Clicked over viewport object
		Object *obj = &_vm->_object->_objects[objId];
		int16 x, y;
		switch (obj->_viewx) {                      // Where to walk to
		case -1: {                                  // Walk to object position
			bool foundFl = false;
			if (_vm->_object->findObjectSpace(obj, &x, &y))
				foundFl = _vm->_route->startRoute(kRouteGet, objId, x, y);  // TRUE if route found to object
			if (!foundFl)                           // Can't get there, try to use from here
				_vm->_object->useObject(objId);
			}
			break;
		case 0:                                     // Immediate use
			_vm->_object->useObject(objId);         // Pick up or use object
			break;
		default:                                    // Walk to view point if possible
			if (!_vm->_route->startRoute(kRouteGet, objId, obj->_viewx, obj->_viewy)) {
				if (_vm->_hero->_cycling == kCycleInvisible) // If invisible do
					_vm->_object->useObject(objId); // immediate use
				else
					Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText)); // Can't get there
			}
			break;
		}
	}
}

/** Process a left mouse click over:
 * 1.  An icon - show description
 * 2.  An object - walk to and show description
 * 3.  An icon scroll arrow - scroll the iconbar
 * 4.  Nothing - attempt to walk there
 * 5.  Exit - walk to exit hotspot
 */
void MouseHandler::processLeftClick(const int16 objId, const int16 cx, const int16 cy) {
	debugC(1, kDebugMouse, "ProcessLeftClick(%d, %d, %d)", objId, cx, cy);

	int16 i, x, y;
	Object *obj;

	Status &gameStatus = _vm->getGameStatus();

	if (gameStatus._storyModeFl || _vm->_hero->_pathType == kPathQuiet) // Make sure user has control
		return;

	switch (objId) {
	case -1:                                        // Empty space - attempt to walk there
		_vm->_route->startRoute(kRouteSpace, 0, cx, cy);
		break;
	case kLeftArrow:                                // A scroll arrow - scroll the iconbar
	case kRightArrow:
		// Scroll the iconbar and display results
		_vm->_inventory->processInventory((objId == kLeftArrow) ? kInventoryActionLeft : kInventoryActionRight);
		_vm->_screen->moveImage(_vm->_screen->getIconBuffer(), 0, 0, kXPix, kInvDy, kXPix, _vm->_screen->getFrontBuffer(), 0, kDibOffY, kXPix);
		_vm->_screen->moveImage(_vm->_screen->getIconBuffer(), 0, 0, kXPix, kInvDy, kXPix, _vm->_screen->getBackBuffer(), 0, kDibOffY, kXPix);
		_vm->_screen->displayList(kDisplayAdd, 0, kDibOffY, kXPix, kInvDy);
		break;
	case kExitHotspot:                              // Walk to exit hotspot
		i = findExit(cx, cy, *_vm->_screenPtr);
		x = _hotspots[i]._viewx;
		y = _hotspots[i]._viewy;
		if (x >= 0) {                               // Hotspot refers to an exit
			// Special case of immediate exit
			if (_jumpExitFl) {
				// Get rid of iconbar if necessary
				if (_vm->_inventory->getInventoryState() != kInventoryOff)
					_vm->_inventory->setInventoryState(kInventoryUp);
				_vm->_scheduler->insertActionList(_hotspots[i]._actIndex);
			} else {    // Set up route to exit spot
				if (_hotspots[i]._direction == Common::KEYCODE_RIGHT)
					x -= kHeroMaxWidth;
				else if (_hotspots[i]._direction == Common::KEYCODE_LEFT)
					x += kHeroMaxWidth;
				if (!_vm->_route->startRoute(kRouteExit, i, x, y))
					Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText)); // Can't get there
			}

			// Get rid of any attached icon
			_vm->_screen->resetInventoryObjId();
		}
		break;
	default:                                        // Look at an icon or object
		obj = &_vm->_object->_objects[objId];

		// Over iconbar - immediate description
		if ((_vm->_inventory->getInventoryState() == kInventoryActive) && (cy < kInvDy + kDibOffY)) {
			_vm->_object->lookObject(obj);
		} else {
			bool foundFl = false;                   // TRUE if route found to object
			switch (obj->_viewx) {                   // Clicked over viewport object
			case -1:                                // Walk to object position
				if (_vm->_object->findObjectSpace(obj, &x, &y))
					foundFl = _vm->_route->startRoute(kRouteLook, objId, x, y);
				if (!foundFl)                       // Can't get there, immediate description
					_vm->_object->lookObject(obj);
				break;
			case 0:                                 // Immediate description
				_vm->_object->lookObject(obj);
				break;
			default:                                // Walk to view point if possible
				if (!_vm->_route->startRoute(kRouteLook, objId, obj->_viewx, obj->_viewy)) {
					if (_vm->_hero->_cycling == kCycleInvisible) // If invisible do
						_vm->_object->lookObject(obj);          // immediate decription
					else
						Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText));  // Can't get there
				}
				break;
			}
		}
		break;
	}
}

/**
 * Process mouse activity
 */
void MouseHandler::mouseHandler() {
	debugC(2, kDebugMouse, "mouseHandler");

	Status &gameStatus = _vm->getGameStatus();
	Istate inventState = _vm->_inventory->getInventoryState();
	if ((gameStatus._viewState != kViewPlay) && (inventState != kInventoryActive))
		return;

	int16 cx = getMouseX();
	int16 cy = getMouseY();

//	gameStatus._cx = cx;                             // Save cursor coords
//	gameStatus._cy = cy;

	// Don't process if outside client area
	if ((cx < 0) || (cx > kXPix) || (cy < kDibOffY) || (cy > kViewSizeY + kDibOffY))
		return;

	int16 objId = -1;                               // Current source object
	// Process cursor over an object or icon
	if (inventState == kInventoryActive) { // Check inventory icon bar first
		objId = _vm->_inventory->processInventory(kInventoryActionGet, cx, cy);
	} else {
		if (cy < 5 && cy > 0) {
			_vm->_topMenu->runModal();
		}
	}

	if (!gameStatus._gameOverFl) {
		if (objId == -1)                            // No match, check rest of view
			objId = _vm->_object->findObject(cx, cy);

		if (objId >= 0) {                           // Got a match
			// Display object name next to cursor (unless CURSOR_NOCHAR)
			// Note test for swapped hero name
			const char *name = _vm->_text->getNoun(_vm->_object->_objects[(objId == kHeroIndex) ? _vm->_heroImage : objId]._nounIndex, kCursorNameIndex);
			if (name[0] != kCursorNochar)
				cursorText(name, cx, cy, U_FONT8, _TBRIGHTWHITE);

			// Process right click over object in view or iconbar
			if (_rightButtonFl)
				processRightClick(objId, cx, cy);
		}

		// Process cursor over an exit hotspot
		if (objId == -1) {
			int i = findExit(cx, cy, *_vm->_screenPtr);
			if (i != -1 && _hotspots[i]._viewx >= 0) {
				objId = kExitHotspot;
				cursorText(_vm->_text->getTextMouse(kMsExit), cx, cy, U_FONT8, _TBRIGHTWHITE);
			}
		}
	}
	// Left click over icon, object or to move somewhere
	if (_leftButtonFl)
		processLeftClick(objId, cx, cy);

	// Clear mouse click states
	resetLeftButton();
	resetRightButton();
}

void MouseHandler::readHotspot(Common::ReadStream &in, Hotspot &hotspot) {
	hotspot._screenIndex = in.readSint16BE();
	hotspot._x1 = in.readSint16BE();
	hotspot._y1 = in.readSint16BE();
	hotspot._x2 = in.readSint16BE();
	hotspot._y2 = in.readSint16BE();
	hotspot._actIndex = in.readUint16BE();
	hotspot._viewx = in.readSint16BE();
	hotspot._viewy = in.readSint16BE();
	hotspot._direction = in.readSint16BE();
}

/**
 * Load hotspots data from hugo.dat
 */
void MouseHandler::loadHotspots(Common::ReadStream &in) {
	Hotspot *wrkHotspots = 0;
	Hotspot tmp;
	memset(&tmp, 0, sizeof(tmp));
	for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
		int numRows = in.readUint16BE();
		if (varnt == _vm->_gameVariant)
			_hotspots = wrkHotspots = (Hotspot *)malloc(sizeof(Hotspot) * numRows);

		for (int i = 0; i < numRows; i++)
			readHotspot(in, (varnt == _vm->_gameVariant) ? wrkHotspots[i] : tmp);
	}
}

/**
 * Display hotspot boundaries for the current screen
 */
void MouseHandler::drawHotspots() const {
	for (int i = 0; _hotspots[i]._screenIndex >= 0; i++) {
		Hotspot *hotspot = &_hotspots[i];
		if (hotspot->_screenIndex == _vm->_hero->_screenIndex)
			_vm->_screen->drawRectangle(false, hotspot->_x1, hotspot->_y1, hotspot->_x2, hotspot->_y2, _TLIGHTRED);
	}
}
} // End of namespace Hugo
