/* 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.
 *
 * $URL$
 * $Id$
 *
 */

// TODO: There is a 'please_hyperwalk' variable that gets accessed that is meant to be global, but
// at the moment it's implemented as a local variable

#include "graphics/cursorman.h"

#include "m4/events.h"
#include "m4/graphics.h"
#include "m4/scene.h"
#include "m4/viewmgr.h"
#include "m4/m4.h"

namespace M4 {

bool please_hyperwalk = false;

/*--------------------------------------------------------------------------*
 * Events																*
 *																			*
 * Implements an interface to the event system								*
 *--------------------------------------------------------------------------*/

Events::Events(MadsM4Engine *vm) : _vm(vm) {
	_mouseState = MSTATE_NO_EVENT;
	quitFlag = false;
	_keyCode = 0;
	_mouseButtons = 0;
	_ctrlFlag = false;

	if (_vm->isM4())
		_console = new M4Console(_m4Vm);
	else
		_console = new MadsConsole(_madsVm);
}

Events::~Events() {
	delete _console;
}

M4EventType Events::handleEvents() {
	static int oldX = -1, oldY = -1;
	static uint32 dclickTime = 0;

	// Handle event types
	while (g_system->getEventManager()->pollEvent(_event)) {
		switch (_event.type) {
		case Common::EVENT_QUIT:
			quitFlag = true;
			break;
		case Common::EVENT_KEYDOWN:
			// Note: The Ctrl-D ScummVM shortcut has been specialised so it will only activate the debugger
			// if it's the first key pressed after the Ctrl key is held down
			if ((_event.kbd.keycode == Common::KEYCODE_LCTRL) || (_event.kbd.keycode == Common::KEYCODE_RCTRL))
				_ctrlFlag = true;

			else if (_event.kbd.hasFlags(Common::KBD_CTRL)) {
				if ((_event.kbd.keycode == Common::KEYCODE_d) && _ctrlFlag) {
					_console->attach();
					_console->onFrame();
				}
				_ctrlFlag = false;
			}
			_keyCode = (int32)_event.kbd.keycode | ((_event.kbd.flags & (Common::KBD_CTRL | Common::KBD_ALT | Common::KBD_SHIFT)) << 24);


			break;
		case Common::EVENT_LBUTTONDOWN:
		case Common::EVENT_LBUTTONUP:
		case Common::EVENT_RBUTTONDOWN:
		case Common::EVENT_RBUTTONUP:
		case Common::EVENT_MBUTTONDOWN:
		case Common::EVENT_MBUTTONUP:
		case Common::EVENT_MOUSEMOVE:
		case Common::EVENT_WHEELUP:
		case Common::EVENT_WHEELDOWN:
			_vm->_mouse->handleEvent(_event);
			break;
		default:
			break;
		}
	}

	_mouseButtons = g_system->getEventManager()->getButtonState();

	// State machine for moving between states
	switch (_mouseState) {
	case MSTATE_NO_EVENT:
		if (_mouseButtons & LEFT_BUTTON_DOWN) {
			if ((dclickTime != 0) && (g_system->getMillis() < dclickTime)) {
				_mouseState = MSTATE_DOUBLECLICK_DOWN;
				dclickTime = 0;
				return MEVENT_DOUBLECLICK;
			}
			dclickTime = 0;
			_mouseState = MSTATE_LEFT_CLICK_DOWN;
			return MEVENT_LEFT_CLICK;
		}
		if (_mouseButtons & RIGHT_BUTTON_DOWN) {
			_mouseState = MSTATE_RIGHT_CLICK_DOWN;
			return MEVENT_RIGHT_CLICK;
		}
		if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) {
			oldX = _event.mouse.x; oldY = _event.mouse.y;
			return MEVENT_MOVE;
		}
		return MEVENT_NO_EVENT;

	case MSTATE_LEFT_CLICK_DOWN:
		if (!(_mouseButtons & LEFT_BUTTON_DOWN)) {
			dclickTime = g_system->getMillis() + 1000 * 15 / 60;
			_mouseState = MSTATE_NO_EVENT;
			return MEVENT_LEFT_RELEASE;
		}
		if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) {
			oldX = _event.mouse.x; oldY = _event.mouse.y;
			return MEVENT_LEFT_DRAG;
		}
		return MEVENT_LEFT_HOLD;

	case MSTATE_RIGHT_CLICK_DOWN:
		if (!(_mouseButtons & RIGHT_BUTTON_DOWN)) {
			_mouseState = MSTATE_NO_EVENT;
			please_hyperwalk = true;
			return MEVENT_RIGHT_RELEASE;
		}
		if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) {
			oldX = _event.mouse.x; oldY = _event.mouse.y;
			return MEVENT_RIGHT_DRAG;
		}
		return MEVENT_RIGHT_HOLD;

	case MSTATE_DOUBLECLICK_DOWN:
		if (!(_mouseButtons & LEFT_BUTTON_DOWN)) {
			_mouseState = MSTATE_NO_EVENT;
			return MEVENT_DOUBLECLICK_RELEASE;
		}
		if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) {
			oldX = _event.mouse.x; oldY = _event.mouse.y;
			return MEVENT_DOUBLECLICK_DRAG;
		}
		return MEVENT_DOUBLECLICK_HOLD;

	default:
		return MEVENT_NO_EVENT;
	}
}

bool Events::kbdCheck(uint32 &keyCode) {
	if (_keyCode == 0)
		return false;

	keyCode = _keyCode;
	_keyCode = 0;
	return true;
}


/*--------------------------------------------------------------------------*
 * Mouse																*
 *																			*
 * Implements an interface to the mouse										*
 *--------------------------------------------------------------------------*/

Mouse::Mouse(MadsM4Engine *vm) : _vm(vm) {
	_locked = false;
	_cursorOn = false;
	_cursor = NULL;
	_cursorSprites = NULL;
	resetMouse();
}

Mouse::~Mouse() {
	delete _cursorSprites;
}

bool Mouse::init(const char *seriesName, RGB8 *palette) {
	Common::SeekableReadStream *stream = _vm->res()->get(seriesName);
	int colorCount = 0;
	RGB8* cursorPalette;

	_cursorSprites = new SpriteAsset(_vm, stream, stream->size(), seriesName);

	// Remove cursor special pixels and set the mouse cursor hotspot in MADS games
	if (!_vm->isM4()) {
		byte *data = NULL;
		for (int i = 0; i < _cursorSprites->getCount(); i++) {
			bool hotSpotSet = false;

			for (int x = 0; x < _cursorSprites->getFrame(i)->width(); x++) {
				for (int y = 0; y < _cursorSprites->getFrame(i)->height(); y++) {
					data = _cursorSprites->getFrame(i)->getBasePtr(x, y);
					if (*data == 1) {
						// It seems that some cursors have more than one hotspot
						// In such a case, the first hotspot seems to set the x and
						// the second one the y hotspot offset
						if (!hotSpotSet) {
							_cursorSprites->getFrame(i)->xOffset = x;
							_cursorSprites->getFrame(i)->yOffset = y;
							hotSpotSet = true;
						} else {
							_cursorSprites->getFrame(i)->yOffset = y;
						}
						*data = 0;
					}
				}	// for y
			}	// for x
		}	// for i
	}

	colorCount = _cursorSprites->getColorCount();
	cursorPalette = _cursorSprites->getPalette();
	_vm->_palette->setPalette(cursorPalette, 0, colorCount);

	//printf("Cursor count: %d\n", _cursorSprites->getCount());

	_vm->res()->toss(seriesName);

	_currentCursor = -1;
	return true;
}

bool Mouse::setCursorNum(int cursorIndex) {
	if ((cursorIndex < 0) || (cursorIndex >= (int)_cursorSprites->getCount()))
		return false;

	_lockedCursor = cursorIndex;
	if (_locked)
		// Cursor is locked, so don't go ahead with changing cursor
		return true;

	_currentCursor = _lockedCursor;
	_cursor = _cursorSprites->getFrame(cursorIndex);

	// Set the cursor to the sprite
	CursorMan.replaceCursor((const byte *)_cursor->getBasePtr(), _cursor->width(), _cursor->height(), _cursor->xOffset, _cursor->yOffset, 0);

	return true;
}

int Mouse::cursorCount() {
	return _cursorSprites->getCount();
}

void Mouse::cursorOn() {
	_cursorOn = true;
	CursorMan.showMouse(!inHideArea());
}

void Mouse::cursorOff() {
	_cursorOn = false;
	CursorMan.showMouse(false);
}

void Mouse::lockCursor(int cursorIndex) {
	_locked = false;
	setCursorNum(cursorIndex);
	_locked = true;
}

void Mouse::unlockCursor() {
	_locked = false;
	setCursorNum(_lockedCursor);
}

const char *Mouse::getVerb() {
	switch (_vm->_mouse->getCursorNum()) {
	case CURSOR_LOOK:
		return "LOOK AT";
	case CURSOR_TAKE:
		return "TAKE";
	case CURSOR_USE:
		return "GEAR";
	default:
		return NULL;
	}
}

void Mouse::resetMouse() {
	_hideRect.left = -1;
	_hideRect.top = -1;
	_hideRect.right = -1;
	_hideRect.bottom = -1;
	_showRect.left = -1;
	_showRect.top = -1;
	_showRect.right = -1;
	_showRect.bottom = -1;
}

void Mouse::setHideRect(Common::Rect &r) {
	_hideRect = r;
}

void Mouse::setShowRect(Common::Rect &r) {
	_showRect = r;
}

const Common::Rect *Mouse::getHideRect() {
	return &_hideRect;
}

const Common::Rect *Mouse::getShowRect() {
	if ((_showRect.top == -1) || (_showRect.left == -1)) {
		// Show rectangle uninitialised - set it to current screen dimensions
		_showRect.top = 0;
		_showRect.left = 0;
		_showRect.right = _vm->_screen->width() - 1;
		_showRect.bottom = _vm->_screen->height() -1;
	}

	return &_showRect;
}

void Mouse::handleEvent(Common::Event &event) {
	_currentPos.x = event.mouse.x;
	_currentPos.y = event.mouse.y;

	// If mouse is turned on, check to see if the position is in the hide rect, or outside the show rect.
	// If so, handle toggling the visibility of the mouse
	bool showFlag = !inHideArea();
	if (_cursorOn && (CursorMan.isVisible() != showFlag)) {
		CursorMan.showMouse(showFlag);
	}
}

bool Mouse::inHideArea() {
	// Returns true if the mouse is inside a specified hide rect, or if a show rect is specified and
	// the mouse is currently outside it
	if ((_currentPos.x >= _hideRect.left) && (_currentPos.x <= _hideRect.right) &&
		(_currentPos.y >= _hideRect.top) && (_currentPos.y <= _hideRect.bottom))
		// Inside a hide area
		return true;


	if ((_showRect.top == -1) && (_showRect.left == -1))
		// No show rect defined
		return false;

	// Return true if the mouse is outside the show area
	return (_currentPos.x < _showRect.left) || (_currentPos.x > _showRect.right) ||
		(_currentPos.y < _showRect.top) || (_currentPos.y > _showRect.bottom);
}

} // End of namespace M4