/*
 *  Copyright (C) 2001-2011 The Exult Team
 *  Copyright (C) 2012 The Nuvie Team
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <iostream>

#include "SDL_keyboard.h"
#include "Keys.h"
#include "KeyActions.h"
#include "nuvieDefs.h"
#include "Game.h"
#include "XMLTree.h"
#include "Player.h"
#include "Event.h"
#include "Utils.h"
#include "MsgScroll.h"
#include "Configuration.h"
#include "U6misc.h"
#include "Console.h"
#include "Effect.h"

#ifndef UNDER_EMBEDDED_CE
using std::atoi;
using std::cerr;
using std::endl;
using std::ifstream;
using std::isspace;
using std::strchr;
using std::string;
using std::strlen;
#endif

static	class Chardata	// ctype-like character lists
	{
	public:
	string	whitespace;
	Chardata()
		{
		for(size_t i=0;i<256;i++)
			if(isspace(i))
				whitespace+=static_cast<char>(i);
		}
	} chardata;

typedef void(*ActionFunc)(int const*);

const struct Action {
	const char *s;
	ActionFunc func; // called on keydown
	const char* desc;
	enum {
		dont_show = 0,
		normal_keys,
		cheat_keys
	} key_type;
	bool allow_in_vehicle;
	ActionKeyType action_key_type;
} NuvieActions[] = {
	{ "WALK_WEST", ActionWalkWest, "Walk west", Action::normal_keys, true, WEST_KEY  },
	{ "WALK_EAST", ActionWalkEast, "Walk east", Action::normal_keys, true, EAST_KEY },
	{ "WALK_NORTH", ActionWalkNorth, "Walk north", Action::normal_keys, true, NORTH_KEY },
	{ "WALK_SOUTH", ActionWalkSouth, "Walk south", Action::normal_keys, true, SOUTH_KEY },
	{ "WALK_NORTH_EAST", ActionWalkNorthEast, "Walk north-east", Action::normal_keys, true, NORTH_EAST_KEY },
	{ "WALK_SOUTH_EAST", ActionWalkSouthEast, "Walk south-east", Action::normal_keys, true, SOUTH_EAST_KEY },
	{ "WALK_NORTH_WEST", ActionWalkNorthWest, "Walk north-west", Action::normal_keys, true, NORTH_WEST_KEY },
	{ "WALK_SOUTH_WEST", ActionWalkSouthWest, "Walk south-west", Action::normal_keys, true, SOUTH_WEST_KEY },
	{ "CAST", ActionCast, "Cast", Action::normal_keys, true, OTHER_KEY }, // allow_in_vehicle is true, so the right message or cancel is done for WOU games
	{ "LOOK", ActionLook, "Look", Action::normal_keys, true, OTHER_KEY },
	{ "TALK", ActionTalk, "Talk", Action::normal_keys, true, OTHER_KEY },
	{ "USE", ActionUse, "Use", Action::normal_keys, true, OTHER_KEY },
	{ "GET", ActionGet, "Get", Action::normal_keys, false, OTHER_KEY },
	{ "MOVE", ActionMove, "Move", Action::normal_keys, false, OTHER_KEY },
	{ "DROP", ActionDrop, "Drop", Action::normal_keys, false, OTHER_KEY },
	{ "TOGGLE_COMBAT", ActionToggleCombat, "Toggle combat", Action::normal_keys, false, OTHER_KEY },
	{ "ATTACK", ActionAttack, "Attack", Action::normal_keys, true, OTHER_KEY },
	{ "REST", ActionRest, "Rest", Action::normal_keys, true, OTHER_KEY },
	{ "MULTI_USE", ActionMultiUse, "Multi-use", Action::normal_keys, true, OTHER_KEY },
	{ "SELECT_COMMAND_BAR", ActionSelectCommandBar, "Select Command Bar", Action::normal_keys, true, OTHER_KEY },
	{ "NEW_COMMAND_BAR", ActionSelectNewCommandBar, "Select New Command Bar", Action::normal_keys, true, NEW_COMMAND_BAR_KEY },
	{ "DOLL_GUMP", ActionDollGump, "Doll gump", Action::normal_keys, true, OTHER_KEY },
	{ "SHOW_STATS", ActionShowStats, "Shows the party member's stats", Action::normal_keys, true, OTHER_KEY },
	{ "INVENTORY", ActionInventory, "inventory", Action::normal_keys, true, OTHER_KEY },
	{ "PREVIOUS_PARTY_MEMBER", ActionPreviousPartyMember, "Previous party member", Action::normal_keys, true, PREVIOUS_PARTY_MEMBER_KEY },
	{ "NEXT_PARTY_MEMBER", ActionNextPartyMember, "Next party member", Action::normal_keys, true, NEXT_PARTY_MEMBER_KEY },
	{ "HOME_KEY", ActionHome, "Home key", Action::normal_keys, true, HOME_KEY },
	{ "END_KEY", ActionEnd, "End key", Action::normal_keys, true, END_KEY },
	{ "TOGGLE_VIEW", ActionToggleView, "Toggle between inventory and actor view", Action::normal_keys, true, OTHER_KEY },
	{ "PARTY_VIEW", ActionPartyView, "Party view", Action::normal_keys, true, OTHER_KEY },
	{ "SOLO_MODE", ActionSoloMode, "Solo mode", Action::normal_keys, true, OTHER_KEY },
	{ "PARTY_MODE", ActionPartyMode, "Party mode", Action::normal_keys, true, OTHER_KEY },
	{ "SAVE_MENU", ActionSaveDialog, "Save menu", Action::normal_keys, true, OTHER_KEY },
	{ "LOAD_LATEST_SAVE", ActionLoadLatestSave, "Load latest save", Action::normal_keys, true, OTHER_KEY },
	{ "QUICK_SAVE", ActionQuickSave, "Quick save", Action::normal_keys, true, OTHER_KEY },
	{ "QUICK_LOAD", ActionQuickLoad, "Quick load", Action::normal_keys, true, OTHER_KEY },
	{ "QUIT", ActionQuitDialog, "Quit", Action::normal_keys, true, QUIT_KEY },
	{ "QUIT_NO_DIALOG", ActionQuitNODialog, "Quit without confirmation", Action::normal_keys, true, QUIT_KEY },
	{ "GAME_MENU_DIALOG", ActionGameMenuDialog, "Show game menu; Cancel action if in middle of action", Action::normal_keys, true, CANCEL_ACTION_KEY },
	{ "TOGGLE_FULLSCREEN", ActionToggleFullscreen, "Toggle Fullscreen", Action::normal_keys, true, TOGGLE_FULLSCREEN_KEY },
	{ "TOGGLE_CURSOR", ActionToggleCursor, "Toggle Cursor", Action::normal_keys, true, TOGGLE_CURSOR_KEY },
	{ "TOGGLE_COMBAT_STRATEGY", ActionToggleCombatStrategy, "Toggle combat strategy", Action::normal_keys, true, OTHER_KEY },
	{ "TOGGLE_FPS_DISPLAY", ActionToggleFps, "Toggle frames per second display", Action::normal_keys, true, TOGGLE_FPS_KEY },
	{ "TOGGLE_AUDIO", ActionToggleAudio, "Toggle audio", Action::normal_keys, true, TOGGLE_AUDIO_KEY },
	{ "TOGGLE_MUSIC", ActionToggleMusic, "Toggle music", Action::normal_keys, true, TOGGLE_MUSIC_KEY },
	{ "TOGGLE_SFX", ActionToggleSFX, "Toggle sfx", Action::normal_keys, true, TOGGLE_SFX_KEY },
	{ "TOGGLE_ORIGINAL_STYLE_COMMAND_BAR", ActionToggleOriginalStyleCommandBar, "Show/hide original style command bar", Action::normal_keys, true,  },
	{ "DO_ACTION", ActionDoAction, "Do action", Action::normal_keys, true, DO_ACTION_KEY },
	{ "CANCEL_ACTION", ActionCancelAction, "Cancel action", Action::normal_keys, true, CANCEL_ACTION_KEY },
	{ "MSG_SCROLL_UP", ActionMsgScrollUP, "Msg scroll up", Action::normal_keys, true, MSGSCROLL_UP_KEY },
	{ "MSG_SCROLL_DOWN", ActionMsgScrollDown, "Msg scroll down", Action::normal_keys, true, MSGSCROLL_DOWN_KEY },
	{ "SHOW_KEYS", ActionShowKeys, "Show keys", Action::normal_keys, true, OTHER_KEY },
	{ "DECREASE_DEBUG", ActionDecreaseDebug, "Decrease debug", Action::normal_keys, true, DECREASE_DEBUG_KEY },
	{ "INCREASE_DEBUG", ActionIncreaseDebug, "Increase debug", Action::normal_keys, true, INCREASE_DEBUG_KEY },
	{ "CLOSE_GUMPS", ActionCloseGumps, "Close gumps", Action::normal_keys, true, OTHER_KEY },
	{ "USE_ITEM", ActionUseItem, "Use item", Action::normal_keys, true, OTHER_KEY },
	{ "SHOW_EGGS", ActionShowEggs, "Show eggs", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_HACKMOVE", ActionToggleHackmove, "Toggle hack move", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_EGG_SPAWN", ActionToggleEggSpawn, "Toggle egg spawn", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_UNLIMITED_CASTING", ActionToggleUnlimitedCasting, "Toggle unlimited casting", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_NO_DARKNESS", ActionToggleNoDarkness, "Toggle no darkness", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_PICKPOCKET_MODE", ActionTogglePickpocket, "Toggle pickpocket mode", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_GOD_MODE",  ActionToggleGodMode, "Toggle god mode", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_ETHEREAL",  ActionToggleEthereal, "Toggle ethereal mode", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_X_RAY",  ActionToggleX_Ray, "Toggle X-ray mode", Action::cheat_keys, true, OTHER_KEY },
	{ "HEAL_PARTY", ActionHealParty, "Heal party", Action::cheat_keys, true, OTHER_KEY },
	{ "TELEPORT_TO_CURSOR", ActionTeleportToCursor, "Teleport to cursor", Action::cheat_keys, true, OTHER_KEY },
	{ "TOGGLE_CHEATS", ActionToggleCheats, "Toggle cheats", Action::normal_keys, true, OTHER_KEY },
	{ "DO_NOTHING", ActionDoNothing, "", Action::dont_show, true, OTHER_KEY },
	{ "", 0, "", Action::dont_show, false, OTHER_KEY } //terminator
};

const struct {
	const char *s;
	SDL_Keycode k;
} SDL_KeycodeStringTable[] = {
	{"LCTRL",     SDLK_LCTRL},
	{"RCTRL",     SDLK_RCTRL},
	{"LALT",      SDLK_LALT},
	{"RALT",      SDLK_RALT},
	{"LSHIFT",    SDLK_LSHIFT},
	{"RSHIFT",    SDLK_RSHIFT},
	{"BACKSPACE", SDLK_BACKSPACE},
	{"TAB",       SDLK_TAB}, 
	{"ENTER",     SDLK_RETURN}, 
	{"PAUSE",     SDLK_PAUSE}, 
	{"ESC",       SDLK_ESCAPE}, 
	{"SPACE",     SDLK_SPACE}, 
	{"DEL",       SDLK_DELETE}, 
	{"KP0",       SDLK_KP_0},
	{"KP1",       SDLK_KP_1},
	{"KP2",       SDLK_KP_2},
	{"KP3",       SDLK_KP_3},
	{"KP4",       SDLK_KP_4},
	{"KP5",       SDLK_KP_5},
	{"KP6",       SDLK_KP_6},
	{"KP7",       SDLK_KP_7},
	{"KP8",       SDLK_KP_8},
	{"KP9",       SDLK_KP_9},
	{"KP.",       SDLK_KP_PERIOD}, 
	{"KP/",       SDLK_KP_DIVIDE}, 
	{"KP*",       SDLK_KP_MULTIPLY}, 
	{"KP-",       SDLK_KP_MINUS}, 
	{"KP+",       SDLK_KP_PLUS}, 
	{"KP_ENTER",  SDLK_KP_ENTER}, 
	{"UP",        SDLK_UP}, 
	{"DOWN",      SDLK_DOWN}, 
	{"RIGHT",     SDLK_RIGHT}, 
	{"LEFT",      SDLK_LEFT}, 
	{"INSERT",    SDLK_INSERT}, 
	{"HOME",      SDLK_HOME}, 
	{"END",       SDLK_END}, 
	{"PAGEUP",    SDLK_PAGEUP}, 
	{"PAGEDOWN",  SDLK_PAGEDOWN}, 
	{"F1",        SDLK_F1}, 
	{"F2",        SDLK_F2}, 
	{"F3",        SDLK_F3}, 
	{"F4",        SDLK_F4}, 
	{"F5",        SDLK_F5}, 
	{"F6",        SDLK_F6}, 
	{"F7",        SDLK_F7}, 
	{"F8",        SDLK_F8}, 
	{"F9",        SDLK_F9}, 
	{"F10",       SDLK_F10}, 
	{"F11",       SDLK_F11}, 
	{"F12",       SDLK_F12}, 
	{"F13",       SDLK_F13}, 
	{"F14",       SDLK_F14}, 
	{"F15",       SDLK_F15},
#ifdef HAVE_JOYSTICK_SUPPORT
// hackishly map joystick buttons to unused keyboard syms
	{"JOY_UP",            JOY_UP},
	{"JOY_DOWN",          JOY_DOWN},
	{"JOY_LEFT",          JOY_LEFT},
	{"JOY_RIGHT",         JOY_RIGHT},
	{"JOY_RIGHTUP",       JOY_RIGHTUP},
	{"JOY_RIGHTDOWN",     JOY_RIGHTDOWN},
	{"JOY_LEFTUP",        JOY_LEFTUP},
	{"JOY_LEFTDOWN",      JOY_LEFTDOWN},
	{"JOY_UP2",           JOY_UP2},
	{"JOY_DOWN2",         JOY_DOWN2},
	{"JOY_LEFT2",         JOY_LEFT2},
	{"JOY_RIGHT2",        JOY_RIGHT2},
	{"JOY_RIGHTUP2",      JOY_RIGHTUP2},
	{"JOY_RIGHTDOWN2",    JOY_RIGHTDOWN2},
	{"JOY_LEFTUP2",       JOY_LEFTUP2},
	{"JOY_LEFTDOWN2",     JOY_LEFTDOWN2},
	{"JOY_UP3",           JOY_UP3},
	{"JOY_DOWN3",         JOY_DOWN3},
	{"JOY_LEFT3",         JOY_LEFT3},
	{"JOY_RIGHT3",        JOY_RIGHT3},
	{"JOY_RIGHTUP3",      JOY_RIGHTUP3},
	{"JOY_RIGHTDOWN3",    JOY_RIGHTDOWN3},
	{"JOY_LEFTUP3",       JOY_LEFTUP3},
	{"JOY_LEFTDOWN3",     JOY_LEFTDOWN3},
	{"JOY_UP4",           JOY_UP4},
	{"JOY_DOWN4",         JOY_DOWN4},
	{"JOY_LEFT4",         JOY_LEFT4},
	{"JOY_RIGHT4",        JOY_RIGHT4},
	{"JOY_RIGHTUP4",      JOY_RIGHTUP4},
	{"JOY_RIGHTDOWN4",    JOY_RIGHTDOWN4},
	{"JOY_LEFTUP4",       JOY_LEFTUP4},
	{"JOY_LEFTDOWN4",     JOY_LEFTDOWN4},
	{"JOY_HAT_UP",        JOY_HAT_UP},
	{"JOY_HAT_DOWN",      JOY_HAT_DOWN},
	{"JOY_HAT_LEFT",      JOY_HAT_LEFT},
	{"JOY_HAT_RIGHT",     JOY_HAT_RIGHT},
	{"JOY_HAT_RIGHTUP",   JOY_HAT_RIGHTUP},
	{"JOY_HAT_RIGHTDOWN", JOY_HAT_RIGHTDOWN},
	{"JOY_HAT_LEFTUP",    JOY_HAT_LEFTUP},
	{"JOY_HAT_LEFTDOWN",  JOY_HAT_LEFTDOWN},
	{"JOY0",              JOY0},
	{"JOY1",              JOY1},
	{"JOY2",              JOY2},
	{"JOY3",              JOY3},
	{"JOY4",              JOY4},
	{"JOY5",              JOY5},
	{"JOY6",              JOY6},
	{"JOY7",              JOY7},
	{"JOY8",              JOY8},
	{"JOY9",              JOY9},
	{"JOY10",             JOY10},
	{"JOY11",             JOY11},
	{"JOY12",             JOY12},
	{"JOY13",             JOY13},
	{"JOY14",             JOY14},
	{"JOY15",             JOY15},
	{"JOY16",             JOY16},
	{"JOY17",             JOY17},
	{"JOY18",             JOY18},
	{"JOY19",             JOY19},
#endif /* HAVE_JOYSTICK_SUPPORT */
	{"", SDLK_UNKNOWN} // terminator
};


typedef std::map<std::string, SDL_Keycode> ParseKeyMap;
typedef std::map<std::string, const Action*> ParseActionMap;

static ParseKeyMap keys;
static ParseActionMap actions;

const Action doNothingAction = { "DO_NOTHING", ActionDoNothing, "", Action::dont_show, true, OTHER_KEY };

KeyBinder::KeyBinder(Configuration *config)
{
	FillParseMaps();

	std::string keyfilename, dir;
	config->value("config/keys",keyfilename,"(default)");
	bool key_file_exists = fileExists(keyfilename.c_str());

	if(keyfilename != "(default)" && !key_file_exists)
		fprintf(stderr, "Couldn't find the default key setting at %s - trying defaultkeys.txt in the data directory\n", keyfilename.c_str());
	if(keyfilename == "(default)" || !key_file_exists)
	{
		config->value("config/datadir", dir, "./data");
		keyfilename = dir + "/defaultkeys.txt";
	}
	LoadFromFile(keyfilename.c_str()); // will throw() if not found

	LoadGameSpecificKeys(); // won't load if file isn't found
	LoadFromPatch(); // won't load if file isn't found

#ifdef HAVE_JOYSTICK_SUPPORT
	joystick = NULL;
	std::string enable_joystick_str;
	config->value("config/joystick/enable_joystick", enable_joystick_str, "no");

	if(enable_joystick_str == "no")
		enable_joystick =  -1;
#if SDL_VERSION_ATLEAST(2,0,0)
	else if(enable_joystick_str == "auto")
		init_joystick(127);
#endif
	else
		init_joystick(clamp(atoi(enable_joystick_str.c_str()), 0, 9));

	int config_int;
	uint16 max_delay = 10000; // 10 seconds but means no repeat
	uint16 max_deadzone = 32766; // one less than highest possible
	config->value("config/joystick/repeat_hat", repeat_hat, false);


	config->value("config/joystick/repeat_delay", config_int, 50);
	joy_repeat_delay = config_int < max_delay ? config_int : max_delay;
	if(joy_repeat_delay == max_delay)
		joy_repeat_enabled = false;
	else
		joy_repeat_enabled = true;

// AXES_PAIR1
	config->value("config/joystick/axes_pair1/x_axis", config_int, 0);
	x_axis = config_int < 255 ? config_int : 255;
	config->value("config/joystick/axes_pair1/y_axis", config_int, 1);
	y_axis = config_int < 255 ? config_int : 255;
	config->value("config/joystick/axes_pair1/x_deadzone", config_int, 8000);
	x_axis_deadzone = config_int < max_deadzone ? config_int : max_deadzone;
	config->value("config/joystick/axes_pair1/y_deadzone", config_int, 8000);
	y_axis_deadzone = config_int < max_deadzone ? config_int : max_deadzone;
	config->value("config/joystick/axes_pair1/delay", config_int, 110);
	pair1_delay = config_int < max_delay ? config_int : max_delay;
// AXES_PAIR2
	config->value("config/joystick/axes_pair2/x_axis", config_int, 3);
	x_axis2 = config_int < 255 ? config_int : 255;
	config->value("config/joystick/axes_pair2/y_axis", config_int, 2);
	y_axis2 = config_int < 255 ? config_int : 255;
	config->value("config/joystick/axes_pair2/x_deadzone", config_int, 8000);
	x_axis2_deadzone = config_int < max_deadzone ? config_int : max_deadzone;
	config->value("config/joystick/axes_pair2/y_deadzone", config_int, 8000);
	y_axis2_deadzone = config_int < max_deadzone ? config_int : max_deadzone;
	config->value("config/joystick/axes_pair2/delay", config_int, 110);
	pair2_delay = config_int < max_delay ? config_int : max_delay;
// AXES_PAIR3
	config->value("config/joystick/axes_pair3/x_axis", config_int, 4);
	x_axis3 = config_int < 255 ? config_int : 255;
	config->value("config/joystick/axes_pair3/y_axis", config_int, 5);
	y_axis3 = config_int < 255 ? config_int : 255;
	config->value("config/joystick/axes_pair3/x_deadzone", config_int, 8000);
	x_axis3_deadzone = config_int < max_deadzone ? config_int : max_deadzone;
	config->value("config/joystick/axes_pair3/y_deadzone", config_int, 8000);
	y_axis3_deadzone = config_int < max_deadzone ? config_int : max_deadzone;
	config->value("config/joystick/axes_pair3/delay", config_int, 110);
	pair3_delay = config_int < max_delay ? config_int : max_delay;
// AXES_PAIR4
	config->value("config/joystick/axes_pair4/x_axis", config_int, 6);
	x_axis4 = config_int < 255 ? config_int : 255;
	config->value("config/joystick/axes_pair4/y_axis", config_int, 7);
	y_axis4 = config_int < 255 ? config_int : 255;
	config->value("config/joystick/axes_pair4/x_deadzone", config_int, 8000);
	x_axis4_deadzone = config_int < max_deadzone ? config_int : max_deadzone;
	config->value("config/joystick/axes_pair4/y_deadzone", config_int, 8000);
	y_axis4_deadzone = config_int < max_deadzone ? config_int : max_deadzone;
	config->value("config/joystick/axes_pair4/delay", config_int, 110);
	pair4_delay = config_int < max_delay ? config_int : max_delay;

	next_axes_pair_update = next_axes_pair2_update = next_axes_pair3_update = 0;
	next_axes_pair4_update = next_joy_repeat_time = 0;
#endif
}

KeyBinder::~KeyBinder()
{
}

void KeyBinder::AddKeyBinding( SDL_Keycode key, int mod, const Action* action,
				 int nparams, int* params)
{
	SDL_Keysym k;
	ActionType a;

	#if SDL_VERSION_ATLEAST(1, 3, 0)
	k.scancode = (SDL_Scancode)0;	
	#else
	k.scancode = 0;
	#endif
	k.sym      = key;
	k.mod      = (SDL_Keymod) mod;
	a.action    = action;
	int i;	// For MSVC
	for (i = 0; i < c_maxparams && i < nparams; i++)
		a.params[i] = params[i];
	for (i = nparams; i < c_maxparams; i++)
		a.params[i] = -1;
	
	bindings[k] = a;
}

ActionType KeyBinder::get_ActionType(SDL_Keysym key)
{
	KeyMap::iterator sdlkey_index = get_sdlkey_index(key);
	if(sdlkey_index == bindings.end())
	{
		ActionType actionType = {&doNothingAction, {0}};
		return actionType;
	}
	return (*sdlkey_index).second;
}

ActionKeyType KeyBinder::GetActionKeyType(ActionType a)
{
	return a.action->action_key_type;
}

bool KeyBinder::DoAction(ActionType const& a) const
{
	if (!a.action->allow_in_vehicle && Game::get_game()->get_player()->is_in_vehicle() && Game::get_game()->get_game_type() != NUVIE_GAME_MD)
	{
		Game::get_game()->get_event()->display_not_aboard_vehicle();
		return true;
	}
	else if(a.action->key_type == Action::cheat_keys && !Game::get_game()->are_cheats_enabled())
	{
		new TextEffect("Cheats are disabled");
		return true;
	}
	a.action->func(a.params);

	return true;
}

KeyMap::iterator KeyBinder::get_sdlkey_index(SDL_Keysym keysym)
{
	SDL_Keysym key = keysym;
	key.mod = KMOD_NONE;
	if (keysym.mod & KMOD_SHIFT)
		key.mod = (SDL_Keymod)(key.mod | KMOD_SHIFT);
	if (keysym.mod & KMOD_CTRL)
		key.mod = (SDL_Keymod)(key.mod | KMOD_CTRL);
#if defined(MACOS) || defined(MACOSX)
	// map Meta to Alt on MacOS
	if (keysym.mod & KMOD_GUI)
		key.mod = (SDL_Keymod)(key.mod | KMOD_ALT);
#else
	if (keysym.mod & KMOD_ALT)
		key.mod = (SDL_Keymod)(key.mod | KMOD_ALT);
#endif

	return bindings.find(key);
}

bool KeyBinder::HandleEvent(const SDL_Event *ev)
{
	SDL_Keysym key = ev->key.keysym;
	KeyMap::iterator sdlkey_index;

	if (ev->type != SDL_KEYDOWN)
		return false;

	sdlkey_index = get_sdlkey_index(key);
	if (sdlkey_index != bindings.end())
		return DoAction((*sdlkey_index).second);

	if (ev->key.keysym.sym != SDLK_LALT && ev->key.keysym.sym != SDLK_RALT
	    && ev->key.keysym.sym != SDLK_LCTRL && ev->key.keysym.sym != SDLK_RCTRL)
	{
		handle_wrong_key_pressed();
	}
	return false;
}

void KeyBinder::handle_wrong_key_pressed()
{
	if(Game::get_game()->get_event()->get_mode() != MOVE_MODE)
		Game::get_game()->get_event()->cancelAction();
	else
	{
		Game::get_game()->get_scroll()->display_string("what?\n\n");
		Game::get_game()->get_scroll()->display_prompt();
	}
}

bool KeyBinder::handle_always_available_keys(ActionType a)
{
	switch(a.action->action_key_type)
	{
		case TOGGLE_AUDIO_KEY: // FIXME - Shutting off the music in cutscenes will not have any music play when
		case TOGGLE_MUSIC_KEY: //         you toggle it back on. It has to wait for the engine to play another song
		case TOGGLE_SFX_KEY:
		case TOGGLE_FPS_KEY: // fps is not available in intro or when viewing ending with command line cheat
		case TOGGLE_FULLSCREEN_KEY:
		case DECREASE_DEBUG_KEY:
		case INCREASE_DEBUG_KEY:
		case QUIT_KEY: // FIXME - doesn't currently work in intro or when viewing ending with command line cheat
			a.action->func(a.params);
			return true;
		default: return false;
	}
}

void KeyBinder::ShowKeys() const // FIXME This doesn't look very good, the font is missing
{                                // some characters, and it is longer than msgscroll can hold
//	if(Game::get_game()->is_orig_style())
	{
		std::vector<string>::const_iterator iter;
		string keys;
		MsgScroll *scroll = Game::get_game()->get_scroll();
		scroll->set_autobreak(true);

		for (iter = keyhelp.begin(); iter != keyhelp.end(); ++iter)
		{
			keys = "\n";
			keys.append(iter->c_str());
			scroll->display_string(keys, 1);
		}
		scroll->message("\n\n\t");
	}
}

void KeyBinder::ParseText(char *text, int len)
{
	char *ptr, *end;
	const char LF = '\n';
	
	ptr = text;
	
	// last (useful) line must end with LF
	while ((ptr - text) < len && (end = strchr(ptr, LF)) != 0) {
		*end = '\0';
		ParseLine(ptr);
		ptr = end + 1;
	}  
}

static void skipspace(string &s) {
	size_t i=s.find_first_not_of(chardata.whitespace);
	if(i&&i!=string::npos)
		s.erase(0,i);
}


void KeyBinder::ParseLine(char *line)
{
	size_t i;
	SDL_Keysym k;
	ActionType a;
	k.sym      = SDLK_UNKNOWN;
	k.mod      = KMOD_NONE;
	string s = line, u;
	string d, desc, keycode;
	bool show;
	
	skipspace(s);
	
	// comments and empty lines
	if (s.length() == 0 || s[0] == '#')
		return;
	
	u = s;
	u = to_uppercase(u);
	
	// get key
	while (s.length() && !isspace(s[0])) {
		// check modifiers
		if (u.substr(0,4) == "ALT-") {
			k.mod = (SDL_Keymod)(k.mod | KMOD_ALT);
			s.erase(0,4); u.erase(0,4);
		} else if (u.substr(0,5) == "CTRL-") {
			k.mod = (SDL_Keymod)(k.mod | KMOD_CTRL);
			s.erase(0,5); u.erase(0,5);
		} else if (u.substr(0,6) == "SHIFT-") {
			k.mod = (SDL_Keymod)(k.mod | KMOD_SHIFT);
			s.erase(0,6); u.erase(0,6);
		} else {
			
			i=s.find_first_of(chardata.whitespace);

			keycode = s.substr(0, i); s.erase(0, i);
			string t = to_uppercase(keycode);
			
			if (t.length() == 0) {
				cerr << "Keybinder: parse error in line: " << s << endl;
				return;
			} else if (t.length() == 1) {
				// translate 1-letter keys straight to SDL_Keycode
				char c = t[0];
				if (c >= 33 && c <= 122 && c != 37) {
					if (c >= 'A' && c <= 'Z')
						c += 32; // need lowercase
					k.sym = static_cast<SDL_Keycode>(c);
				} else {
					cerr << "Keybinder: unsupported key: " << keycode << endl;
				}
			} else {
				// lookup in table
				ParseKeyMap::iterator key_index;
				key_index = keys.find(t);
				if (key_index != keys.end()) {
					k.sym = (*key_index).second;
				} else {
					cerr << "Keybinder: unsupported key: " << keycode << endl;
					return;
				}
			}
		}
	}
	
	if (k.sym == SDLK_UNKNOWN) {
		cerr << "Keybinder: parse error in line: " << s << endl;
		return;
	}
	
	// get function
	skipspace(s);
	
	i=s.find_first_of(chardata.whitespace);
	string t = s.substr(0, i); s.erase(0, i);
	t = to_uppercase(t);
	
	ParseActionMap::iterator action_index;
	action_index = actions.find(t);
	if (action_index != actions.end()) {
		a.action = (*action_index).second;
	} else {
		cerr << "Keybinder: unsupported action: " << t << endl;
		return;
	}
	
	// get params
	skipspace(s);
	
	int np = 0;
	while (s.length() && s[0] != '#' && np < c_maxparams) {
		i=s.find_first_of(chardata.whitespace);
		string t = s.substr(0, i);
		s.erase(0, i);
		skipspace(s);
		
		int p = atoi(t.c_str());
		a.params[np++] = p;
	}
	
	// read optional help comment
	if (s.length() >= 1 && s[0] == '#') {
		if (s.length() >= 2 && s[1] == '-') {
			show = false;
		} else {
			s.erase(0,1);
			skipspace(s);
			d = s;
			show = true;
		}
	} else {
		d = a.action->desc;
		show = a.action->key_type != Action::dont_show;
	}
	
	if (show) {
		desc = "";
		if (k.mod & KMOD_CTRL)
			desc += "Ctrl-";
#if defined(MACOS) || defined(MACOSX)
		if (k.mod & KMOD_ALT)
			desc += "Cmd-";
#else
		if (k.mod & KMOD_ALT)
			desc += "Alt-";
#endif
		if (k.mod & KMOD_SHIFT)
			desc += "Shift-";
		if(keycode == "`")
			desc += "grave";
		else
			desc += keycode;
		desc += " - " + d;
		
		// add to help list
		if (a.action->key_type == Action::normal_keys)
			keyhelp.push_back(desc);
		else if (a.action->key_type == Action::cheat_keys)
			cheathelp.push_back(desc);
	}
	
	// bind key
	AddKeyBinding(k.sym, k.mod, a.action, np, a.params);
}

void KeyBinder::LoadFromFileInternal(const char* filename)
{
	ifstream keyfile;

	openFile(keyfile, filename);
	char temp[1024]; // 1024 should be long enough
	while(!keyfile.eof()) {
		keyfile.getline(temp, 1024);
		if (keyfile.gcount() >= 1023) {
			fprintf(stderr, "Keybinder: parse error: line too long. Skipping rest of file.\n");
			return;
		}
		ParseLine(temp);
	}
	keyfile.close();
}

void KeyBinder::LoadFromFile(const char* filename)
{
	
	Flush();
	
	ConsoleAddInfo("Loading keybindings from file %s\n", filename);
	LoadFromFileInternal(filename);
}

void KeyBinder::LoadGameSpecificKeys()
{
	string key_path_str;
	string default_key_path;
	Configuration *config = Game::get_game()->get_config();
	config->value("config/datadir", default_key_path, "./data");
	nuvie_game_t game_type = get_game_type(config);

	if(game_type == NUVIE_GAME_U6)
		default_key_path += "/u6keys.txt";
	else if(game_type ==  NUVIE_GAME_MD)
		default_key_path += "/mdkeys.txt";
	else // SE
		default_key_path += "/sekeys.txt";

	config->value(config_get_game_key(config) + "/game_specific_keys", key_path_str, default_key_path.c_str());
	const char *key_path;
	if(key_path_str == "(default)")
		key_path = default_key_path.c_str();
	else
		key_path = key_path_str.c_str();
	if (fileExists(key_path)) {
		ConsoleAddInfo("Loading %s\n", key_path);
		LoadFromFileInternal(key_path);
	}
	else // These aren't critical so failing to load doesn't matter much
		ConsoleAddInfo("Couldn't find $s\n", key_path);
}

void KeyBinder::LoadFromPatch() // FIXME default should probably be system specific
{
	string PATCH_KEYS;
	Configuration *config = Game::get_game()->get_config();

	config->value(config_get_game_key(config) + "/patch_keys", PATCH_KEYS, "./patchkeys.txt");
	if (fileExists(PATCH_KEYS.c_str())) {
		ConsoleAddInfo("Loading patch keybindings\n");
		LoadFromFileInternal(PATCH_KEYS.c_str());
	}
}

// codes used in keybindings-files. (use uppercase here)
void KeyBinder::FillParseMaps()
{
	int i;	// For MSVC
	for (i = 0; strlen(SDL_KeycodeStringTable[i].s) > 0; i++)
		keys[SDL_KeycodeStringTable[i].s] = SDL_KeycodeStringTable[i].k;
	
	for (i = 0; strlen(NuvieActions[i].s) > 0; i++)
		actions[NuvieActions[i].s] = &(NuvieActions[i]);
}

#ifdef HAVE_JOYSTICK_SUPPORT

uint8 KeyBinder::get_axis(uint8 index)
{
	switch(index)
	{
		case 0: return x_axis;
		case 1: return y_axis;
		case 2: return x_axis2;
		case 3: return y_axis2;
		case 4: return x_axis3;
		case 5: return y_axis3;
		case 6: return x_axis4;
		case 7:
		default: return y_axis4;
	}
}

void KeyBinder::set_axis(uint8 index, uint8 value)
{
	switch(index)
	{
		case 0: x_axis = value;
		case 1: y_axis = value;
		case 2: x_axis2 = value;
		case 3: y_axis2 = value;
		case 4: x_axis3 = value;
		case 5: y_axis3 = value;
		case 6: x_axis4 = value;
		case 7:
		default: y_axis4 = value;
	}
}

joy_axes_pairs KeyBinder::get_axes_pair(int axis)
{
	if(axis == x_axis || axis == y_axis)
		return AXES_PAIR1;
	else if(axis == x_axis2 || axis == y_axis2)
		return AXES_PAIR2;
	else if(axis == x_axis3 || axis == y_axis3)
		return AXES_PAIR3;
	else if(axis == x_axis4 || axis == y_axis4)
		return AXES_PAIR4;
	else
		return UNHANDLED_AXES_PAIR;
}

uint16 KeyBinder::get_x_axis_deadzone(joy_axes_pairs axes_pair)
{
	switch(axes_pair)
	{
		case AXES_PAIR1: return x_axis_deadzone;
		case AXES_PAIR2: return x_axis2_deadzone;
		case AXES_PAIR3: return x_axis3_deadzone;
		case AXES_PAIR4: return x_axis4_deadzone;
		default: return 0; // shouldn't happen
	}
}

uint16 KeyBinder::get_y_axis_deadzone(joy_axes_pairs axes_pair)
{
	switch(axes_pair)
	{
		case AXES_PAIR1: return y_axis_deadzone;
		case AXES_PAIR2: return y_axis2_deadzone;
		case AXES_PAIR3: return y_axis3_deadzone;
		case AXES_PAIR4: return y_axis4_deadzone;
		default: return 0; // shouldn't happen
	}
}

SDL_Keycode KeyBinder::get_key_from_joy_axis_motion(int axis, bool repeating)
{
	joy_axes_pairs axes_pair =  get_axes_pair(axis);

	if(axes_pair == UNHANDLED_AXES_PAIR) // joystick NULL check doesn't seem to be needed - It is also checked before tring to repeat
		return SDLK_UNKNOWN;
	sint8 xoff = 0;
	sint8 yoff = 0;
	int xaxis, yaxis;

	switch(axes_pair)
	{
		case AXES_PAIR1: xaxis = x_axis; yaxis = y_axis; break;
		case AXES_PAIR2: xaxis = x_axis2; yaxis = y_axis2; break;
		case AXES_PAIR3: xaxis = x_axis3; yaxis = y_axis3; break;
		case AXES_PAIR4: xaxis = x_axis4; yaxis = y_axis4; break;
		default: return SDLK_UNKNOWN; // shouldn't happen
	}

	if(xaxis != 255 && abs(SDL_JoystickGetAxis(joystick, xaxis)) > get_x_axis_deadzone(axes_pair))
		xoff = (SDL_JoystickGetAxis(joystick, xaxis) < 0 ? -1 : 1);
	if(yaxis != 255 && abs(SDL_JoystickGetAxis(joystick, yaxis)) > get_y_axis_deadzone(axes_pair))
		yoff = (SDL_JoystickGetAxis(joystick, yaxis) < 0 ? -1 : 1);

	uint8 dir = get_direction_code(xoff, yoff);
	if(axes_pair == AXES_PAIR1)
	{
		if(dir == NUVIE_DIR_NONE)
		{
			next_axes_pair_update = 0; // centered so okay to reset
			if(!repeat_hat)
				next_joy_repeat_time = SDL_GetTicks() + joy_repeat_delay;
			return SDLK_UNKNOWN;
		}
		else if((repeating && next_joy_repeat_time > SDL_GetTicks()) || (!repeating && next_axes_pair_update > SDL_GetTicks())) // don't repeat too fast
			return SDLK_UNKNOWN;

		next_axes_pair_update = SDL_GetTicks() + pair1_delay;
		if(!repeat_hat)
			next_joy_repeat_time = SDL_GetTicks() + joy_repeat_delay;
		switch(dir)
		{
			case NUVIE_DIR_N: return JOY_UP;
			case NUVIE_DIR_E: return JOY_RIGHT;
			case NUVIE_DIR_S: return JOY_DOWN;
			case NUVIE_DIR_W: return JOY_LEFT;
			case NUVIE_DIR_NE: return JOY_RIGHTUP;
			case NUVIE_DIR_SE: return JOY_RIGHTDOWN;
			case NUVIE_DIR_SW: return JOY_LEFTDOWN;
			case NUVIE_DIR_NW: return JOY_LEFTUP;
			default: return SDLK_UNKNOWN; // shouldn't happen
		}
	}
	else if(axes_pair == AXES_PAIR2)
	{
		if(dir == NUVIE_DIR_NONE)
		{
			next_axes_pair2_update = 0; // centered so okay to reset
			return SDLK_UNKNOWN;
		}
		else if(next_axes_pair2_update > SDL_GetTicks()) // don't repeat too fast
			return SDLK_UNKNOWN;
		else
			next_axes_pair2_update = SDL_GetTicks() + pair2_delay;
		switch(dir)
		{
			case NUVIE_DIR_N: return JOY_UP2;
			case NUVIE_DIR_E: return JOY_RIGHT2;
			case NUVIE_DIR_S: return JOY_DOWN2;
			case NUVIE_DIR_W: return JOY_LEFT2;
			case NUVIE_DIR_NE: return JOY_RIGHTUP2;
			case NUVIE_DIR_SE: return JOY_RIGHTDOWN2;
			case NUVIE_DIR_SW: return JOY_LEFTDOWN2;
			case NUVIE_DIR_NW: return JOY_LEFTUP2;
			default: return SDLK_UNKNOWN; // shouldn't happen
		}
	}
	else if(axes_pair == AXES_PAIR3)
	{
		if(dir == NUVIE_DIR_NONE)
		{
			next_axes_pair3_update = 0; // centered so okay to reset
			return SDLK_UNKNOWN;
		}
		else if(next_axes_pair3_update > SDL_GetTicks()) // don't repeat too fast
			return SDLK_UNKNOWN;
		else
			next_axes_pair3_update = SDL_GetTicks() + pair3_delay;
		switch(dir)
		{
			case NUVIE_DIR_N: return JOY_UP3;
			case NUVIE_DIR_E: return JOY_RIGHT3;
			case NUVIE_DIR_S: return JOY_DOWN3;
			case NUVIE_DIR_W: return JOY_LEFT3;
			case NUVIE_DIR_NE: return JOY_RIGHTUP3;
			case NUVIE_DIR_SE: return JOY_RIGHTDOWN3;
			case NUVIE_DIR_SW: return JOY_LEFTDOWN3;
			case NUVIE_DIR_NW: return JOY_LEFTUP3;
			default: return SDLK_UNKNOWN; // shouldn't happen
		}
	}
	else // AXES_PAIR4
	{
		if(dir == NUVIE_DIR_NONE)
		{
			next_axes_pair4_update = 0; // centered so okay to reset
			return SDLK_UNKNOWN;
		}
		else if(next_axes_pair4_update > SDL_GetTicks()) // don't repeat too fast
			return SDLK_UNKNOWN;

		next_axes_pair4_update = SDL_GetTicks() + pair4_delay;
		switch(dir)
		{
			case NUVIE_DIR_N: return JOY_UP4;
			case NUVIE_DIR_E: return JOY_RIGHT4;
			case NUVIE_DIR_S: return JOY_DOWN4;
			case NUVIE_DIR_W: return JOY_LEFT4;
			case NUVIE_DIR_NE: return JOY_RIGHTUP4;
			case NUVIE_DIR_SE: return JOY_RIGHTDOWN4;
			case NUVIE_DIR_SW: return JOY_LEFTDOWN4;
			case NUVIE_DIR_NW: return JOY_LEFTUP4;
			default: return SDLK_UNKNOWN; // shouldn't happen
		}
	}
}

SDL_Keycode KeyBinder::get_key_from_joy_button(uint8 button)
{
	switch(button)
	{
		case 0: return JOY0;
		case 1: return JOY1;
		case 2: return JOY2;
		case 3: return JOY3;
		case 4: return JOY4;
		case 5: return JOY5;
		case 6: return JOY6;
		case 7: return JOY7;
		case 8: return JOY8;
		case 9: return JOY9;
		case 10: return JOY10;
		case 11: return JOY11;
		case 12: return JOY12;
		case 13: return JOY13;
		case 14: return JOY14;
		case 15: return JOY15;
		case 16: return JOY16;
		case 17: return JOY17;
		case 18: return JOY18;
		case 19: return JOY19;
		default: return SDLK_UNKNOWN; // unhandled button
	}
}

SDL_Keycode KeyBinder::get_key_from_joy_hat(SDL_JoyHatEvent jhat)
{
//	if(jhat.which == 0) // only handling one jhat for now and some devices don't start at 0
		return get_key_from_joy_hat_button(jhat.value);
//	else
//		return SDLK_UNKNOWN; // unhandled hat
}

SDL_Keycode KeyBinder::get_key_from_joy_hat_button(uint8 hat_button)
{
	if(repeat_hat)
		next_joy_repeat_time = SDL_GetTicks() + joy_repeat_delay;
	switch(hat_button)
	{
		case SDL_HAT_UP: return JOY_HAT_UP;
		case SDL_HAT_DOWN: return JOY_HAT_DOWN;
		case SDL_HAT_LEFT: return JOY_HAT_LEFT;
		case SDL_HAT_RIGHT: return JOY_HAT_RIGHT;
		case SDL_HAT_RIGHTUP: return JOY_HAT_RIGHTUP;
		case SDL_HAT_RIGHTDOWN: return JOY_HAT_RIGHTDOWN;
		case SDL_HAT_LEFTUP: return JOY_HAT_LEFTUP;
		case SDL_HAT_LEFTDOWN: return JOY_HAT_LEFTDOWN;
		default: return SDLK_UNKNOWN; // center or unhandled position
	}
}

SDL_Keycode KeyBinder::get_key_from_joy_events(SDL_Event *event)
{
	if(event->type == SDL_JOYBUTTONUP)
		return get_key_from_joy_button(event->jbutton.button);
	else if(event->type == SDL_JOYHATMOTION)
		return get_key_from_joy_hat(event->jhat);
	else if(event->type == SDL_JOYAXISMOTION)
		return get_key_from_joy_axis_motion(event->jaxis.axis, false);
	else
		return SDLK_UNKNOWN;
}

void KeyBinder::init_joystick(sint8 joy_num)
{
	enable_joystick = joy_num;
	if(joystick)
	{
		SDL_JoystickClose(joystick);
		joystick = NULL;
		if(enable_joystick == - 1)
			SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
	}
	enable_joystick = joy_num;

	if(enable_joystick > - 1)
	{
		int joystick_index = (int)enable_joystick;
		SDL_InitSubSystem(SDL_INIT_JOYSTICK);

		if(SDL_NumJoysticks() > 0)
		{
			for(int i=0; i < SDL_NumJoysticks(); i++)
				fprintf(stdout, "Joystick %i is %s.\n", i, SDL_JoystickNameForIndex(i));
#if SDL_VERSION_ATLEAST(2,0,0)
//can use SDL_GameControllerGetAttached(SDL_GameController* gamecontroller) when we implement SDL2
			if(enable_joystick == 127) // autodetect - seems to always pick joystick 0 but there is some possiblity that SDL couldn't open it
			{
				for(int i=0; joystick == NULL && i < SDL_NumJoysticks(); i++)
				{
					joystick_index = i;
					joystick = SDL_JoystickOpen(i);
				}
			}
			else
#endif
				joystick = SDL_JoystickOpen(joystick_index);
		}
		if(joystick == NULL)
		{
			if(enable_joystick == 127 || SDL_NumJoysticks() == 0)
				fprintf(stderr, "Couldn't find any joysticks.\n");
			else
				fprintf(stderr, "Joysticks number %d was not found.\n", joystick_index);
			SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
		}
		else
		{
			fprintf(stdout, "Using joystick #%u, \"%s\". It has %u axes, %u %s, and %u buttons.\n", joystick_index, SDL_JoystickNameForIndex(joystick_index),
			        SDL_JoystickNumAxes(joystick), SDL_JoystickNumHats(joystick), SDL_JoystickNumHats(joystick) == 1? "hat" : "hats", SDL_JoystickNumButtons(joystick));
			SDL_JoystickEventState(SDL_ENABLE);
		}
	}
}

#endif /* HAVE_JOYSTICK_SUPPORT */

char get_ascii_char_from_keysym(SDL_Keysym keysym)
{
	char ascii = 0;
	if(keysym.sym < 128)
	{
		ascii = (char) keysym.sym;
		if(ascii >= 97 && ascii <= 122 && keysym.mod & (KMOD_SHIFT|KMOD_CAPS))
		{
			ascii -= 32;
		}
	}
	return ascii;
}
