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

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

#include "sci/sci.h"
#include "sci/event.h"
#include "sci/console.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"

namespace Sci {

#define SCANCODE_ROWS_NR 3

SciEvent::SciEvent() {
}

SciEvent::~SciEvent() {
}

struct scancode_row {
	int offset;
	const char *keys;
} scancode_rows[SCANCODE_ROWS_NR] = {
	{0x10, "QWERTYUIOP[]"},
	{0x1e, "ASDFGHJKL;'\\"},
	{0x2c, "ZXCVBNM,./"}
};

int SciEvent::controlify(int ch) {
	if (ch < 26)
		ch += 96; // 0x01 -> 'a'
	return ch;
}

int SciEvent::altify (int ch) {
	// Calculates a PC keyboard scancode from a character */
	int row;
	int c = toupper((char)ch);

	for (row = 0; row < SCANCODE_ROWS_NR; row++) {
		const char *keys = scancode_rows[row].keys;
		int offset = scancode_rows[row].offset;

		while (*keys) {
			if (*keys == c)
				return offset << 8;

			offset++;
			keys++;
		}
	}

	return ch;
}

int SciEvent::numlockify (int c) {
	switch (c) {
	case SCI_KEY_DELETE:
		return '.';
	case SCI_KEY_INSERT:
		return '0';
	case SCI_KEY_END:
		return '1';
	case SCI_KEY_DOWN:
		return '2';
	case SCI_KEY_PGDOWN:
		return '3';
	case SCI_KEY_LEFT:
		return '4';
	case SCI_KEY_CENTER:
		return '5';
	case SCI_KEY_RIGHT:
		return '6';
	case SCI_KEY_HOME:
		return '7';
	case SCI_KEY_UP:
		return '8';
	case SCI_KEY_PGUP:
		return '9';
	default:
		return c; // Unchanged
	}
}

sciEvent SciEvent::getFromScummVM() {
	static int _modifierStates = 0;	// FIXME: Avoid non-const global vars
	sciEvent input = { SCI_EVENT_NONE, 0, 0, 0 };

	Common::EventManager *em = g_system->getEventManager();
	Common::Event ev;

	bool found = em->pollEvent(ev);
	Common::Point p = ev.mouse;

	// Don't generate events for mouse movement
	while (found && ev.type == Common::EVENT_MOUSEMOVE) {
		found = em->pollEvent(ev);
	}

	if (found && !ev.synthetic && ev.type != Common::EVENT_MOUSEMOVE) {
		int modifiers = em->getModifierState();

		// We add the modifier key status to buckybits
		// SDL sends a keydown event if a modifier key is turned on and a keyup event if it's off
		//
		// FIXME: This code is semi-bogus. It only records the modifier key being *pressed*.
		// It does not track correctly whether capslock etc. is active. To do that, we
		// would have to record the fact that the modifier was pressed in global var,
		// and also watch for Common::EVENT_KEYUP events.
		// But this is still not quite good enough, because not all events might
		// pass through here (e.g. the GUI might be running with its own event loop).
		//
		// The best solution likely would be to add code to the EventManager class
		// for tracking which keys are pressed and which are not...
		if (ev.type == Common::EVENT_KEYDOWN || ev.type == Common::EVENT_KEYUP) {
			switch (ev.kbd.keycode) {
			case Common::KEYCODE_CAPSLOCK:
				if (ev.type == Common::EVENT_KEYDOWN) {
					_modifierStates |= SCI_KEYMOD_CAPSLOCK;
				} else {
					_modifierStates &= ~SCI_KEYMOD_CAPSLOCK;
				}
				break;
			case Common::KEYCODE_NUMLOCK:
				if (ev.type == Common::EVENT_KEYDOWN) {
					_modifierStates |= SCI_KEYMOD_NUMLOCK;
				} else {
					_modifierStates &= ~SCI_KEYMOD_NUMLOCK;
				}
				break;
			case Common::KEYCODE_SCROLLOCK:
				if (ev.type == Common::EVENT_KEYDOWN) {
					_modifierStates |= SCI_KEYMOD_SCRLOCK;
				} else {
					_modifierStates &= ~SCI_KEYMOD_SCRLOCK;
				}
				break;
			default:
				break;
			}
		}
		//TODO: SCI_EVM_INSERT

		input.modifiers =
		    ((modifiers & Common::KBD_ALT) ? SCI_KEYMOD_ALT : 0) |
		    ((modifiers & Common::KBD_CTRL) ? SCI_KEYMOD_CTRL : 0) |
		    ((modifiers & Common::KBD_SHIFT) ? SCI_KEYMOD_LSHIFT | SCI_KEYMOD_RSHIFT : 0) |
			_modifierStates;

		switch (ev.type) {
			// Keyboard events
		case Common::EVENT_KEYDOWN:
			input.data = ev.kbd.keycode;
			input.character = ev.kbd.ascii;

			// Debug console
			if (ev.kbd.flags == Common::KBD_CTRL && ev.kbd.keycode == Common::KEYCODE_d) {
				// Open debug console
				Console *con = ((Sci::SciEngine*)g_engine)->getSciDebugger();
				con->attach();

				// Clear keyboard event
				input.type = SCI_EVENT_NONE;
				input.character = 0;
				input.data = 0;
				input.modifiers = 0;

				return input;
			}

			if (!(input.data & 0xFF00)) {
				// Directly accept most common keys without conversion
				input.type = SCI_EVENT_KEYBOARD;
				if (input.data == Common::KEYCODE_TAB) {
					// Tab
					input.type = SCI_EVENT_KEYBOARD;
					input.data = SCI_KEY_TAB;
					if (input.modifiers & (SCI_KEYMOD_LSHIFT | SCI_KEYMOD_RSHIFT))
						input.character = SCI_KEY_SHIFT_TAB;
					else
						input.character = SCI_KEY_TAB;
				}
			} else if ((input.data >= Common::KEYCODE_F1) && input.data <= Common::KEYCODE_F10) {
				// F1-F10
				input.type = SCI_EVENT_KEYBOARD;
				// SCI_K_F1 == 59 << 8
				// SCI_K_SHIFT_F1 == 84 << 8
				input.data = SCI_KEY_F1 + ((input.data - Common::KEYCODE_F1)<<8);
				if (input.modifiers & (SCI_KEYMOD_LSHIFT | SCI_KEYMOD_RSHIFT))
					input.character = input.data + 25;
				else
					input.character = input.data;
			} else {
				// Special keys that need conversion
				input.type = SCI_EVENT_KEYBOARD;
				switch (ev.kbd.keycode) {
				case Common::KEYCODE_UP:
					input.data = SCI_KEY_UP;
					break;
				case Common::KEYCODE_DOWN:
					input.data = SCI_KEY_DOWN;
					break;
				case Common::KEYCODE_RIGHT:
					input.data = SCI_KEY_RIGHT;
					break;
				case Common::KEYCODE_LEFT:
					input.data = SCI_KEY_LEFT;
					break;
				case Common::KEYCODE_INSERT:
					input.data = SCI_KEY_INSERT;
					break;
				case Common::KEYCODE_HOME:
					input.data = SCI_KEY_HOME;
					break;
				case Common::KEYCODE_END:
					input.data = SCI_KEY_END;
					break;
				case Common::KEYCODE_PAGEUP:
					input.data = SCI_KEY_PGUP;
					break;
				case Common::KEYCODE_PAGEDOWN:
					input.data = SCI_KEY_PGDOWN;
					break;
				case Common::KEYCODE_DELETE:
					input.data = SCI_KEY_DELETE;
					break;
				// Keypad keys
				case Common::KEYCODE_KP8:	// up
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_UP;
					break;
				case Common::KEYCODE_KP2:	// down
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_DOWN;
					break;
				case Common::KEYCODE_KP6:	// right
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_RIGHT;
					break;
				case Common::KEYCODE_KP4:	// left
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_LEFT;
					break;
				case Common::KEYCODE_KP5:	// center
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_CENTER;
					break;
				case Common::KEYCODE_KP7:	// home
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_HOME;
					break;
				case Common::KEYCODE_KP9:	// pageup
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_PGUP;
					break;
				case Common::KEYCODE_KP1:	// end
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_END;
					break;
				case Common::KEYCODE_KP3:	// pagedown
					if (!(_modifierStates & SCI_KEYMOD_NUMLOCK))
						input.data = SCI_KEY_PGDOWN;
					break;					
				default:
					input.type = SCI_EVENT_NONE;
					break;
				}
				input.character = input.data;
			}
			break;

			// Mouse events
		case Common::EVENT_LBUTTONDOWN:
			input.type = SCI_EVENT_MOUSE_PRESS;
			input.data = 1;
			break;
		case Common::EVENT_RBUTTONDOWN:
			input.type = SCI_EVENT_MOUSE_PRESS;
			input.data = 2;
			break;
		case Common::EVENT_LBUTTONUP:
			input.type = SCI_EVENT_MOUSE_RELEASE;
			input.data = 1;
			break;
		case Common::EVENT_RBUTTONUP:
			input.type = SCI_EVENT_MOUSE_RELEASE;
			input.data = 2;
			break;

			// Misc events
		case Common::EVENT_QUIT:
			input.type = SCI_EVENT_QUIT;
			break;

		default:
			break;
		}
	}

	return input;
}

sciEvent SciEvent::get(unsigned int mask) {
	//sci_event_t error_event = { SCI_EVT_ERROR, 0, 0, 0 };
	sciEvent event = { 0, 0, 0, 0 };

	// TODO: we need to call SciGuiCursor::refreshPosition() before each screen update to limit the mouse cursor position

	// Update the screen here, since it's called very often
	g_system->updateScreen();

	// Get all queued events from graphics driver
	do {
		event = getFromScummVM();
		if (event.type != SCI_EVENT_NONE)
			_events.push_back(event);
	} while (event.type != SCI_EVENT_NONE);

	// Search for matching event in queue
	Common::List<sciEvent>::iterator iter = _events.begin();
	while (iter != _events.end() && !((*iter).type & mask))
		++iter;

	if (iter != _events.end()) {
		// Event found
		event = *iter;

		// If not peeking at the queue, remove the event
		if (!(mask & SCI_EVENT_PEEK)) {
			_events.erase(iter);
		}
	} else {
		// No event found: we must return a SCI_EVT_NONE event.

		// Because event.type is SCI_EVT_NONE already here,
		// there is no need to change it.
	}

	if (event.type == SCI_EVENT_KEYBOARD) {
		// Do we still have to translate the key?

		// Scancodify if appropriate
		if (event.modifiers & SCI_KEYMOD_ALT) {
			event.character = altify(event.character);
		} else if (event.modifiers & SCI_KEYMOD_CTRL) {
			//event.character = event.data;
			event.character = controlify(event.character);
		}

		// Numlockify if appropriate
		//if (event.modifiers & SCI_KEYMOD_NUMLOCK)
		//	event.data = numlockify(event.data);
		// TODO: dont know yet if this can get dumped as well
	}

	return event;
}

} // End of namespace Sci