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

#include "gob/hotspots.h"
#include "gob/global.h"
#include "gob/draw.h"
#include "gob/game.h"
#include "gob/script.h"
#include "gob/inter.h"

namespace Gob {

Hotspots::Hotspot::Hotspot() {
	clear();
}

Hotspots::Hotspot::Hotspot(uint16 i,
		uint16 l, uint16 t, uint16 r, uint16 b, uint16 f, uint16 k,
		uint16 enter, uint16 leave, uint16 pos) {

	id        = i;
	left      = l;
	top       = t;
	right     = r;
	bottom    = b;
	flags     = f;
	key       = k;
	funcEnter = enter;
	funcLeave = leave;
	funcPos   = pos;
	script    = 0;
}

void Hotspots::Hotspot::clear() {
	id        = 0;
	left      = 0xFFFF;
	top       = 0;
	right     = 0;
	bottom    = 0;
	flags     = 0;
	key       = 0;
	funcEnter = 0;
	funcLeave = 0;
	funcPos   = 0;
	script    = 0;
}

Hotspots::Type Hotspots::Hotspot::getType() const {
	return (Type) (flags & 0xF);
}

MouseButtons Hotspots::Hotspot::getButton() const {
	uint8 buttonBits = ((flags & 0x70) >> 4);

	if (buttonBits == 0)
		return kMouseButtonsLeft;
	if (buttonBits == 1)
		return kMouseButtonsRight;
	if (buttonBits == 2)
		return kMouseButtonsAny;

	return kMouseButtonsNone;
}

uint16 Hotspots::Hotspot::getWindow() const {
	return (flags & 0x0F00);
}

uint8 Hotspots::Hotspot::getCursor() const {
	return (flags & 0xF000) >> 12;
}

uint8 Hotspots::Hotspot::getState(uint16 id) {
	return (id & 0xF000) >> 12;
}

uint8 Hotspots::Hotspot::getState() const {
	return getState(id);
}

bool Hotspots::Hotspot::isEnd() const {
	return (left == 0xFFFF);
}

bool Hotspots::Hotspot::isInput() const {
	if (getType() < kTypeInput1NoLeave)
		return false;

	if (getType() > kTypeInputFloatLeave)
		return false;

	return true;
}

bool Hotspots::Hotspot::isActiveInput() const {
	if (isEnd())
		return false;

	if (!isFilledEnabled())
		return false;

	if (!isInput())
		return false;

	return true;
}

bool Hotspots::Hotspot::isInputLeave() const {
	if (!isInput())
		return false;

	if (!(getType() & 1))
		return true;

	return false;
}

bool Hotspots::Hotspot::isFilled() const {
	return getState() & kStateFilled;
}

bool Hotspots::Hotspot::isFilledEnabled() const {
	return (getState() & kStateFilledDisabled) == kStateFilled;
}

bool Hotspots::Hotspot::isFilledNew() const {
	return getState() == kStateFilled;
}

bool Hotspots::Hotspot::isDisabled() const {
	return getState() & kStateDisabled;
}

bool Hotspots::Hotspot::isIn(uint16 x, uint16 y) const {
	// FIXME: the cast to int16 is a hack, to fix handling of Gob2 problems related to
	// hotspots with negative offset (to temporary disable them).
	if ((int16) x < (int16) left)
		return false;
	if ((int16) x > (int16) right)
		return false;
	if ((int16) y < (int16) top)
		return false;
	if ((int16) y > (int16) bottom)
		return false;

	return true;
}

bool Hotspots::Hotspot::buttonMatch(MouseButtons button) const {
	MouseButtons myButton = getButton();

	if (myButton == kMouseButtonsAny)
		// Any button allowed
		return true;

	if (myButton == kMouseButtonsNone)
		// No button allowed
		return false;

	if (myButton == button)
		// Exact match
		return true;

	return false;
}

void Hotspots::Hotspot::disable() {
	id |= (kStateDisabled << 12);
}

void Hotspots::Hotspot::enable() {
	id &= ~(kStateDisabled << 12);
}


Hotspots::Hotspots(GobEngine *vm) : _vm(vm) {
	_hotspots = new Hotspot[kHotspotCount];

	_shouldPush = false;

	_currentKey   = 0;
	_currentIndex = 0;
	_currentId    = 0;
	_currentX     = 0;
	_currentY     = 0;
}

Hotspots::~Hotspots() {
	delete[] _hotspots;

	// Pop the whole stack and free each element's memory
	while (!_stack.empty()) {

		StackEntry backup = _stack.pop();

		delete[] backup.hotspots;
	}
}

void Hotspots::clear() {
	_currentKey = 0;

	for (int i = 0; i < kHotspotCount; i++)
		_hotspots[i].clear();
}

uint16 Hotspots::add(uint16 id,
		uint16 left,  uint16 top, uint16 right, uint16 bottom,
		uint16 flags, uint16 key,
		uint16 funcEnter, uint16 funcLeave, uint16 funcPos) {

	Hotspot hotspot(id, left, top, right, bottom,
			flags, key, funcEnter, funcLeave, funcPos);

	return add(hotspot);
}

uint16 Hotspots::add(const Hotspot &hotspot) {
	for (int i = 0; i < kHotspotCount; i++) {
		Hotspot &spot = _hotspots[i];

		//     free space => add    same id => update
		if (! (spot.isEnd() || (spot.id == hotspot.id)))
			continue;

		// When updating, keep disabled state intact
		uint16 id = hotspot.id;
		if ((spot.id     & ~(kStateDisabled << 12)) ==
		     (hotspot.id & ~(kStateDisabled << 12)))
			id = spot.id;

		// Set
		spot    = hotspot;
		spot.id = id;

		// Remember the current script
		spot.script = _vm->_game->_script;

		debugC(1, kDebugHotspots, "Adding hotspot %03d: Coord:%3d+%3d+%3d+%3d - id:%04X, key:%04X, flag:%04X - fcts:%5d, %5d, %5d",
				i, spot.left, spot.top, spot.right, spot.bottom,
				spot.id, spot.key, spot.flags, spot.funcEnter, spot.funcLeave, spot.funcPos);

		return i;
	}

	error("Hotspots::add(): Hotspot array full");
	return 0xFFFF;	// for compilers that don't support NORETURN
}

void Hotspots::remove(uint16 id) {
	for (int i = 0; i < kHotspotCount; i++) {
		if (_hotspots[i].id == id) {
			debugC(1, kDebugHotspots, "Removing hotspot %d: %X", i, id);
			_hotspots[i].clear();
		}
	}
}

void Hotspots::removeState(uint8 state) {
	for (int i = 0; i < kHotspotCount; i++) {
		Hotspot &spot = _hotspots[i];

		if (spot.getState() == state) {
			debugC(1, kDebugHotspots, "Removing hotspot %d: %X (by state %X)", i, spot.id, state);
			spot.clear();
		}
	}
}

void Hotspots::recalculate(bool force) {
	debugC(5, kDebugHotspots, "Recalculating hotspots");

	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		Hotspot &spot = _hotspots[i];

		if (!force && ((spot.flags & 0x80) != 0))
			// Not forcing a special hotspot
			continue;

		if (spot.funcPos == 0)
			// Simple coordinates don't need update
			continue;

		// Setting the needed script
		Script *curScript = _vm->_game->_script;

		_vm->_game->_script = spot.script;
		if (!_vm->_game->_script)
			_vm->_game->_script = curScript;

		// Calling the function that contains the positions
		_vm->_game->_script->call(spot.funcPos);

		// Calculate positions
		int16 left   = _vm->_game->_script->readValExpr();
		int16 top    = _vm->_game->_script->readValExpr();
		int16 width  = _vm->_game->_script->readValExpr();
		int16 height = _vm->_game->_script->readValExpr();

		// Re-read the flags too, if applicable
		uint16 flags = 0;
		if (spot.getState() == (kStateFilled | kStateType2))
			flags = _vm->_game->_script->readValExpr();

		// Apply backDelta, if needed
		if ((_vm->_draw->_renderFlags & RENDERFLAG_CAPTUREPOP) && (left != -1)) {
			left += _vm->_draw->_backDeltaX;
			top  += _vm->_draw->_backDeltaY;
		}

		// Clamping
		if (left < 0) {
			width += left;
			left   = 0;
		}
		if (top < 0) {
			height += top;
			top     = 0;
		}

		// Set the updated position
		spot.left   = left;
		spot.top    = top;
		spot.right  = left + width  - 1;
		spot.bottom = top  + height - 1;

		if (spot.getState() == (kStateFilled | kStateType2))
			spot.flags = flags;

		// Return
		_vm->_game->_script->pop();

		_vm->_game->_script = curScript;
	}
}

void Hotspots::push(uint8 all, bool force) {
	debugC(1, kDebugHotspots, "Pushing hotspots (%d, %d)", all, force);

	// Should we push at all?
	if (!_shouldPush && !force)
		return;

	// Count the hotspots
	uint32 size = 0;
	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		Hotspot &spot = _hotspots[i];

		     // Save all of them
		if ( (all == 1) ||
		     // Don't save the global ones
		    ((all == 0) && (spot.id >= 20)) ||
		     // Only save disabled ones
		    ((all == 2) && ((spot.getState() == (kStateFilledDisabled | kStateType1)) ||
		                    (spot.getState() == (kStateDisabled)) ||
		                    (spot.getState() == (kStateFilledDisabled | kStateType2))))) {
			size++;
		}

	}

	StackEntry backup;

	backup.shouldPush = _shouldPush;
	backup.size       = size;
	backup.key        = _currentKey;
	backup.id         = _currentId;
	backup.index      = _currentIndex;
	backup.x          = _currentX;
	backup.y          = _currentY;

	backup.hotspots = new Hotspot[size];

	// Copy the hotspots
	Hotspot *destPtr = backup.hotspots;
	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		Hotspot &spot = _hotspots[i];

		     // Save all of them
		if ( (all == 1) ||
		     // Don't save the global ones
		    ((all == 0) && (spot.id >= 20)) ||
		     // Only save disabled ones
		    ((all == 2) && ((spot.getState() == (kStateFilledDisabled | kStateType1)) ||
		                    (spot.getState() == (kStateDisabled)) ||
		                    (spot.getState() == (kStateFilledDisabled | kStateType2))))) {

			memcpy(destPtr, &spot, sizeof(Hotspot));
			destPtr++;

			spot.clear();
		}

	}

	// Reset current state
	_shouldPush   = false;
	_currentKey   = 0;
	_currentId    = 0;
	_currentIndex = 0;
	_currentX     = 0;
	_currentY     = 0;

	_stack.push(backup);
}

void Hotspots::pop() {
	debugC(1, kDebugHotspots, "Popping hotspots");

	assert(!_stack.empty());

	StackEntry backup = _stack.pop();

	// Find the end of the filled hotspot space
	int i;
	Hotspot *destPtr = _hotspots;
	for (i = 0; i < kHotspotCount; i++, destPtr++) {
		if (destPtr->isEnd())
			break;
	}

	if (((uint32) (kHotspotCount - i)) < backup.size)
		error("Hotspots::pop(): Not enough free space in the current Hotspot "
		      "array to pop %d elements (got %d)", backup.size, kHotspotCount - i);

	// Copy
	memcpy(destPtr, backup.hotspots, backup.size * sizeof(Hotspot));

	_shouldPush   = backup.shouldPush;
	_currentKey   = backup.key;
	_currentId    = backup.id;
	_currentIndex = backup.index;
	_currentX     = backup.x;
	_currentY     = backup.y;

	delete[] backup.hotspots;
}

bool Hotspots::isValid(uint16 key, uint16 id, uint16 index) const {
	if (index >= kHotspotCount)
		return false;

	if (key == 0)
		return false;

	if (!(Hotspot::getState(id) & kStateFilled))
		return false;

	return true;
}

void Hotspots::call(uint16 offset) {
	debugC(4, kDebugHotspots, "Calling hotspot function %d", offset);

	_vm->_game->_script->call(offset);

	_shouldPush = true;

	Common::Stack<StackEntry>::size_type stackSize = _stack.size();

	_vm->_inter->funcBlock(0);

	while (stackSize != _stack.size())
		pop();

	_shouldPush = false;

	_vm->_game->_script->pop();

	recalculate(false);
}

void Hotspots::enter(uint16 index) {
	debugC(2, kDebugHotspots, "Entering hotspot %d", index);

	if (index >= kHotspotCount) {
		warning("Hotspots::enter(): Index %d out of range", index);
		return;
	}

	Hotspot &spot = _hotspots[index];

	// If requested, write the ID into a variable
	if ((spot.getState() == (kStateFilled | kStateType1)) ||
	    (spot.getState() == (kStateFilled | kStateType2)))
		WRITE_VAR(17, -(spot.id & 0x0FFF));

	_currentX = _vm->_global->_inter_mouseX;
	_currentY = _vm->_global->_inter_mouseY;

	if (spot.funcEnter != 0)
		call(spot.funcEnter);
}

void Hotspots::leave(uint16 index) {
	debugC(2, kDebugHotspots, "Leaving hotspot %d", index);

	if (index >= kHotspotCount) {
		warning("Hotspots::leave(): Index %d out of range", index);
		return;
	}

	Hotspot &spot = _hotspots[index];

	// If requested, write the ID into a variable
	if ((spot.getState() == (kStateFilled | kStateType1)) ||
	    (spot.getState() == (kStateFilled | kStateType2)))
		WRITE_VAR(17, spot.id & 0x0FFF);

	if (spot.funcLeave != 0)
		call(spot.funcLeave);
}

int16 Hotspots::windowCursor(int16 &dx, int16 &dy) const {
	if (!(_vm->_draw->_renderFlags & RENDERFLAG_HASWINDOWS))
		return 0;

	for (int i = 0; i < 10; i++) {
		if (_vm->_draw->_fascinWin[i].id == -1)
			// No such windows
			continue;

		const int left   = _vm->_draw->_fascinWin[i].left;
		const int top    = _vm->_draw->_fascinWin[i].top;
		const int right  = _vm->_draw->_fascinWin[i].left + _vm->_draw->_fascinWin[i].width  - 1;
		const int bottom = _vm->_draw->_fascinWin[i].top  + _vm->_draw->_fascinWin[i].height - 1;

		if ((_vm->_global->_inter_mouseX < left) || (_vm->_global->_inter_mouseX > right) ||
		    (_vm->_global->_inter_mouseY < top ) || (_vm->_global->_inter_mouseY > bottom))
			// We're not inside that window
			continue;

		if (_vm->_draw->_fascinWin[i].id != (_vm->_draw->_winCount - 1))
			// Only consider the top-most window
			continue;

		dx = _vm->_draw->_fascinWin[i].left;
		dy = _vm->_draw->_fascinWin[i].top;

		if ((_vm->_global->_inter_mouseX < (left + 12)) && (_vm->_global->_inter_mouseY < (top + 12)) &&
		    (VAR((_vm->_draw->_winVarArrayStatus / 4) + i) & 2))
			// Cursor on 'Close Window'
			return 5;

		if ((_vm->_global->_inter_mouseX > (right - 12)) & (_vm->_global->_inter_mouseY < (top + 12)) &&
		    (VAR((_vm->_draw->_winVarArrayStatus / 4) + i) & 4))
			// Cursor on 'Move Window'
			return 6;

		return -1;
	}

	return 0;
}

uint16 Hotspots::checkMouse(Type type, uint16 &id, uint16 &index) const {
	id    = 0;
	index = 0;

	int16 dx = 0;
	int16 dy = 0;
	int16 winId = _vm->_draw->getWinFromCoord(dx, dy);

	if (winId < 0) {
		winId = 0;
		dx = 0;
		dy = 0;
	} else
		winId *= 256;

	if (type == kTypeMove) {
		// Check where the mouse was moved to

		for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
			const Hotspot &spot = _hotspots[i];

			if (spot.isDisabled())
				// Only consider enabled hotspots
				continue;

			if (spot.getType() > kTypeMove)
				// Only consider click and move hotspots
				continue;

			if (spot.getWindow() != winId)
				// Only check the current window
				continue;

			if (!spot.isIn(_vm->_global->_inter_mouseX - dx, _vm->_global->_inter_mouseY - dy))
				// If we're not in it, ignore it
				continue;

			id    = spot.id;
			index = i;

			return spot.key;
		}

		return 0;

	} else if (type == kTypeClick) {
		// Check if something was clicked

		for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
			const Hotspot &spot = _hotspots[i];

			if (spot.isDisabled())
				// Only consider enabled hotspots
				continue;

			if (spot.getWindow() != winId)
				// Only check the active window
				continue;

			if (spot.getType() < kTypeMove)
				// Only consider hotspots that can be clicked
				continue;

			if (!spot.isIn(_vm->_global->_inter_mouseX - dx, _vm->_global->_inter_mouseY - dy))
				// If we're not in it, ignore it
				continue;

			if (!spot.buttonMatch(_vm->_game->_mouseButtons))
				// Don't follow hotspots with button requirements we don't meet
				continue;

			id    = spot.id;
			index = i;

			if ((spot.getType() == kTypeMove) || (spot.getType() == kTypeClick))
				// It's a move or click => return the key
				return spot.key;

			// Otherwise, the key has a different meaning, so ignore it
			return 0;
		}

		if (_vm->_game->_mouseButtons != kMouseButtonsLeft)
			// Let the right mouse button act as an escape key
			return kKeyEscape;

		return 0;
	}

	return 0;
}

bool Hotspots::checkHotspotChanged() {
	uint16 key, id, index;

	// Get the current hotspot
	key = checkMouse(kTypeMove, id, index);

	uint16 mouseX = _vm->_global->_inter_mouseX;
	uint16 mouseY = _vm->_global->_inter_mouseY;

	if (key == _currentKey) {
		// Still the same hotspot, just update the mouse position

		_currentX = mouseX;
		_currentY = mouseY;
		return false;
	}

	// In Geisha, no move hotspot changes should occur when
	// we didn't actually move the mouse
	if (_vm->getGameType() == kGameTypeGeisha)
		if ((mouseX == _currentX) && (mouseY == _currentY))
			return false;

	// Leave the old area
	if (isValid(_currentKey, _currentId,_currentIndex))
		leave(_currentIndex);

	_currentKey   = key;
	_currentId    = id;
	_currentIndex = index;
	_currentX     = mouseX;
	_currentY     = mouseY;

	// Enter the new one
	if (isValid(key, id, index))
		enter(index);

	return true;
}

uint16 Hotspots::check(uint8 handleMouse, int16 delay, uint16 &id, uint16 &index) {
	if (delay >= -1) {
		_currentKey   = 0;
		_currentId    = 0;
		_currentIndex = 0;
	}

	id    = 0;
	index = 0;

	if (handleMouse) {
		if ((_vm->_draw->_cursorIndex == -1) && (_currentKey == 0)) {
			// Last know state: No hotspot hit. Look if that changed

			_currentKey = checkMouse(kTypeMove, _currentId, _currentIndex);

			if (isValid(_currentKey, _currentId, _currentIndex))
				enter(_currentIndex);
		}
		_vm->_draw->animateCursor(-1);
	}

	uint32 startTime = _vm->_util->getTimeKey();

	// Update display
	_vm->_draw->blitInvalidated();
	_vm->_video->waitRetrace();

	uint16 key = 0;
	while (key == 0) {

		if (_vm->_inter->_terminate || _vm->shouldQuit()) {
			if (handleMouse)
				_vm->_draw->blitCursor();
			return 0;
		}

		// Anything changed?
		checkHotspotChanged();

		// Update display
		if (!_vm->_draw->_noInvalidated) {
			if (handleMouse)
				_vm->_draw->animateCursor(-1);
			else
				_vm->_draw->blitInvalidated();
			_vm->_video->waitRetrace();
		}

		if (handleMouse)
			_vm->_game->evaluateScroll();

		// Update keyboard and mouse state
		key = _vm->_game->checkKeys(&_vm->_global->_inter_mouseX,
				&_vm->_global->_inter_mouseY, &_vm->_game->_mouseButtons, handleMouse);

		if (!handleMouse && (_vm->_game->_mouseButtons != kMouseButtonsNone)) {
			// We don't want any mouse input but got one => Wait till it went away

			_vm->_util->waitMouseRelease(0);
			key = 3;
		}

		if (key != 0) {
			// Got a key press

			if (handleMouse & 1)
				_vm->_draw->blitCursor();

			id    = 0;
			index = 0;

			// Leave the current hotspot
			if (isValid(_currentKey, _currentId, _currentIndex))
				leave(_currentIndex);

			_currentKey = 0;
			break;
		}

		if (handleMouse) {

			if (_vm->_game->_mouseButtons != kMouseButtonsNone) {
				// Mouse button pressed
				int i = _vm->_draw->handleCurWin();

				if (!i) {
					_vm->_draw->animateCursor(2);
					if (delay > 0) {
						// If a delay was requested, wait the specified time
						_vm->_util->delay(delay);
					} else if (handleMouse & 1)
						_vm->_util->waitMouseRelease(1);

					_vm->_draw->animateCursor(-1);

					// Which region was clicked?
					key = checkMouse(kTypeClick, id, index);

					if ((key != 0) || (id != 0)) {
						// Got a valid region

						if ( (handleMouse & 1) &&
							  ((delay <= 0) || (_vm->_game->_mouseButtons == kMouseButtonsNone)))
							_vm->_draw->blitCursor();


						if ((key != _currentKey) && (_vm->getGameType() != kGameTypeFascination) &&
						                            (_vm->getGameType() != kGameTypeGeisha))
						// If the hotspot changed, leave the old one
						// Code not present in Fascination executables
								leave(_currentIndex);

						_currentKey = 0;
						break;
					}

					if (handleMouse & 4)
						// Nothing further than one simple check was requested => return
						return 0;

					// Leave the current area
					if (_currentKey != 0)
						leave(_currentIndex);

					// No click, but do we have a move event? If so, enter that hotspot
					_currentKey = checkMouse(kTypeMove, _currentId, _currentIndex);
					if (isValid(_currentKey, _currentId, _currentIndex))
						enter(_currentIndex);
				} else {
					WRITE_VAR(16, (int32)i);
					id = 0;
					index = 0;
					return 0;
				}
			} else
				// No mouse button pressed, check whether the position changed at least
				checkHotspotChanged();
		}

		if ((delay == -2) && (key == 0) &&
		    (_vm->_game->_mouseButtons == kMouseButtonsNone)) {
			// Nothing found and no further handling requested. Return.

			id    = 0;
			index = 0;
			break;
		}

		if (handleMouse)
			_vm->_draw->animateCursor(-1);

		if ((delay < 0) && (key == 0) &&
		    (_vm->_game->_mouseButtons == kMouseButtonsNone)) {

			// Look if we've maybe reached the timeout

			uint32 curTime = _vm->_util->getTimeKey();
			if ((curTime + delay) > startTime) {
				// If so, return

				id    = 0;
				index = 0;
				break;
			}

		}

	// Sleep for a short amount of time
	_vm->_util->delay(10);

	}

	return key;
}

uint16 Hotspots::check(uint8 handleMouse, int16 delay) {
	uint16 id, index;

	// Check and ignore the id and index
	return Hotspots::check(handleMouse, delay, id, index);
}

uint16 Hotspots::updateInput(uint16 xPos, uint16 yPos, uint16 width, uint16 height,
		uint16 backColor, uint16 frontColor, char *str, uint16 fontIndex,
		Type type, int16 &duration, uint16 &id, uint16 &index) {

	if ((fontIndex >= Draw::kFontCount) || !_vm->_draw->_fonts[fontIndex]) {
		warning("Hotspots::updateInput(): Invalid font specified: %d", fontIndex);
		return 0;
	}

	// Check if we need to consider mouse events
	bool handleMouse = false;
	if ( (_vm->_game->_handleMouse != 0) &&
	    ((_vm->_global->_useMouse != 0) || (_vm->_game->_forceHandleMouse != 0)))
		handleMouse = true;

	const Font &font = *_vm->_draw->_fonts[fontIndex];

	// Current position in the string, preset to the end
	uint32 pos      = strlen(str);
	/* Size of input field in characters.
	 * If the font is not monospaced, we can't know that */
	uint32 editSize = font.isMonospaced() ? (width / font.getCharWidth()) : 0;

	uint16 key = 0;
	char tempStr[256];

	while (1) {
		// If we the edit field has enough space, add a space for the new character
		Common::strlcpy(tempStr, str, 255);
		strcat(tempStr, " ");
		if ((editSize != 0) && strlen(tempStr) > editSize)
			Common::strlcpy(tempStr, str, 256);

		// Clear input area
		fillRect(xPos, yPos,
		         font.isMonospaced() ? (editSize * font.getCharWidth()) : width, height,
		         backColor);

		// Print the current string, vertically centered
		printText(xPos, yPos + (height - font.getCharHeight()) / 2,
				tempStr, fontIndex, frontColor);

		// If we've reached the end of the input field, set the cursor to the last character
		if ((editSize != 0) && (pos == editSize))
			pos--;

		// The character under the cursor
		char curSym = tempStr[pos];

		if (_vm->_inter->_variables)
			WRITE_VAR(56, pos);

		bool first = true;
		while (1) {
			tempStr[0] = curSym;
			tempStr[1] = 0;

			// Draw cursor
			uint16 cursorX, cursorY, cursorWidth, cursorHeight;
			getTextCursorPos(font, str, pos, xPos, yPos, width, height,
					cursorX, cursorY, cursorWidth, cursorHeight);
			fillRect(cursorX, cursorY, cursorWidth, cursorHeight, frontColor);

			if (first) {
				// The first time, purge old information too
				key = check(handleMouse, -1, id, index);

				if (key == 0)
					// We didn't catch any input, let's try again with a real timeout
					key = check(handleMouse, -300, id, index);

				first = false;
			} else
				// Try to catch a character
				key = check(handleMouse, -300, id, index);

			tempStr[0] = curSym;
			tempStr[1] = 0;

			// Clear cursor
			getTextCursorPos(font, str, pos, xPos, yPos, width, height,
					cursorX, cursorY, cursorWidth, cursorHeight);
			fillRect(cursorX, cursorY, cursorWidth, cursorHeight, backColor);

			// Print the current string, vertically centered
			printText(cursorX, yPos + (height - font.getCharHeight()) / 2,
					tempStr, fontIndex, frontColor);

			if ((key != 0) || (id != 0))
				// We did get a key, stop looking
				break;

			// Try again
			key = check(handleMouse, -300, id, index);

			if ((key != 0) || (id != 0) ||
					_vm->_inter->_terminate || _vm->shouldQuit())
				// We did get a key, stop looking
				break;

			if (duration > 0) {
				// Look if we reached the time limit
				duration -= 600;
				if (duration <= 1) {
					// If so, abort
					key = 0;
					id  = 0;
					break;
				}
			}
		}

		if ((key == 0) || (id != 0) ||
				_vm->_inter->_terminate || _vm->shouldQuit())
			// Got no key, or a region ID instead, return
			return 0;

		switch (key) {
		case kKeyRight:
			// If possible, move the cursor right
			if (((editSize != 0) && ((pos > strlen(str)) || (pos > (editSize - 1)))) ||
			    ((editSize == 0) && (pos > strlen(str)))) {
				pos++;
				continue;
			}
			// Continue downwards instead
			return kKeyDown;

		case kKeyLeft:
			// If possible, move the cursor left
			if (pos > 0) {
				pos--;
				continue;
			}
			// Continue upwards instead
			return kKeyUp;

		case kKeyBackspace:
			if (pos > 0) {
				// Delete the character to the left
				_vm->_util->cutFromStr(str, pos - 1, 1);
				pos--;
			} else {
				if (pos < strlen(str))
					// Delete the character to the right
					_vm->_util->cutFromStr(str, pos, 1);
			}
			continue;

		case kKeyDelete:
			if (pos >= strlen(str))
				continue;

			// Delete the character to the right
			_vm->_util->cutFromStr(str, pos, 1);
			continue;

		case kKeyReturn:
		case kKeyF1:
		case kKeyF2:
		case kKeyF3:
		case kKeyF4:
		case kKeyF5:
		case kKeyF6:
		case kKeyF7:
		case kKeyF8:
		case kKeyF9:
		case kKeyF10:
		case kKeyUp:
		case kKeyDown:
			return key;

		case kKeyEscape:
			// If we got an escape event, wait until the mouse buttons have been released
			if (_vm->_global->_useMouse != 0)
				continue;

			_vm->_game->_forceHandleMouse = !_vm->_game->_forceHandleMouse;

			handleMouse = false;
			if ( (_vm->_game->_handleMouse != 0) &&
			    ((_vm->_global->_useMouse != 0) || (_vm->_game->_forceHandleMouse != 0)))
				handleMouse = true;

			while (_vm->_global->_pressedKeys[1] != 0)
				;
			continue;

		default:
			// Got a "normal" key

			uint16 savedKey = key;

			key &= 0xFF;

			if (((type == kTypeInputFloatNoLeave) || (type == kTypeInputFloatLeave)) &&
					 (key >= ' ') && (key <= 0xFF)) {

				// Only allow character found in numerical floating values

				const char *str1 = "0123456789-.,+ ";
				const char *str2 = "0123456789-,,+ ";

				if ((((savedKey >> 8) > 1) && ((savedKey >> 8) < 12)) &&
						((_vm->_global->_pressedKeys[42] != 0) ||
						 (_vm->_global->_pressedKeys[56] != 0)))
					key = ((savedKey >> 8) - 1) % 10 + '0';

				int i;
				for (i = 0; str1[i] != 0; i++) {
					if (key == str1[i]) {
						key = str2[i];
						break;
					}
				}

				if (i == (int16) strlen(str1))
					key = 0;
			}

			if ((key >= ' ') && (key <= 0xFF)) {
				if (editSize == 0) {
					// Length of the string + current character + next one
					int length = _vm->_draw->stringLength(str, fontIndex) +
						font.getCharWidth(' ') + font.getCharWidth(key);

					if (length > width)
						// We're above the limit, ignore the key
						continue;

					if (((int32) strlen(str)) >= (_vm->_global->_inter_animDataSize * 4 - 1))
						// Above the limit of character allowed in a string, ignore the key
						continue;

				} else {
					if (strlen(str) > editSize)
						// We're over the upper character limit for this field
						continue;
					else if (editSize == strlen(str))
						// We've reached the upper limit, overwrite the last character
						_vm->_util->cutFromStr(str, strlen(str) - 1, 1);
				}

				// Advance cursor
				pos++;
				tempStr[0] = key;
				tempStr[1] = 0;

				// Add character
				_vm->_util->insertStr(tempStr, str, pos - 1);
			}
		}
	}
}

uint16 Hotspots::handleInputs(int16 time, uint16 inputCount, uint16 &curInput,
		InputDesc *inputs, uint16 &id, uint16 &index) {

	// Redraw all texts in all inputs we currently manage
	updateAllTexts(inputs);

	for (int i = 0; i < 40; i++)
		WRITE_VAR(17 + i, 0);

	while (1) {
		// Find the hotspot index to our current input
		uint16 hotspotIndex = inputToHotspot(curInput);

		assert(hotspotIndex != 0xFFFF);

		Hotspot inputSpot = _hotspots[hotspotIndex];

		// Handle input events from that input field
		uint16 key = updateInput(inputSpot.left, inputSpot.top,
				inputSpot.right - inputSpot.left + 1,
				inputSpot.bottom - inputSpot.top + 1,
				inputs[curInput].backColor, inputs[curInput].frontColor,
				GET_VARO_STR(inputSpot.key), inputs[curInput].fontIndex,
				inputSpot.getType(), time, id, index);

		if (_vm->_inter->_terminate)
			return 0;

		switch (key) {
		case kKeyNone:
			if (id == 0)
				// No key and no hotspot => return
				return 0;

			if (_vm->_game->_mouseButtons != kMouseButtonsNone)
				// Clicked something, get the hotspot index
				index = findClickedInput(index);

			if (!_hotspots[index].isInput())
				// It's no input, return
				return 0;

			// Get the associated input index
			curInput = hotspotToInput(index);
			break;

		case kKeyF1:
		case kKeyF2:
		case kKeyF3:
		case kKeyF4:
		case kKeyF5:
		case kKeyF6:
		case kKeyF7:
		case kKeyF8:
		case kKeyF9:
		case kKeyF10:
			return key;

		case kKeyReturn:
			// Just one input => return
			if (inputCount == 1)
				return kKeyReturn;

			// End of input chain reached => wrap
			if (curInput == (inputCount - 1)) {
				curInput = 0;
				break;
			}

			// Next input
			curInput++;
			break;

		case kKeyDown:
			// Next input
			if ((inputCount - 1) > curInput)
				curInput++;
			break;

		case kKeyUp:
			// Previous input
			if (curInput > 0)
				curInput--;
			break;
		}
	}
}

void Hotspots::evaluateNew(uint16 i, uint16 *ids, InputDesc *inputs,
		uint16 &inputId, bool &hasInput, uint16 &inputCount) {

	ids[i] = 0;

	// Type and window
	byte type      = _vm->_game->_script->readByte();
	byte windowNum = 0;

	if ((type & 0x40) != 0) {
		// Got a window ID

		type     -= 0x40;
		windowNum = _vm->_game->_script->readByte();
	}

	// Coordinates
	uint16 left, top, width, height, right, bottom;
	uint32 funcPos = 0;
	if ((type & 0x80) != 0) {
		// Complex coordinate expressions
		funcPos = _vm->_game->_script->pos();
		left    = _vm->_game->_script->readValExpr();
		top     = _vm->_game->_script->readValExpr();
		width   = _vm->_game->_script->readValExpr();
		height  = _vm->_game->_script->readValExpr();
	} else {
		// Immediate values
		funcPos = 0;
		left    = _vm->_game->_script->readUint16();
		top     = _vm->_game->_script->readUint16();
		width   = _vm->_game->_script->readUint16();
		height  = _vm->_game->_script->readUint16();
	}
	type &= 0x7F;

	// Draw a border around the hotspot
	if (_vm->_draw->_renderFlags & RENDERFLAG_BORDERHOTSPOTS) {
		Surface &surface = *_vm->_draw->_spritesArray[_vm->_draw->_destSurface];

		_vm->_video->dirtyRectsAll();

		if (windowNum == 0) {
			// The hotspot is not inside a window, just draw border it
			surface.drawRect(left, top, left + width - 1, top + height - 1, 0);

		} else {
			// The hotspot is inside a window, only draw it if it's the topmost window

			if ((_vm->_draw->_fascinWin[windowNum].id != -1) &&
			    (_vm->_draw->_fascinWin[windowNum].id == (_vm->_draw->_winCount - 1))) {

				const uint16 wLeft = left + _vm->_draw->_fascinWin[windowNum].left;
				const uint16 wTop  = top  + _vm->_draw->_fascinWin[windowNum].top;

				surface.drawRect(wLeft, wTop, wLeft + width - 1, wTop + height - 1, 0);
			}
		}
	}

	// Apply global drawing offset
	if ((_vm->_draw->_renderFlags & RENDERFLAG_CAPTUREPOP) && (left != 0xFFFF)) {
		left += _vm->_draw->_backDeltaX;
		top  += _vm->_draw->_backDeltaY;
	}

	right  = left + width  - 1;
	bottom = top  + height - 1;

	// Enabling the hotspots again
	if ((type == kTypeEnable2) || (type == kTypeEnable1)) {
		uint8 wantedState = 0;
		if (type == kTypeEnable2)
			wantedState = kStateFilledDisabled | kStateType2;
		else
			wantedState = kStateFilledDisabled | kStateType1;

		_vm->_game->_script->skip(6);

		for (int j = 0; j < kHotspotCount; j++) {
			Hotspot &spot = _hotspots[j];

			if (spot.getState() == wantedState) {
				spot.enable();
				spot.funcEnter = _vm->_game->_script->pos();
				spot.funcLeave = _vm->_game->_script->pos();
			}
		}

		_vm->_game->_script->skipBlock();

		return;
	}

	int16 key   = 0;
	int16 flags = 0;
	Font *font = 0;
	uint32 funcEnter = 0, funcLeave = 0;

	if ((windowNum != 0) && (type != 0) && (type != 2))
		debugC(0, kDebugHotspots, "evaluateNew - type %d, win %d",type, windowNum);

	// Evaluate parameters for the new hotspot
	switch (type) {
	case kTypeNone:
		_vm->_game->_script->skip(6);

		funcEnter = _vm->_game->_script->pos();
		_vm->_game->_script->skipBlock();

		funcLeave = _vm->_game->_script->pos();
		_vm->_game->_script->skipBlock();

		key   = i + ((kStateFilled | kStateType2) << 12);
		flags = type + (windowNum << 8);
		break;

	case kTypeMove:
		key    = _vm->_game->_script->readInt16();
		ids[i] = _vm->_game->_script->readInt16();
		flags  = _vm->_game->_script->readInt16();

		funcEnter = _vm->_game->_script->pos();
		_vm->_game->_script->skipBlock();

		funcLeave = _vm->_game->_script->pos();
		_vm->_game->_script->skipBlock();

		if (key == 0)
			key = i + ((kStateFilled | kStateType2) << 12);

		flags = type + (windowNum << 8) + (flags << 4);
		break;

	case kTypeInput1NoLeave:
	case kTypeInput1Leave:
	case kTypeInput2NoLeave:
	case kTypeInput2Leave:
	case kTypeInput3NoLeave:
	case kTypeInput3Leave:
	case kTypeInputFloatNoLeave:
	case kTypeInputFloatLeave:
		hasInput = true;

		_vm->_util->clearKeyBuf();

		// Input text parameters
		key                           = _vm->_game->_script->readVarIndex();
		inputs[inputCount].fontIndex  = _vm->_game->_script->readInt16();
		inputs[inputCount].backColor  = _vm->_game->_script->readByte();
		inputs[inputCount].frontColor = _vm->_game->_script->readByte();
		inputs[inputCount].length     = 0;
		inputs[inputCount].str        = 0;

		if ((type >= kTypeInput2NoLeave) && (type <= kTypeInput3Leave)) {
			inputs[inputCount].length = _vm->_game->_script->readUint16();

			inputs[inputCount].str =
				(const char *)(_vm->_game->_script->getData() + _vm->_game->_script->pos());

			_vm->_game->_script->skip(inputs[inputCount].length);
		}

		if (left == 0xFFFF) {
			if (!(type & 1))
				// No coordinates but a leave block => skip it
				_vm->_game->_script->skipBlock();
			break;
		}

		font = _vm->_draw->_fonts[inputs[inputCount].fontIndex];
		if (font->isMonospaced())
			right = left + width * font->getCharWidth() - 1;

		funcEnter = 0;
		funcPos   = 0;
		funcLeave = 0;
		if (!(type & 1)) {
			// Got a leave
			funcLeave = _vm->_game->_script->pos();
			_vm->_game->_script->skipBlock();
		}

		flags = type;

		inputCount++;
		break;

	case 20:
		inputId = i;
		// fall through
	case kTypeClick:
		key    = _vm->_game->_script->readInt16();
		ids[i] = _vm->_game->_script->readInt16();
		flags  = _vm->_game->_script->readInt16();

		if (flags > 3)
			warning("evaluateNew: Warning, use of type 2 or 20. flags = %d, should be %d", flags, flags&3);

		funcEnter = 0;

		funcLeave = _vm->_game->_script->pos();
		_vm->_game->_script->skipBlock();

		flags = ((uint16) kTypeClick) + (windowNum << 8) + (flags << 4);
		break;

	case kTypeClickEnter:
		key    = _vm->_game->_script->readInt16();
		ids[i] = _vm->_game->_script->readInt16();
		flags  = _vm->_game->_script->readInt16() & 3;

		funcEnter = _vm->_game->_script->pos();
		_vm->_game->_script->skipBlock();

		funcLeave = 0;

		flags = ((uint16) kTypeClick) + (windowNum << 8) + (flags << 4);
		break;
	}

	// Add the new hotspot
	add(i | (kStateFilled << 12), left, top, right, bottom,
			flags, key, funcEnter, funcLeave, funcPos);
}

bool Hotspots::evaluateFind(uint16 key, int16 timeVal, const uint16 *ids,
		uint16 leaveWindowIndex, uint16 hotspotIndex1, uint16 hotspotIndex2,
		uint16 endIndex, int16 &duration, uint16 &id, uint16 &index, bool &finished) {

	bool fascinCheck = false;

	if (id != 0)
		// We already found a hotspot, nothing to do
		return true;

	if (key != 0) {
		// We've got a key

		// Find the hotspot with that key associated
		findKey(key, id, index);
		if (id != 0)
			// Found it
			return true;

		// Try it case insensitively
		findKeyCaseInsensitive(key, id, index);
		if (id != 0)
			// Found it
			return true;

		return false;
	}
	if ((_vm->getGameType() == kGameTypeFascination) && (getCurrentHotspot()))
		fascinCheck = true;

	if ((duration != 0) && (!fascinCheck)) {
		// We've got a time duration

		if        (hotspotIndex1 != 0) {
			finished =
				leaveNthPlain(hotspotIndex1, endIndex, timeVal, ids, id, index, duration);
		} else if (hotspotIndex2 != 0) {
			findNthPlain(hotspotIndex2, endIndex, id, index);
		} else {
			// Enter the first hotspot
			for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
				Hotspot &spot = _hotspots[i];
				if (spot.isFilledNew()) {
					id    = spot.id;
					index = i;
					break;
				}
			}

			// Leave the current hotspot
			if ((_currentKey != 0) && (_hotspots[_currentIndex].funcLeave != 0))
				call(_hotspots[_currentIndex].funcLeave);

			_currentKey = 0;
		}

		if (id != 0)
			return true;

		return false;
	} else {
		if (leaveWindowIndex != 0)
			findNthPlain(leaveWindowIndex, endIndex, id, index);

		if (id != 0)
			return true;
	}

	return false;
}

void Hotspots::evaluate() {
	InputDesc inputs[20];
	uint16 ids[kHotspotCount];

	// Push all local hotspots
	push(0);

	// Find the current end of the hotspot block
	uint16 endIndex = 0;
	while (!_hotspots[endIndex].isEnd())
		endIndex++;

	_shouldPush = false;

	_vm->_game->_script->skip(1);

	// Number of new hotspots
	byte count = _vm->_game->_script->readByte();

	// Parameters of this block
	_vm->_game->_handleMouse = _vm->_game->_script->peekByte(0);
	int16 duration           = _vm->_game->_script->peekByte(1);

	byte leaveWindowIndex = 0;
	if (_vm->getGameType() == kGameTypeFascination)
		leaveWindowIndex = _vm->_game->_script->peekByte(2);

	byte hotspotIndex1       = _vm->_game->_script->peekByte(3);
	byte hotspotIndex2       = _vm->_game->_script->peekByte(4);
	bool needRecalculation   = _vm->_game->_script->peekByte(5) != 0;

	// Seconds -> Milliseconds
	duration *= 1000;

	if ((hotspotIndex1 != 0) || (hotspotIndex2 != 0)) {
		duration /= 100;
		if (_vm->_game->_script->peekByte(1) == 100)
			duration = 2;
	}

	int16 timeVal = duration;

	_vm->_game->_script->skip(6);

	setCurrentHotspot(0, 0);

	bool finishedDuration = false;

	uint16 id      = 0;
	uint16 inputId = 0xFFFF;
	uint16 index   = 0;

	bool   hasInput   = false;
	uint16 inputCount = 0;

	// Adding new hotspots
	for (uint16 i = 0; i < count; i++)
		evaluateNew(i, ids, inputs, inputId, hasInput, inputCount);

	// Recalculate all hotspots if requested
	if (needRecalculation)
		recalculate(true);

	_vm->_game->_forceHandleMouse = 0;
	_vm->_util->clearKeyBuf();

	while ((id == 0) && !_vm->_inter->_terminate && !_vm->shouldQuit()) {
		uint16 key = 0;
		if (hasInput) {
			// Input

			uint16 curInput = 0;

			key = handleInputs(duration, inputCount, curInput, inputs, id, index);

			// Notify the script of the current input index
			WRITE_VAR(17 + 38, curInput);
			if (key == kKeyReturn) {
				// Return pressed, invoke input leave
				findFirstInputLeave(id, inputId, index);
				break;
			}
		} else
			// Normal move or click check
			key = check(_vm->_game->_handleMouse, -duration, id, index);

		key = convertSpecialKey(key);

		// Try to find a fitting hotspot
		evaluateFind(key, timeVal, ids, leaveWindowIndex, hotspotIndex1, hotspotIndex2, endIndex,
				duration, id, index, finishedDuration);

		if (finishedDuration)
			break;

		if ((id == 0) || (_hotspots[index].funcLeave != 0))
			// We don't have a new ID, but haven't yet handled the leave function
			continue;

		_vm->_inter->storeMouse();

		setCurrentHotspot(ids, id);

		// Enter it
		if (_hotspots[index].funcEnter != 0)
			call(_hotspots[index].funcEnter);

		setCurrentHotspot(0, 0);
		id = 0;
	}

	if ((id & 0xFFF) == inputId)
		matchInputStrings(inputs);

	if (_vm->_game->_handleMouse == 1)
		_vm->_draw->blitCursor();

	if (!_vm->_inter->_terminate && (!finishedDuration)) {
		_vm->_game->_script->seek(_hotspots[index].funcLeave);

		_vm->_inter->storeMouse();
		if (getCurrentHotspot() == 0) {
			// No hotspot currently handled, now we'll handle the newly found one

			setCurrentHotspot(ids, id);
		}
	} else
		_vm->_game->_script->setFinished(true);

	for (int i = 0; i < count; i++)
		// Remove all local hotspots
		remove(i + (kStateFilled << 12));

	for (int i = 0; i < kHotspotCount; i++) {
		Hotspot &spot = _hotspots[i];

		// Disable the ones still there
		if ((spot.getState() == (kStateFilled | kStateType1)) ||
				(spot.getState() == (kStateFilled | kStateType2)))
				spot.disable();
	}

}

int16 Hotspots::findCursor(uint16 x, uint16 y) const {
	int16 cursor = 0;

	int16 deltax = 0;
	int16 deltay = 0;

	// Fascination uses hard-coded windows
	if (_vm->getGameType() == kGameTypeFascination) {
		cursor = windowCursor(deltax, deltay);

		// We're in a window and in an area that forces a specific cursor
		if (cursor > 0)
			return cursor;

		// We're somewhere else inside a window
		if (cursor < 0) {
			int16 curType = -cursor * 256;
			cursor = 0;

			for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
				const Hotspot &spot = _hotspots[i];
				// this check is /really/ Fascination specific.
				// It's illogical, so if it's to be reused in Adi games... Be careful!
				if ((spot.flags & 0xFF00) == curType)
					if (spot.isIn(x - deltax, y - deltay)) {
						if (spot.getType() < kTypeInput1NoLeave)
							cursor = 1;
						else
							cursor = 3;
						break;
					}
			}

			if (_vm->_draw->_cursorAnimLow[cursor] == -1)
			// If the cursor is invalid... there's a generic "click" cursor
				cursor = 1;

			return cursor;
		}

	}

	// Normal, non-window cursor handling
	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		const Hotspot &spot = _hotspots[i];

		if ((spot.getWindow() != 0) || spot.isDisabled())
			// Ignore disabled and non-main-windowed hotspots
			continue;

		if (!spot.isIn(x, y))
			// We're not in that hotspot, ignore it
			continue;

		if (spot.getCursor() == 0) {
			// Hotspot doesn't itself specify a cursor...
			if (spot.getType() >= kTypeInput1NoLeave) {
				// ...but the type has a generic one
				cursor = 3;
				break;
			} else if ((spot.getButton() != kMouseButtonsRight) && (cursor == 0))
				// ...but there's a generic "click" cursor
				cursor = 1;
		} else if (cursor == 0)
			// Hotspot had an attached cursor index
			cursor = spot.getCursor();
	}

	return cursor;
}

void Hotspots::oPlaytoons_F_1B() {
	int16 shortId;
	int16 longId;
	int16 var2;
	int16 fontIndex;
	int16 var4;

	uint16 left;
	uint16 top;
	uint16 right;
	uint16 bottom;

	shortId = _vm->_game->_script->readValExpr();
	var2 = _vm->_game->_script->readValExpr();

	_vm->_game->_script->evalExpr(0);

	fontIndex = _vm->_game->_script->readValExpr();
	var4 = _vm->_game->_script->readValExpr();

//  this variable is always set to 0 in Playtoons
//	var_4 += unk_var;

	for (int i = 0; i < kHotspotCount; i++) {
		if (_hotspots[i].isEnd())
			return;

		if ((_hotspots[i].id == 0xD000 + shortId) || (_hotspots[i].id == 0xB000 + shortId) ||
			(_hotspots[i].id == 0x4000 + shortId)) {
			longId = _hotspots[i].id;
			warning("oPlaytoons_F_1B: shortId %d, var2 %d fontIndex %d var4 %d - longId %d", shortId, var2, fontIndex, var4, longId);

			left = _hotspots[i].left;
			top = _hotspots[i].top;
			right = _hotspots[i].right;
			bottom = _hotspots[i].bottom;

			left += 2;
			top += 2;
			right -= 2;
			bottom -= 2;
			if ((_vm->_draw->_needAdjust != 2) && (_vm->_draw->_needAdjust != 10)) {
				left += 2;
				top += 2;
				right -= 2;
				bottom -= 2;
			}
			_vm->_draw->oPlaytoons_sub_F_1B(0x8000+ var2, left, top, right, bottom, _vm->_game->_script->getResultStr(), fontIndex, var4, shortId);
			return;
		}
	}
	warning("shortId not found %d", shortId);
	return;
}

uint16 Hotspots::inputToHotspot(uint16 input) const {
	uint16 inputIndex = 0;
	for (int i = 0; i < kHotspotCount; i++) {
		const Hotspot &spot = _hotspots[i];

		if (!spot.isActiveInput())
			// Not an active input
			continue;

		if (inputIndex == input)
			// We've found our input
			return i;

		// Next one
		inputIndex++;
	}

	// None found
	return 0xFFFF;
}

uint16 Hotspots::hotspotToInput(uint16 hotspot) const {
	uint16 input = 0;

	for (int i = 0; i < kHotspotCount; i++) {
		const Hotspot &spot = _hotspots[i];

		if (!spot.isActiveInput())
			// Not an active input
			continue;

		if (i == hotspot)
			// We've found our hotspot
			break;

		// Next one
		input++;
	}

	return input;
}

uint16 Hotspots::findClickedInput(uint16 index) const {
	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		const Hotspot &spot = _hotspots[i];

		if (spot.getWindow() != 0)
			// Ignore other windows
			continue;

		if (spot.isDisabled())
			// Ignore disabled hotspots
			continue;

		if (!spot.isIn(_vm->_global->_inter_mouseX, _vm->_global->_inter_mouseY))
			// This one wasn't it
			continue;

		if (spot.getCursor() != 0)
			// This one specifies a cursor, so we don't want it
			continue;

		if (!spot.isInput())
			// It's no input
			continue;

		index = i;
		break;
	}

	return index;
}

bool Hotspots::findFirstInputLeave(uint16 &id, uint16 &inputId, uint16 &index) const {
	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		const Hotspot &spot = _hotspots[i];

		if (!spot.isFilledEnabled())
			// Not filled or disabled
			continue;

		if (!spot.isInputLeave())
			// Not an input with a leave function
			continue;

		id      = spot.id;
		inputId = spot.id & 0x7FFF;
		index   = i;
		return true;
	}

	return false;
}

bool Hotspots::findKey(uint16 key, uint16 &id, uint16 &index) const {
	id    = 0;
	index = 0;

	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		const Hotspot &spot = _hotspots[i];

		if (!spot.isFilledEnabled())
			// Not filled or disabled
			continue;

		//      Key match              Catch all
		if ((spot.key == key) || (spot.key == 0x7FFF)) {
			id    = spot.id;
			index = i;
			return true;
		}
	}

	return false;
}

bool Hotspots::findKeyCaseInsensitive(uint16 key, uint16 &id, uint16 &index) const {
	id    = 0;
	index = 0;

	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		const Hotspot &spot = _hotspots[i];

		if (!spot.isFilledEnabled())
			// Not filled or disabled, ignore
			continue;

		if ((spot.key & 0xFF00) != 0)
			continue;

		if (spot.key == 0)
			// No associated key, ignore
			continue;

		// Compare
		if (toupper(key & 0xFF) == toupper(spot.key)) {
			id    = spot.id;
			index = i;
			return true;
		}
	}

	return false;
}

bool Hotspots::findNthPlain(uint16 n, uint16 startIndex, uint16 &id, uint16 &index) const {
	id    = 0;
	index = 0;

	uint16 counter = 0;
	for (int i = startIndex; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
		const Hotspot &spot = _hotspots[i];

		if (!spot.isFilledNew())
			// Not filled, ignore
			continue;

		if (++counter != n)
			// Not yet the one we want
			continue;

		id    = spot.id;
		index = i;
		return true;
	}

	return false;
}

bool Hotspots::leaveNthPlain(uint16 n, uint16 startIndex, int16 timeVal, const uint16 *ids,
		uint16 &id, uint16 &index, int16 &duration) {

	id    = 0;
	index = 0;

	if (!findNthPlain(n, startIndex, id, index))
		// Doesn't exist
		return false;

	_vm->_inter->storeMouse();

	if (getCurrentHotspot() != 0)
		// We already handle a hotspot
		return false;

	setCurrentHotspot(ids, id);

	const Hotspot &spot = _hotspots[index];
	if (spot.funcLeave != 0) {
		// It has a leave function

		uint32 startTime, callTime;

		// Call the leave and time it
		startTime = _vm->_util->getTimeKey();
		call(spot.funcLeave);
		_vm->_inter->animPalette();
		callTime = _vm->_util->getTimeKey() - startTime;

		// Remove the time it took from the time we have available
		duration = CLIP<int>(timeVal - callTime, 2, timeVal);
	}

	if (getCurrentHotspot() == 0) {
		id    = 0;
		index = 0;
	}

	return getCurrentHotspot() != 0;
}

void Hotspots::setCurrentHotspot(const uint16 *ids, uint16 id) const {
	if (!ids) {
		WRITE_VAR(16, 0);
		return;
	}

	if (Hotspot::getState(id) == kStateFilled)
		WRITE_VAR(16, ids[id & 0xFFF]);
	else
		WRITE_VAR(16, id & 0xFFF);
}

uint32 Hotspots::getCurrentHotspot() const {
	return VAR(16);
}

void Hotspots::cleanFloatString(const Hotspot &spot) const {
	char *to, *from;

	to = from = GET_VARO_STR(spot.key);
	for (int i = 0; (i < 257) && (*from != '\0'); i++, from++) {
		char c = *from;

		// Skip spaces
		if (c == ' ')
			continue;

		// Convert decimal separator if necessary
		if ((_vm->_global->_language == kLanguageBritish) && (c == '.'))
			c = ',';

		*to++ = c;
	}

	*to = '\0';
}

void Hotspots::checkStringMatch(const Hotspot &spot, const InputDesc &input,
		uint16 inputPos) const {

	const char *str = input.str;

	char tempStr[256];
	char spotStr[256];

	Common::strlcpy(tempStr, GET_VARO_STR(spot.key), 256);

	if (spot.getType() < kTypeInput3NoLeave)
		_vm->_util->cleanupStr(tempStr);

	uint16 pos = 0;
	do {
		Common::strlcpy(spotStr, str, 256);

		pos += strlen(str) + 1;
		str += strlen(str) + 1;

		if (spot.getType() < kTypeInput3NoLeave)
			_vm->_util->cleanupStr(spotStr);

		// Compare the entered string with the string we wanted
		if (strcmp(tempStr, spotStr) == 0) {
			WRITE_VAR(17, VAR(17) + 1);
			WRITE_VAR(17 + inputPos, 1);
			break;
		}
	} while (input.length > pos);
}

void Hotspots::matchInputStrings(const InputDesc *inputs) const {
	uint16 strInputCount = 0;
	uint16 inputIndex    = 0;
	uint16 inputPos      = 1;

	for (int i = 0; i < kHotspotCount; i++) {
		const Hotspot &spot = _hotspots[i];

		// Looking for all enabled inputs
		if (spot.isEnd())
			continue;
		if (!spot.isFilledEnabled())
			continue;
		if (!spot.isInput())
			continue;

		if (spot.getType() >= kTypeInputFloatNoLeave)
			cleanFloatString(spot);

		if ((spot.getType() >= kTypeInput2NoLeave) && (spot.getType() <= kTypeInput3Leave)) {
			// Look if we find a match between the wanted and the typed string
			checkStringMatch(spot, inputs[inputIndex], inputPos);
			strInputCount++;
		} else
			WRITE_VAR(17 + inputPos, 2);

		inputIndex++;
		inputPos++;
	}

	// Notify the scripts if we reached the requested hotspot
	WRITE_VAR(17, (uint32) (strInputCount == ((uint16) VAR(17))));
}

uint16 Hotspots::convertSpecialKey(uint16 key) const {
	if (((key & 0xFF) >= ' ') && ((key & 0xFF) <= 0xFF) &&
			((key >> 8) > 1) && ((key >> 8) < 12))
		key = '0' + (((key >> 8) - 1) % 10) + (key & 0xFF00);

	return key;
}

void Hotspots::getTextCursorPos(const Font &font, const char *str,
		uint32 pos, uint16 x, uint16 y, uint16 width, uint16 height,
		uint16 &cursorX, uint16 &cursorY, uint16 &cursorWidth, uint16 &cursorHeight) const {

	if (!font.isMonospaced()) {
		// Cursor to the right of the current character

		cursorX      = x;
		cursorY      = y;
		cursorWidth  = 1;
		cursorHeight = height;

		// Iterate through the string and add each character's width
		for (uint32 i = 0; i < pos; i++)
			cursorX += font.getCharWidth(str[i]);

	} else {
		// Cursor underlining the current character

		cursorX      = x + font.getCharWidth() * pos;
		cursorY      = y + height - 1;
		cursorWidth  = font.getCharWidth();
		cursorHeight = 1;
	}
}

void Hotspots::fillRect(uint16 x, uint16 y, uint16 width, uint16 height, uint16 color) const {
	_vm->_draw->_destSurface  = Draw::kBackSurface;
	_vm->_draw->_destSpriteX  = x;
	_vm->_draw->_destSpriteY  = y;
	_vm->_draw->_spriteRight  = width;
	_vm->_draw->_spriteBottom = height;
	_vm->_draw->_backColor    = color;

	_vm->_draw->spriteOperation(DRAW_FILLRECT | 0x10);
}

void Hotspots::printText(uint16 x, uint16 y, const char *str, uint16 fontIndex, uint16 color) const {
	_vm->_draw->_destSpriteX  = x;
	_vm->_draw->_destSpriteY  = y;
	_vm->_draw->_frontColor   = color;
	_vm->_draw->_fontIndex    = fontIndex;
	_vm->_draw->_textToPrint  = str;
	_vm->_draw->_transparency = 1;

	_vm->_draw->spriteOperation(DRAW_PRINTTEXT | 0x10);
}

void Hotspots::updateAllTexts(const InputDesc *inputs) const {
	uint16 input = 0;

	for (int i = 0; i < kHotspotCount; i++) {
		const Hotspot &spot = _hotspots[i];

		if (spot.isEnd())
			// It's an end, we don't want it
			continue;

		if (!spot.isFilledEnabled())
			// This one's either not used or disabled
			continue;

		if (!spot.isInput())
			// Not an input
			continue;

		// Get its text
		char tempStr[256];
		Common::strlcpy(tempStr, GET_VARO_STR(spot.key), 256);

		// Coordinates
		uint16 x      = spot.left;
		uint16 y      = spot.top;
		uint16 width  = spot.right  - spot.left + 1;
		uint16 height = spot.bottom - spot.top  + 1;
		// Clear the background
		fillRect(x, y, width, height, inputs[input].backColor);

		// Center the text vertically
		y += (height - _vm->_draw->_fonts[_vm->_draw->_fontIndex]->getCharHeight()) / 2;

		// Draw it
		printText(x, y, tempStr, inputs[input].fontIndex, inputs[input].frontColor);

		input++;
	}
}
} // End of namespace Gob