/*
 *      Copyright (C) 2005-2013 Team XBMC
 *      http://xbmc.org
 *
 *  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, 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 XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#include "system.h"
#include "interfaces/Builtins.h"
#include "ButtonTranslator.h"
#include "profiles/ProfilesManager.h"
#include "utils/URIUtils.h"
#include "input/Key.h"
#include "guilib/WindowIDs.h"
#include "input/MouseStat.h"
#include "input/XBMC_keytable.h"
#include "filesystem/File.h"
#include "filesystem/Directory.h"
#include "FileItem.h"
#include "utils/StringUtils.h"
#include "utils/log.h"
#include "utils/XBMCTinyXML.h"
#include "utils/RegExp.h"
#include "XBIRRemote.h"
#include "Util.h"

#include <algorithm>

#if defined(TARGET_WINDOWS)
#include "input/windows/WINJoystick.h"
#elif defined(HAS_SDL_JOYSTICK) || defined(HAS_EVENT_SERVER)
#include "SDLJoystick.h"
#endif

#define JOYSTICK_DEFAULT_MAP "_xbmc_"

using namespace std;
using namespace XFILE;

typedef struct
{
  const char* name;
  int action;
} ActionMapping;

typedef struct
{
  int origin;
  int target;
} WindowMapping;

static const ActionMapping actions[] =
{
        {"left"              , ACTION_MOVE_LEFT },
        {"right"             , ACTION_MOVE_RIGHT},
        {"up"                , ACTION_MOVE_UP   },
        {"down"              , ACTION_MOVE_DOWN },
        {"pageup"            , ACTION_PAGE_UP   },
        {"pagedown"          , ACTION_PAGE_DOWN},
        {"select"            , ACTION_SELECT_ITEM},
        {"highlight"         , ACTION_HIGHLIGHT_ITEM},
        {"parentdir"         , ACTION_NAV_BACK},       // backward compatibility
        {"parentfolder"      , ACTION_PARENT_DIR},
        {"back"              , ACTION_NAV_BACK},
        {"previousmenu"      , ACTION_PREVIOUS_MENU},
        {"info"              , ACTION_SHOW_INFO},
        {"pause"             , ACTION_PAUSE},
        {"stop"              , ACTION_STOP},
        {"skipnext"          , ACTION_NEXT_ITEM},
        {"skipprevious"      , ACTION_PREV_ITEM},
        {"fullscreen"        , ACTION_SHOW_GUI},
        {"aspectratio"       , ACTION_ASPECT_RATIO},
        {"stepforward"       , ACTION_STEP_FORWARD},
        {"stepback"          , ACTION_STEP_BACK},
        {"bigstepforward"    , ACTION_BIG_STEP_FORWARD},
        {"bigstepback"       , ACTION_BIG_STEP_BACK},
        {"chapterorbigstepforward", ACTION_CHAPTER_OR_BIG_STEP_FORWARD},
        {"chapterorbigstepback"   , ACTION_CHAPTER_OR_BIG_STEP_BACK},
        {"osd"               , ACTION_SHOW_OSD},
        {"showsubtitles"     , ACTION_SHOW_SUBTITLES},
        {"nextsubtitle"      , ACTION_NEXT_SUBTITLE},
        {"cyclesubtitle"     , ACTION_CYCLE_SUBTITLE},
        {"codecinfo"         , ACTION_SHOW_CODEC},
        {"nextpicture"       , ACTION_NEXT_PICTURE},
        {"previouspicture"   , ACTION_PREV_PICTURE},
        {"zoomout"           , ACTION_ZOOM_OUT},
        {"zoomin"            , ACTION_ZOOM_IN},
        {"playlist"          , ACTION_SHOW_PLAYLIST},
        {"queue"             , ACTION_QUEUE_ITEM},
        {"zoomnormal"        , ACTION_ZOOM_LEVEL_NORMAL},
        {"zoomlevel1"        , ACTION_ZOOM_LEVEL_1},
        {"zoomlevel2"        , ACTION_ZOOM_LEVEL_2},
        {"zoomlevel3"        , ACTION_ZOOM_LEVEL_3},
        {"zoomlevel4"        , ACTION_ZOOM_LEVEL_4},
        {"zoomlevel5"        , ACTION_ZOOM_LEVEL_5},
        {"zoomlevel6"        , ACTION_ZOOM_LEVEL_6},
        {"zoomlevel7"        , ACTION_ZOOM_LEVEL_7},
        {"zoomlevel8"        , ACTION_ZOOM_LEVEL_8},
        {"zoomlevel9"        , ACTION_ZOOM_LEVEL_9},
        {"nextcalibration"   , ACTION_CALIBRATE_SWAP_ARROWS},
        {"resetcalibration"  , ACTION_CALIBRATE_RESET},
        {"analogmove"        , ACTION_ANALOG_MOVE},
        {"analogmovex"       , ACTION_ANALOG_MOVE_X},
        {"analogmovey"       , ACTION_ANALOG_MOVE_Y},
        {"rotate"            , ACTION_ROTATE_PICTURE_CW},
        {"rotateccw"         , ACTION_ROTATE_PICTURE_CCW},
        {"close"             , ACTION_NAV_BACK}, // backwards compatibility
        {"subtitledelayminus", ACTION_SUBTITLE_DELAY_MIN},
        {"subtitledelay"     , ACTION_SUBTITLE_DELAY},
        {"subtitledelayplus" , ACTION_SUBTITLE_DELAY_PLUS},
        {"audiodelayminus"   , ACTION_AUDIO_DELAY_MIN},
        {"audiodelay"        , ACTION_AUDIO_DELAY},
        {"audiodelayplus"    , ACTION_AUDIO_DELAY_PLUS},
        {"subtitleshiftup"   , ACTION_SUBTITLE_VSHIFT_UP},
        {"subtitleshiftdown" , ACTION_SUBTITLE_VSHIFT_DOWN},
        {"subtitlealign"     , ACTION_SUBTITLE_ALIGN},
        {"audionextlanguage" , ACTION_AUDIO_NEXT_LANGUAGE},
        {"verticalshiftup"   , ACTION_VSHIFT_UP},
        {"verticalshiftdown" , ACTION_VSHIFT_DOWN},
        {"nextresolution"    , ACTION_CHANGE_RESOLUTION},
        {"audiotoggledigital", ACTION_TOGGLE_DIGITAL_ANALOG},
        {"number0"           , REMOTE_0},
        {"number1"           , REMOTE_1},
        {"number2"           , REMOTE_2},
        {"number3"           , REMOTE_3},
        {"number4"           , REMOTE_4},
        {"number5"           , REMOTE_5},
        {"number6"           , REMOTE_6},
        {"number7"           , REMOTE_7},
        {"number8"           , REMOTE_8},
        {"number9"           , REMOTE_9},
        {"osdleft"           , ACTION_OSD_SHOW_LEFT},
        {"osdright"          , ACTION_OSD_SHOW_RIGHT},
        {"osdup"             , ACTION_OSD_SHOW_UP},
        {"osddown"           , ACTION_OSD_SHOW_DOWN},
        {"osdselect"         , ACTION_OSD_SHOW_SELECT},
        {"osdvalueplus"      , ACTION_OSD_SHOW_VALUE_PLUS},
        {"osdvalueminus"     , ACTION_OSD_SHOW_VALUE_MIN},
        {"smallstepback"     , ACTION_SMALL_STEP_BACK},
        {"fastforward"       , ACTION_PLAYER_FORWARD},
        {"rewind"            , ACTION_PLAYER_REWIND},
        {"play"              , ACTION_PLAYER_PLAY},
        {"playpause"         , ACTION_PLAYER_PLAYPAUSE},
        {"switchplayer"      , ACTION_SWITCH_PLAYER},
        {"delete"            , ACTION_DELETE_ITEM},
        {"copy"              , ACTION_COPY_ITEM},
        {"move"              , ACTION_MOVE_ITEM},
        {"mplayerosd"        , ACTION_SHOW_MPLAYER_OSD},
        {"hidesubmenu"       , ACTION_OSD_HIDESUBMENU},
        {"screenshot"        , ACTION_TAKE_SCREENSHOT},
        {"rename"            , ACTION_RENAME_ITEM},
        {"togglewatched"     , ACTION_TOGGLE_WATCHED},
        {"scanitem"          , ACTION_SCAN_ITEM},
        {"reloadkeymaps"     , ACTION_RELOAD_KEYMAPS},
        {"volumeup"          , ACTION_VOLUME_UP},
        {"volumedown"        , ACTION_VOLUME_DOWN},
        {"mute"              , ACTION_MUTE},
        {"backspace"         , ACTION_BACKSPACE},
        {"scrollup"          , ACTION_SCROLL_UP},
        {"scrolldown"        , ACTION_SCROLL_DOWN},
        {"analogfastforward" , ACTION_ANALOG_FORWARD},
        {"analogrewind"      , ACTION_ANALOG_REWIND},
        {"moveitemup"        , ACTION_MOVE_ITEM_UP},
        {"moveitemdown"      , ACTION_MOVE_ITEM_DOWN},
        {"contextmenu"       , ACTION_CONTEXT_MENU},
        {"shift"             , ACTION_SHIFT},
        {"symbols"           , ACTION_SYMBOLS},
        {"cursorleft"        , ACTION_CURSOR_LEFT},
        {"cursorright"       , ACTION_CURSOR_RIGHT},
        {"showtime"          , ACTION_SHOW_OSD_TIME},
        {"analogseekforward" , ACTION_ANALOG_SEEK_FORWARD},
        {"analogseekback"    , ACTION_ANALOG_SEEK_BACK},
        {"showpreset"        , ACTION_VIS_PRESET_SHOW},
        {"nextpreset"        , ACTION_VIS_PRESET_NEXT},
        {"previouspreset"    , ACTION_VIS_PRESET_PREV},
        {"lockpreset"        , ACTION_VIS_PRESET_LOCK},
        {"randompreset"      , ACTION_VIS_PRESET_RANDOM},
        {"increasevisrating" , ACTION_VIS_RATE_PRESET_PLUS},
        {"decreasevisrating" , ACTION_VIS_RATE_PRESET_MINUS},
        {"showvideomenu"     , ACTION_SHOW_VIDEOMENU},
        {"enter"             , ACTION_ENTER},
        {"increaserating"    , ACTION_INCREASE_RATING},
        {"decreaserating"    , ACTION_DECREASE_RATING},
        {"togglefullscreen"  , ACTION_TOGGLE_FULLSCREEN},
        {"nextscene"         , ACTION_NEXT_SCENE},
        {"previousscene"     , ACTION_PREV_SCENE},
        {"nextletter"        , ACTION_NEXT_LETTER},
        {"prevletter"        , ACTION_PREV_LETTER},
        {"jumpsms2"          , ACTION_JUMP_SMS2},
        {"jumpsms3"          , ACTION_JUMP_SMS3},
        {"jumpsms4"          , ACTION_JUMP_SMS4},
        {"jumpsms5"          , ACTION_JUMP_SMS5},
        {"jumpsms6"          , ACTION_JUMP_SMS6},
        {"jumpsms7"          , ACTION_JUMP_SMS7},
        {"jumpsms8"          , ACTION_JUMP_SMS8},
        {"jumpsms9"          , ACTION_JUMP_SMS9},
        {"filter"            , ACTION_FILTER},
        {"filterclear"       , ACTION_FILTER_CLEAR},
        {"filtersms2"        , ACTION_FILTER_SMS2},
        {"filtersms3"        , ACTION_FILTER_SMS3},
        {"filtersms4"        , ACTION_FILTER_SMS4},
        {"filtersms5"        , ACTION_FILTER_SMS5},
        {"filtersms6"        , ACTION_FILTER_SMS6},
        {"filtersms7"        , ACTION_FILTER_SMS7},
        {"filtersms8"        , ACTION_FILTER_SMS8},
        {"filtersms9"        , ACTION_FILTER_SMS9},
        {"firstpage"         , ACTION_FIRST_PAGE},
        {"lastpage"          , ACTION_LAST_PAGE},
        {"guiprofile"        , ACTION_GUIPROFILE_BEGIN},
        {"red"               , ACTION_TELETEXT_RED},
        {"green"             , ACTION_TELETEXT_GREEN},
        {"yellow"            , ACTION_TELETEXT_YELLOW},
        {"blue"              , ACTION_TELETEXT_BLUE},
        {"increasepar"       , ACTION_INCREASE_PAR},
        {"decreasepar"       , ACTION_DECREASE_PAR},
        {"volampup"          , ACTION_VOLAMP_UP},
        {"volampdown"        , ACTION_VOLAMP_DOWN},
        {"volumeamplification", ACTION_VOLAMP},
        {"createbookmark"        , ACTION_CREATE_BOOKMARK},
        {"createepisodebookmark" , ACTION_CREATE_EPISODE_BOOKMARK},
        {"settingsreset"      , ACTION_SETTINGS_RESET},
        {"settingslevelchange", ACTION_SETTINGS_LEVEL_CHANGE},

        // 3D movie playback/GUI
        {"stereomode"                , ACTION_STEREOMODE_SELECT}, // cycle 3D modes, for now an alias for next
        {"nextstereomode"            , ACTION_STEREOMODE_NEXT},
        {"previousstereomode"        , ACTION_STEREOMODE_PREVIOUS},
        {"togglestereomode"          , ACTION_STEREOMODE_TOGGLE},
        {"stereomodetomono"          , ACTION_STEREOMODE_TOMONO},

        // PVR actions
        {"channelup"             , ACTION_CHANNEL_UP},
        {"channeldown"           , ACTION_CHANNEL_DOWN},
        {"previouschannelgroup"  , ACTION_PREVIOUS_CHANNELGROUP},
        {"nextchannelgroup"      , ACTION_NEXT_CHANNELGROUP},
        {"playpvr"               , ACTION_PVR_PLAY},
        {"playpvrtv"             , ACTION_PVR_PLAY_TV},
        {"playpvrradio"          , ACTION_PVR_PLAY_RADIO},
        {"record"                , ACTION_RECORD},

        // Mouse actions
        {"leftclick"         , ACTION_MOUSE_LEFT_CLICK},
        {"rightclick"        , ACTION_MOUSE_RIGHT_CLICK},
        {"middleclick"       , ACTION_MOUSE_MIDDLE_CLICK},
        {"doubleclick"       , ACTION_MOUSE_DOUBLE_CLICK},
        {"longclick"         , ACTION_MOUSE_LONG_CLICK},
        {"wheelup"           , ACTION_MOUSE_WHEEL_UP},
        {"wheeldown"         , ACTION_MOUSE_WHEEL_DOWN},
        {"mousedrag"         , ACTION_MOUSE_DRAG},
        {"mousemove"         , ACTION_MOUSE_MOVE},

        // Touch
        {"tap"               , ACTION_TOUCH_TAP},
        {"longpress"         , ACTION_TOUCH_LONGPRESS},
        {"pangesture"        , ACTION_GESTURE_PAN},
        {"zoomgesture"       , ACTION_GESTURE_ZOOM},
        {"rotategesture"     , ACTION_GESTURE_ROTATE},
        {"swipeleft"         , ACTION_GESTURE_SWIPE_LEFT},
        {"swiperight"        , ACTION_GESTURE_SWIPE_RIGHT},
        {"swipeup"           , ACTION_GESTURE_SWIPE_UP},
        {"swipedown"         , ACTION_GESTURE_SWIPE_DOWN},

        // Do nothing / error action
        { "error"            , ACTION_ERROR},
        { "noop"             , ACTION_NOOP}
};

static const ActionMapping windows[] =
       {{"home"                     , WINDOW_HOME},
        {"programs"                 , WINDOW_PROGRAMS},
        {"pictures"                 , WINDOW_PICTURES},
        {"filemanager"              , WINDOW_FILES},
        {"files"                    , WINDOW_FILES}, // backward compat
        {"settings"                 , WINDOW_SETTINGS_MENU},
        {"music"                    , WINDOW_MUSIC},
        {"video"                    , WINDOW_VIDEOS},
        {"videos"                   , WINDOW_VIDEO_NAV},
        {"pvr"                      , WINDOW_TV_CHANNELS}, // backward compat
        {"tvchannels"               , WINDOW_TV_CHANNELS},
        {"tvrecordings"             , WINDOW_TV_RECORDINGS},
        {"tvguide"                  , WINDOW_TV_GUIDE},
        {"tvtimers"                 , WINDOW_TV_TIMERS},
        {"tvsearch"                 , WINDOW_TV_SEARCH},
        {"radiochannels"            , WINDOW_RADIO_CHANNELS},
        {"radiorecordings"          , WINDOW_RADIO_RECORDINGS},
        {"radioguide"               , WINDOW_RADIO_GUIDE},
        {"radiotimers"              , WINDOW_RADIO_TIMERS},
        {"radiosearch"              , WINDOW_RADIO_SEARCH},
        {"pvrguideinfo"             , WINDOW_DIALOG_PVR_GUIDE_INFO},
        {"pvrrecordinginfo"         , WINDOW_DIALOG_PVR_RECORDING_INFO},
        {"pvrtimersetting"          , WINDOW_DIALOG_PVR_TIMER_SETTING},
        {"pvrgroupmanager"          , WINDOW_DIALOG_PVR_GROUP_MANAGER},
        {"pvrchannelmanager"        , WINDOW_DIALOG_PVR_CHANNEL_MANAGER},
        {"pvrguidesearch"           , WINDOW_DIALOG_PVR_GUIDE_SEARCH},
        {"pvrchannelscan"           , WINDOW_DIALOG_PVR_CHANNEL_SCAN},
        {"pvrupdateprogress"        , WINDOW_DIALOG_PVR_UPDATE_PROGRESS},
        {"pvrosdchannels"           , WINDOW_DIALOG_PVR_OSD_CHANNELS},
        {"pvrosdguide"              , WINDOW_DIALOG_PVR_OSD_GUIDE},
        {"pvrosdteletext"           , WINDOW_DIALOG_OSD_TELETEXT},
        {"systeminfo"               , WINDOW_SYSTEM_INFORMATION},
        {"testpattern"              , WINDOW_TEST_PATTERN},
        {"screencalibration"        , WINDOW_SCREEN_CALIBRATION},
        {"guicalibration"           , WINDOW_SCREEN_CALIBRATION}, // backward compat
        {"picturessettings"         , WINDOW_SETTINGS_MYPICTURES},
        {"programssettings"         , WINDOW_SETTINGS_MYPROGRAMS},
        {"weathersettings"          , WINDOW_SETTINGS_MYWEATHER},
        {"musicsettings"            , WINDOW_SETTINGS_MYMUSIC},
        {"systemsettings"           , WINDOW_SETTINGS_SYSTEM},
        {"videossettings"           , WINDOW_SETTINGS_MYVIDEOS},
        {"networksettings"          , WINDOW_SETTINGS_SERVICE}, // backward compat
        {"servicesettings"          , WINDOW_SETTINGS_SERVICE},
        {"appearancesettings"       , WINDOW_SETTINGS_APPEARANCE},
        {"pvrsettings"              , WINDOW_SETTINGS_MYPVR},
        {"tvsettings"               , WINDOW_SETTINGS_MYPVR},  // backward compat
        {"scripts"                  , WINDOW_PROGRAMS}, // backward compat
        {"videofiles"               , WINDOW_VIDEO_FILES},
        {"videolibrary"             , WINDOW_VIDEO_NAV},
        {"videoplaylist"            , WINDOW_VIDEO_PLAYLIST},
        {"loginscreen"              , WINDOW_LOGIN_SCREEN},
        {"profiles"                 , WINDOW_SETTINGS_PROFILES},
        {"skinsettings"             , WINDOW_SKIN_SETTINGS},
        {"addonbrowser"             , WINDOW_ADDON_BROWSER},
        {"yesnodialog"              , WINDOW_DIALOG_YES_NO},
        {"progressdialog"           , WINDOW_DIALOG_PROGRESS},
        {"virtualkeyboard"          , WINDOW_DIALOG_KEYBOARD},
        {"volumebar"                , WINDOW_DIALOG_VOLUME_BAR},
        {"submenu"                  , WINDOW_DIALOG_SUB_MENU},
        {"favourites"               , WINDOW_DIALOG_FAVOURITES},
        {"contextmenu"              , WINDOW_DIALOG_CONTEXT_MENU},
        {"infodialog"               , WINDOW_DIALOG_KAI_TOAST},
        {"numericinput"             , WINDOW_DIALOG_NUMERIC},
        {"gamepadinput"             , WINDOW_DIALOG_GAMEPAD},
        {"shutdownmenu"             , WINDOW_DIALOG_BUTTON_MENU},
        {"mutebug"                  , WINDOW_DIALOG_MUTE_BUG},
        {"playercontrols"           , WINDOW_DIALOG_PLAYER_CONTROLS},
        {"seekbar"                  , WINDOW_DIALOG_SEEK_BAR},
        {"musicosd"                 , WINDOW_DIALOG_MUSIC_OSD},
        {"addonsettings"            , WINDOW_DIALOG_ADDON_SETTINGS},
        {"visualisationsettings"    , WINDOW_DIALOG_ADDON_SETTINGS}, // backward compat
        {"visualisationpresetlist"  , WINDOW_DIALOG_VIS_PRESET_LIST},
        {"osdvideosettings"         , WINDOW_DIALOG_VIDEO_OSD_SETTINGS},
        {"osdaudiosettings"         , WINDOW_DIALOG_AUDIO_OSD_SETTINGS},
        {"videobookmarks"           , WINDOW_DIALOG_VIDEO_BOOKMARKS},
        {"filebrowser"              , WINDOW_DIALOG_FILE_BROWSER},
        {"networksetup"             , WINDOW_DIALOG_NETWORK_SETUP},
        {"mediasource"              , WINDOW_DIALOG_MEDIA_SOURCE},
        {"profilesettings"          , WINDOW_DIALOG_PROFILE_SETTINGS},
        {"locksettings"             , WINDOW_DIALOG_LOCK_SETTINGS},
        {"contentsettings"          , WINDOW_DIALOG_CONTENT_SETTINGS},
        {"songinformation"          , WINDOW_DIALOG_SONG_INFO},
        {"smartplaylisteditor"      , WINDOW_DIALOG_SMART_PLAYLIST_EDITOR},
        {"smartplaylistrule"        , WINDOW_DIALOG_SMART_PLAYLIST_RULE},
        {"busydialog"               , WINDOW_DIALOG_BUSY},
        {"pictureinfo"              , WINDOW_DIALOG_PICTURE_INFO},
        {"accesspoints"             , WINDOW_DIALOG_ACCESS_POINTS},
        {"fullscreeninfo"           , WINDOW_DIALOG_FULLSCREEN_INFO},
        {"karaokeselector"          , WINDOW_DIALOG_KARAOKE_SONGSELECT},
        {"karaokelargeselector"     , WINDOW_DIALOG_KARAOKE_SELECTOR},
        {"sliderdialog"             , WINDOW_DIALOG_SLIDER},
        {"addoninformation"         , WINDOW_DIALOG_ADDON_INFO},
        {"subtitlesearch"           , WINDOW_DIALOG_SUBTITLES},
        {"musicplaylist"            , WINDOW_MUSIC_PLAYLIST},
        {"musicfiles"               , WINDOW_MUSIC_FILES},
        {"musiclibrary"             , WINDOW_MUSIC_NAV},
        {"musicplaylisteditor"      , WINDOW_MUSIC_PLAYLIST_EDITOR},
        {"teletext"                 , WINDOW_DIALOG_OSD_TELETEXT},
        {"selectdialog"             , WINDOW_DIALOG_SELECT},
        {"musicinformation"         , WINDOW_DIALOG_MUSIC_INFO},
        {"okdialog"                 , WINDOW_DIALOG_OK},
        {"movieinformation"         , WINDOW_DIALOG_VIDEO_INFO},
        {"textviewer"               , WINDOW_DIALOG_TEXT_VIEWER},
        {"fullscreenvideo"          , WINDOW_FULLSCREEN_VIDEO},
        {"fullscreenlivetv"         , WINDOW_FULLSCREEN_LIVETV}, // virtual window/keymap section for PVR specific bindings in fullscreen playback (which internally uses WINDOW_FULLSCREEN_VIDEO)
        {"fullscreenradio"          , WINDOW_FULLSCREEN_RADIO}, // virtual window for fullscreen radio, uses WINDOW_VISUALISATION as fallback
        {"visualisation"            , WINDOW_VISUALISATION},
        {"slideshow"                , WINDOW_SLIDESHOW},
        {"filestackingdialog"       , WINDOW_DIALOG_FILESTACKING},
        {"karaoke"                  , WINDOW_KARAOKELYRICS},
        {"weather"                  , WINDOW_WEATHER},
        {"screensaver"              , WINDOW_SCREENSAVER},
        {"videoosd"                 , WINDOW_DIALOG_VIDEO_OSD},
        {"videomenu"                , WINDOW_VIDEO_MENU},
        {"videotimeseek"            , WINDOW_VIDEO_TIME_SEEK},
        {"musicoverlay"             , WINDOW_DIALOG_MUSIC_OVERLAY},
        {"videooverlay"             , WINDOW_DIALOG_VIDEO_OVERLAY},
        {"startwindow"              , WINDOW_START},
        {"startup"                  , WINDOW_STARTUP_ANIM},
        {"peripherals"              , WINDOW_DIALOG_PERIPHERAL_MANAGER},
        {"peripheralsettings"       , WINDOW_DIALOG_PERIPHERAL_SETTINGS},
        {"extendedprogressdialog"   , WINDOW_DIALOG_EXT_PROGRESS},
        {"mediafilter"              , WINDOW_DIALOG_MEDIA_FILTER},
        {"addon"                    , WINDOW_ADDON_START}};

static const ActionMapping mousekeys[] =
{
  { "click",       KEY_MOUSE_CLICK },
  { "leftclick",   KEY_MOUSE_CLICK },
  { "rightclick",  KEY_MOUSE_RIGHTCLICK },
  { "middleclick", KEY_MOUSE_MIDDLECLICK },
  { "doubleclick", KEY_MOUSE_DOUBLE_CLICK },
  { "longclick",   KEY_MOUSE_LONG_CLICK },
  { "wheelup",     KEY_MOUSE_WHEEL_UP },
  { "wheeldown",   KEY_MOUSE_WHEEL_DOWN },
  { "mousemove",   KEY_MOUSE_MOVE },
  { "mousedrag",   KEY_MOUSE_DRAG },
  { "mousedragstart", KEY_MOUSE_DRAG_START },
  { "mousedragend",   KEY_MOUSE_DRAG_END },
  { "mouserdrag", KEY_MOUSE_RDRAG },
  { "mouserdragstart", KEY_MOUSE_RDRAG_START },
  { "mouserdragend", KEY_MOUSE_RDRAG_END }
};

static const ActionMapping touchcommands[] =
{
  { "tap",                ACTION_TOUCH_TAP },
  { "longpress",          ACTION_TOUCH_LONGPRESS },
  { "pan",                ACTION_GESTURE_PAN },
  { "zoom",               ACTION_GESTURE_ZOOM },
  { "rotate",             ACTION_GESTURE_ROTATE },
  { "swipeleft",          ACTION_GESTURE_SWIPE_LEFT },
  { "swiperight",         ACTION_GESTURE_SWIPE_RIGHT },
  { "swipeup",            ACTION_GESTURE_SWIPE_UP },
  { "swipedown",          ACTION_GESTURE_SWIPE_DOWN }
};

static const WindowMapping fallbackWindows[] =
{
  { WINDOW_FULLSCREEN_LIVETV,          WINDOW_FULLSCREEN_VIDEO },
  { WINDOW_FULLSCREEN_RADIO,           WINDOW_VISUALISATION    }
};

#ifdef TARGET_WINDOWS
static const ActionMapping appcommands[] =
{
  { "browser_back",        APPCOMMAND_BROWSER_BACKWARD },
  { "browser_forward",     APPCOMMAND_BROWSER_FORWARD },
  { "browser_refresh",     APPCOMMAND_BROWSER_REFRESH },
  { "browser_stop",        APPCOMMAND_BROWSER_STOP },
  { "browser_search",      APPCOMMAND_BROWSER_SEARCH },
  { "browser_favorites",   APPCOMMAND_BROWSER_FAVORITES },
  { "browser_home",        APPCOMMAND_BROWSER_HOME },
  { "volume_mute",         APPCOMMAND_VOLUME_MUTE },
  { "volume_down",         APPCOMMAND_VOLUME_DOWN },
  { "volume_up",           APPCOMMAND_VOLUME_UP },
  { "next_track",          APPCOMMAND_MEDIA_NEXTTRACK },
  { "prev_track",          APPCOMMAND_MEDIA_PREVIOUSTRACK },
  { "stop",                APPCOMMAND_MEDIA_STOP },
  { "play_pause",          APPCOMMAND_MEDIA_PLAY_PAUSE },
  { "launch_mail",         APPCOMMAND_LAUNCH_MAIL },
  { "launch_media_select", APPCOMMAND_LAUNCH_MEDIA_SELECT },
  { "launch_app1",         APPCOMMAND_LAUNCH_APP1 },
  { "launch_app2",         APPCOMMAND_LAUNCH_APP2 },
  { "play",                APPCOMMAND_MEDIA_PLAY },
  { "pause",               APPCOMMAND_MEDIA_PAUSE },
  { "fastforward",         APPCOMMAND_MEDIA_FAST_FORWARD },
  { "rewind",              APPCOMMAND_MEDIA_REWIND },
  { "channelup",           APPCOMMAND_MEDIA_CHANNEL_UP },
  { "channeldown",         APPCOMMAND_MEDIA_CHANNEL_DOWN }
};
#endif

CButtonTranslator& CButtonTranslator::GetInstance()
{
  static CButtonTranslator sl_instance;
  return sl_instance;
}

CButtonTranslator::CButtonTranslator()
{
  m_deviceList.clear();
  m_Loaded = false;
}

#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE)
void CButtonTranslator::ClearLircButtonMapEntries()
{
  vector<lircButtonMap*> maps;
  for (map<std::string,lircButtonMap*>::iterator it  = lircRemotesMap.begin();
                                                 it != lircRemotesMap.end();++it)
    maps.push_back(it->second);
  sort(maps.begin(),maps.end());
  vector<lircButtonMap*>::iterator itend = unique(maps.begin(),maps.end());
  for (vector<lircButtonMap*>::iterator it = maps.begin(); it != itend;++it)
    delete *it;
}
#endif

CButtonTranslator::~CButtonTranslator()
{
#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE)
  ClearLircButtonMapEntries();
#endif
}

// Add the supplied device name to the list of connected devices
void CButtonTranslator::AddDevice(std::string& strDevice)
{
  // Only add the device if it isn't already in the list
  std::list<std::string>::iterator it;
  for (it = m_deviceList.begin(); it != m_deviceList.end(); ++it)
    if (*it == strDevice)
      return;

  // Add the device
  m_deviceList.push_back(strDevice);
  m_deviceList.sort();

  // New device added so reload the key mappings
  Load();
}

void CButtonTranslator::RemoveDevice(std::string& strDevice)
{
  // Find the device
  std::list<std::string>::iterator it;
  for (it = m_deviceList.begin(); it != m_deviceList.end(); ++it)
    if (*it == strDevice)
      break;
  if (it == m_deviceList.end())
    return;

  // Remove the device
  m_deviceList.remove(strDevice);

  // Device removed so reload the key mappings
  Load();
}

bool CButtonTranslator::Load(bool AlwaysLoad)
{
  m_translatorMap.clear();

  // Directories to search for keymaps. They're applied in this order,
  // so keymaps in profile/keymaps/ override e.g. system/keymaps
  static const char* DIRS_TO_CHECK[] = {
    "special://xbmc/system/keymaps/",
    "special://masterprofile/keymaps/",
    "special://profile/keymaps/"
  };
  bool success = false;

  for (unsigned int dirIndex = 0; dirIndex < ARRAY_SIZE(DIRS_TO_CHECK); ++dirIndex)
  {
    if (XFILE::CDirectory::Exists(DIRS_TO_CHECK[dirIndex]))
    {
      CFileItemList files;
      XFILE::CDirectory::GetDirectory(DIRS_TO_CHECK[dirIndex], files, ".xml");
      // Sort the list for filesystem based priorities, e.g. 01-keymap.xml, 02-keymap-overrides.xml
      files.Sort(SortByFile, SortOrderAscending);
      for(int fileIndex = 0; fileIndex<files.Size(); ++fileIndex)
      {
        if (!files[fileIndex]->m_bIsFolder)
          success |= LoadKeymap(files[fileIndex]->GetPath());
      }

      // Load mappings for any HID devices we have connected
      std::list<std::string>::iterator it;
      for (it = m_deviceList.begin(); it != m_deviceList.end(); ++it)
      {
        std::string devicedir = DIRS_TO_CHECK[dirIndex];
        devicedir.append(*it);
        devicedir.append("/");
        if( XFILE::CDirectory::Exists(devicedir) )
        {
          CFileItemList files;
          XFILE::CDirectory::GetDirectory(devicedir, files, ".xml");
          // Sort the list for filesystem based priorities, e.g. 01-keymap.xml, 02-keymap-overrides.xml
          files.Sort(SortByFile, SortOrderAscending);
          for(int fileIndex = 0; fileIndex<files.Size(); ++fileIndex)
          {
            if (!files[fileIndex]->m_bIsFolder)
              success |= LoadKeymap(files[fileIndex]->GetPath());
          }
        }
      }
    }
  }

  if (!success)
  {
    CLog::Log(LOGERROR, "Error loading keymaps from: %s or %s or %s", DIRS_TO_CHECK[0], DIRS_TO_CHECK[1], DIRS_TO_CHECK[2]);
    return false;
  }

#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE)
#ifdef TARGET_POSIX
#define REMOTEMAP "Lircmap.xml"
#else
#define REMOTEMAP "IRSSmap.xml"
#endif
  std::string lircmapPath = URIUtils::AddFileToFolder("special://xbmc/system/", REMOTEMAP);
  lircRemotesMap.clear();
  if(CFile::Exists(lircmapPath))
    success |= LoadLircMap(lircmapPath);
  else
    CLog::Log(LOGDEBUG, "CButtonTranslator::Load - no system %s found, skipping", REMOTEMAP);

  lircmapPath = CProfilesManager::Get().GetUserDataItem(REMOTEMAP);
  if(CFile::Exists(lircmapPath))
    success |= LoadLircMap(lircmapPath);
  else
    CLog::Log(LOGDEBUG, "CButtonTranslator::Load - no userdata %s found, skipping", REMOTEMAP);

  if (!success)
    CLog::Log(LOGERROR, "CButtonTranslator::Load - unable to load remote map %s", REMOTEMAP);
  // don't return false - it is to only indicate a fatal error (which this is not)
#endif

  // Done!
  m_Loaded = true;
  return true;
}

bool CButtonTranslator::LoadKeymap(const std::string &keymapPath)
{
  CXBMCTinyXML xmlDoc;

  CLog::Log(LOGINFO, "Loading %s", keymapPath.c_str());
  if (!xmlDoc.LoadFile(keymapPath))
  {
    CLog::Log(LOGERROR, "Error loading keymap: %s, Line %d\n%s", keymapPath.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
    return false;
  }
  TiXmlElement* pRoot = xmlDoc.RootElement();
  if (!pRoot)
  {
    CLog::Log(LOGERROR, "Error getting keymap root: %s", keymapPath.c_str());
    return false;
  }
  std::string strValue = pRoot->Value();
  if ( strValue != "keymap")
  {
    CLog::Log(LOGERROR, "%s Doesn't contain <keymap>", keymapPath.c_str());
    return false;
  }
  // run through our window groups
  TiXmlNode* pWindow = pRoot->FirstChild();
  while (pWindow)
  {
    if (pWindow->Type() == TiXmlNode::TINYXML_ELEMENT)
    {
      int windowID = WINDOW_INVALID;
      const char *szWindow = pWindow->Value();
      if (szWindow)
      {
        if (strcmpi(szWindow, "joystickFamily") == 0)
          MapJoystickFamily(pWindow);
        else if (strcmpi(szWindow, "global") == 0)
          windowID = -1;
        else
          windowID = TranslateWindow(szWindow);
      }
      MapWindowActions(pWindow, windowID);
    }
    pWindow = pWindow->NextSibling();
  }

  return true;
}

bool CButtonTranslator::LoadLircMap(const std::string &lircmapPath)
{
#ifdef TARGET_POSIX
#define REMOTEMAPTAG "lircmap"
#else
#define REMOTEMAPTAG "irssmap"
#endif
  // load our xml file, and fill up our mapping tables
  CXBMCTinyXML xmlDoc;

  // Load the config file
  CLog::Log(LOGINFO, "Loading %s", lircmapPath.c_str());
  if (!xmlDoc.LoadFile(lircmapPath))
  {
    CLog::Log(LOGERROR, "%s, Line %d\n%s", lircmapPath.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
    return false; // This is so people who don't have the file won't fail, just warn
  }

  TiXmlElement* pRoot = xmlDoc.RootElement();
  std::string strValue = pRoot->Value();
  if (strValue != REMOTEMAPTAG)
  {
    CLog::Log(LOGERROR, "%sl Doesn't contain <%s>", lircmapPath.c_str(), REMOTEMAPTAG);
    return false;
  }

  // run through our window groups
  TiXmlNode* pRemote = pRoot->FirstChild();
  while (pRemote)
  {
    if (pRemote->Type() == TiXmlNode::TINYXML_ELEMENT)
    {
      const char *szRemote = pRemote->Value();
      if (szRemote)
      {
        TiXmlAttribute* pAttr = pRemote->ToElement()->FirstAttribute();
        if (pAttr)
          MapRemote(pRemote, pAttr->Value());
      }
    }
    pRemote = pRemote->NextSibling();
  }

  return true;
}

void CButtonTranslator::MapRemote(TiXmlNode *pRemote, const char* szDevice)
{
  CLog::Log(LOGINFO, "* Adding remote mapping for device '%s'", szDevice);
  vector<string> RemoteNames;
  map<std::string, lircButtonMap*>::iterator it = lircRemotesMap.find(szDevice);
  if (it == lircRemotesMap.end())
    lircRemotesMap[szDevice] = new lircButtonMap;
  lircButtonMap& buttons = *lircRemotesMap[szDevice];

  TiXmlElement *pButton = pRemote->FirstChildElement();
  while (pButton)
  {
    if (!pButton->NoChildren())
    {
      if (pButton->ValueStr() == "altname")
        RemoteNames.push_back(pButton->FirstChild()->ValueStr());
      else
        buttons[pButton->FirstChild()->ValueStr()] = pButton->ValueStr();
    }
    pButton = pButton->NextSiblingElement();
  }
  for (vector<string>::iterator it  = RemoteNames.begin();
                                it != RemoteNames.end();++it)
  {
    CLog::Log(LOGINFO, "* Linking remote mapping for '%s' to '%s'", szDevice, it->c_str());
    lircRemotesMap[*it] = &buttons;
  }
}

int CButtonTranslator::TranslateLircRemoteString(const char* szDevice, const char *szButton)
{
  // Find the device
  map<std::string, lircButtonMap*>::iterator it = lircRemotesMap.find(szDevice);
  if (it == lircRemotesMap.end())
    return 0;

  // Find the button
  lircButtonMap::iterator it2 = (*it).second->find(szButton);
  if (it2 == (*it).second->end())
    return 0;

  // Convert the button to code
  if (strnicmp((*it2).second.c_str(), "obc", 3) == 0)
    return TranslateUniversalRemoteString((*it2).second.c_str());

  return TranslateRemoteString((*it2).second.c_str());
}

#if defined(HAS_SDL_JOYSTICK) || defined(HAS_EVENT_SERVER)
void CButtonTranslator::MapJoystickFamily(TiXmlNode *pNode)
{
  TiXmlElement *pFamily = pNode->ToElement();
  if (pFamily && pFamily->Attribute("name"))
  {
    std::string joyFamilyName = pFamily->Attribute("name");
    JoystickFamily* joyFamily = &m_joystickFamilies[joyFamilyName];

    TiXmlElement *pMember = pFamily->FirstChildElement();
    while (pMember)
    {
      TiXmlNode* pName = pMember->FirstChild();
      if (pName && pName->ValueStr() != "") {
        std::shared_ptr<CRegExp> re(new CRegExp(true, CRegExp::asciiOnly));
        std::string joyRe = JoynameToRegex(pName->ValueStr());
        if (!re->RegComp(joyRe, CRegExp::StudyRegExp))
        {
          CLog::Log(LOGNOTICE, "Invalid joystick regex specified: '%s'", pName->Value());
          continue;
        }
        AddFamilyRegex(joyFamily, re);
      }
      pMember = pMember->NextSiblingElement();
    }
  }
  else
  {
    CLog::Log(LOGNOTICE, "Ignoring nameless joystick family");
  }
}

void CButtonTranslator::MapJoystickActions(int windowID, TiXmlNode *pJoystick)
{
  std::string joyFamilyName;
  map<int, string> buttonMap;
  map<int, string> axisMap;
  AxesConfig axesConfig;
  ActionMap hatMap;

  TiXmlElement *pJoy = pJoystick->ToElement();
  if (pJoy && pJoy->Attribute("family"))
    joyFamilyName = pJoy->Attribute("family");
  else if (pJoy) {
    // transform loose name to new family, including altnames
    string joyName = JOYSTICK_DEFAULT_MAP; // default global map name
    if (pJoy->Attribute("name"))
      joyName = pJoy->Attribute("name");
    joyFamilyName = joyName;    
    JoystickFamily* joyFamily = &m_joystickFamilies[joyFamilyName];

    std::shared_ptr<CRegExp> re(new CRegExp(true, CRegExp::asciiOnly));
    std::string joyRe = JoynameToRegex(joyName);
    if (!re->RegComp(joyRe, CRegExp::StudyRegExp))
    {
      CLog::Log(LOGNOTICE, "Invalid joystick regex specified: '%s'", joyName.c_str());
      return;
    }
    AddFamilyRegex(joyFamily, re);

    // add altnames to family
    TiXmlElement *pNode = pJoystick->FirstChildElement();
    while (pNode) {
      const std::string &type = pNode->ValueStr();
      if (type == "altname") {
        std::string altName = pNode->FirstChild()->ValueStr();
        std::shared_ptr<CRegExp> altRe(new CRegExp(true, CRegExp::asciiOnly));
        std::string altReStr = JoynameToRegex(altName);
        if (!altRe->RegComp(altReStr, CRegExp::StudyRegExp))
          CLog::Log(LOGNOTICE, "Ignoring invalid joystick altname regex: '%s'", altReStr.c_str());
        else
          AddFamilyRegex(joyFamily, altRe);
      }
      pNode = pNode->NextSiblingElement();
    }

  }

  // parse map
  TiXmlElement *pButton = pJoystick->FirstChildElement();
  int id = 0;
  while (pButton)
  {
    const std::string &type = pButton->ValueStr();
    std::string action;
    if (!pButton->NoChildren())
      action = pButton->FirstChild()->ValueStr();

    // skip altname tags here because those contain no mappings ...
    if (type == "altname")
    {
      pButton = pButton->NextSiblingElement();
      continue;
    }

    if ((pButton->QueryIntAttribute("id", &id) == TIXML_SUCCESS) && id>=0 && id<=256)
    {
      if (type == "button")
      {
        buttonMap[id] = action;
      }
      else if (type == "axis")
      {
        int limit = 0;
        if (pButton->QueryIntAttribute("limit", &limit) == TIXML_SUCCESS)
        {
          if (limit==-1)
            axisMap[-id] = action;
          else if (limit==1)
            axisMap[id] = action;
          else if (limit==0)
            axisMap[id|0xFFFF0000] = action;
          else
          {
            axisMap[id] = action;
            axisMap[-id] = action;
            CLog::Log(LOGERROR, "Error in joystick map, invalid limit specified %d for axis %d", limit, id);
          }
        }
        else
        {
          axisMap[id] = action;
          axisMap[-id] = action;
        }

        if (windowID == -1) {
          // in <global> we can override the rest state value of axes and whether they are triggers
          bool trigger = false;
          int restStateValue = 0;
          pButton->QueryBoolAttribute("trigger", &trigger);
          pButton->QueryIntAttribute("rest", &restStateValue);
          // if it deviates from the defaults
          if (trigger || restStateValue != 0)
            axesConfig.push_back(AxisConfig(id, trigger, restStateValue));
        }
      }
      else if (type == "hat")
      {
        string position;
        if (pButton->QueryValueAttribute("position", &position) == TIXML_SUCCESS)
        {
          uint32_t hatID = id|0xFFF00000;
          if (position.compare("up") == 0)
            hatMap[(JACTIVE_HAT_UP<<16)|hatID] = action;
          else if (position.compare("down") == 0)
            hatMap[(JACTIVE_HAT_DOWN<<16)|hatID] = action;
          else if (position.compare("right") == 0)
            hatMap[(JACTIVE_HAT_RIGHT<<16)|hatID] = action;
          else if (position.compare("left") == 0)
            hatMap[(JACTIVE_HAT_LEFT<<16)|hatID] = action;
          else
            CLog::Log(LOGERROR, "Error in joystick map, invalid position specified %s for axis %d", position.c_str(), id);
        }
      }
      else
        CLog::Log(LOGERROR, "Error reading joystick map element, unknown button type: %s", type.c_str());
    }
    else
      CLog::Log(LOGERROR, "Error reading joystick map element, Invalid id: %d", id);

    pButton = pButton->NextSiblingElement();
  }

  // add/overwrite keys with mapped actions
  for (auto button : buttonMap)
    m_joystickButtonMap[joyFamilyName][windowID][button.first] = button.second;

  for (auto axis : axisMap)
    m_joystickAxisMap[joyFamilyName][windowID][axis.first] = axis.second;

  for (auto hat : hatMap)
    m_joystickHatMap[joyFamilyName][windowID][hat.first] = hat.second;

  if (windowID == -1) 
    m_joystickAxesConfigs[joyFamilyName] = axesConfig;
}

std::string CButtonTranslator::JoynameToRegex(const std::string& joyName) const
{
  if (joyName.empty()) 
    return joyName;

  // names already presented as regex are identified by a leading /
  else if (joyName[0] == '/') 
    return joyName.substr(1);

  // the others we'll have to escape
  return "\\Q" + joyName + "\\E";
}

bool CButtonTranslator::AddFamilyRegex(JoystickFamily* family, std::shared_ptr<CRegExp> regex)
{
  // even though family is a set, this does not prevent the same regex 
  // from being added twice, so we manually match on pattern equality
  JoystickFamily::iterator it;
  for (it = family->begin(); it != family->end(); it++)
  {
    if ((*it)->GetPattern() == regex->GetPattern())
      return false;
  }
  family->insert(regex);
  return true;
}

const AxesConfig* CButtonTranslator::GetAxesConfigFor(const std::string& joyName) const
{
  JoystickFamilyMap::const_iterator familyIt = FindJoystickFamily(joyName);
  if (familyIt == m_joystickFamilies.end())
    return NULL;
  else {
    std::map<std::string, AxesConfig>::const_iterator it = m_joystickAxesConfigs.find(familyIt->first);
    if (it == m_joystickAxesConfigs.end())
      return NULL;
    else
      return &it->second;
  }
}

CButtonTranslator::JoystickMap::const_iterator CButtonTranslator::FindWindowMap(const std::string& joyName, const JoystickMap &maps) const
{
  JoystickFamilyMap::const_iterator familyIt = FindJoystickFamily(joyName);
  if (familyIt == m_joystickFamilies.end())
    return maps.end();
  else
    return maps.find(familyIt->first);
}

CButtonTranslator::JoystickFamilyMap::const_iterator CButtonTranslator::FindJoystickFamily(const std::string& joyName) const
{
  // find the family corresponding to a joystick name
  JoystickFamilyMap::const_iterator it;
  for (it = m_joystickFamilies.begin(); it != m_joystickFamilies.end(); it++)
  {
    JoystickFamily::const_iterator regexIt;
    for (regexIt = it->second.begin(); regexIt != it->second.end(); regexIt++)
    {
      if ((*regexIt)->RegFind(joyName) >= 0)
      {
        // CLog::Log(LOGDEBUG, "Regex %s matches joystick %s", it->first->GetPattern().c_str(), joyName.c_str());
        return it;
      }
    }
  }
  return it;
}

bool CButtonTranslator::TranslateJoystickString(int window, const std::string& joyName, int id, short inputType, int& action, std::string& strAction, bool &fullrange)
{
  fullrange = false;

  // resolve the correct JoystickMap
  JoystickMap *jmap;
  if (inputType == JACTIVE_AXIS)
    jmap = &m_joystickAxisMap;
  else if (inputType == JACTIVE_BUTTON)
    jmap = &m_joystickButtonMap;
  else if (inputType == JACTIVE_HAT)
    jmap = &m_joystickHatMap;
  else
  {
    CLog::Log(LOGERROR, "Error reading joystick input type '%i'", (int) inputType);
    return false;
  }

  JoystickMap::const_iterator it = FindWindowMap(joyName, *jmap);
  if (it==jmap->end())
  {
    it = FindWindowMap(JOYSTICK_DEFAULT_MAP, *jmap); // default global map name
    if (it==jmap->end())
      return false;
  }

  const WindowMap *wmap = &it->second;

  // try to get the action from the current window
  action = GetActionCode(window, id, *wmap, strAction, fullrange);

  // if it's invalid, try to get it from a fallback window or the global map
  if (action == 0)
  {
    int fallbackWindow = GetFallbackWindow(window);
    if (fallbackWindow > -1)
      action = GetActionCode(fallbackWindow, id, *wmap, strAction, fullrange);
    // still no valid action? use global map
    if (action == 0)
      action = GetActionCode(-1, id, *wmap, strAction, fullrange);
  }

  return (action > 0);
}

bool CButtonTranslator::TranslateTouchAction(int window, int touchAction, int touchPointers, int &action, std::string &actionString)
{
  action = 0;
  if (touchPointers <= 0)
    touchPointers = 1;

  touchAction += touchPointers - 1;
  touchAction |= KEY_TOUCH;

  action = GetTouchActionCode(window, touchAction, actionString);
  if (action <= 0)
  {
    int fallbackWindow = GetFallbackWindow(window);
    if (fallbackWindow > -1)
      action = GetTouchActionCode(fallbackWindow, touchAction, actionString);
    if (action <= 0)
      action = GetTouchActionCode(-1, touchAction, actionString);
  }

  return action > 0;
}

int CButtonTranslator::GetActionCode(int window, int action)
{
  map<int, buttonMap>::const_iterator it = m_translatorMap.find(window);
  if (it == m_translatorMap.end())
    return 0;

  buttonMap::const_iterator it2 = it->second.find(action);
  if (it2 == it->second.end())
    return 0;

  return it2->second.id;
}

/*
 * Translates a joystick input to an action code
 */
int CButtonTranslator::GetActionCode(int window, int id, const WindowMap &wmap, std::string &strAction, bool &fullrange) const
{
  int action = 0;
  bool found = false;

  WindowMap::const_iterator it = wmap.find(window);
  if (it != wmap.end())
  {
    const map<int, string> &windowbmap = it->second;
    map<int, string>::const_iterator it2 = windowbmap.find(id);
    if (it2 != windowbmap.end())
    {
      strAction = (it2->second).c_str();
      found = true;
    }

    it2 = windowbmap.find(abs(id)|0xFFFF0000);
    if (it2 != windowbmap.end())
    {
      strAction = (it2->second).c_str();
      found = true;
      fullrange = true;
    }

    // Hats joystick
    it2 = windowbmap.find(id|0xFFF00000);
    if (it2 != windowbmap.end())
    {
      strAction = (it2->second).c_str();
      found = true;
    }
  }

  if (found)
    TranslateActionString(strAction.c_str(), action);
  return action;
}
#endif

void CButtonTranslator::GetActions(std::vector<std::string> &actionList)
{
  unsigned int size = sizeof(actions) / sizeof(ActionMapping);
  actionList.clear();
  actionList.reserve(size);
  for (unsigned int index = 0; index < size; index++)
    actionList.push_back(actions[index].name);
}

void CButtonTranslator::GetWindows(std::vector<std::string> &windowList)
{
  unsigned int size = sizeof(windows) / sizeof(ActionMapping);
  windowList.clear();
  windowList.reserve(size);
  for (unsigned int index = 0; index < size; index++)
    windowList.push_back(windows[index].name);
}

int CButtonTranslator::GetFallbackWindow(int windowID)
{
  for (unsigned int index = 0; index < ARRAY_SIZE(fallbackWindows); ++index)
  {
    if (fallbackWindows[index].origin == windowID)
      return fallbackWindows[index].target;
  }
  // for addon windows use WINDOW_ADDON_START
  // because id is dynamic
  if (windowID >= WINDOW_ADDON_START && windowID <= WINDOW_ADDON_END)
    return WINDOW_ADDON_START;

  return -1;
}

CAction CButtonTranslator::GetAction(int window, const CKey &key, bool fallback)
{
  std::string strAction;
  // try to get the action from the current window
  int actionID = GetActionCode(window, key, strAction);
  // if it's invalid, try to get it from the global map
  if (actionID == 0 && fallback)
  {
    int fallbackWindow = GetFallbackWindow(window);
    if (fallbackWindow > -1)
      actionID = GetActionCode(fallbackWindow, key, strAction);
    // still no valid action? use global map
    if (actionID == 0)
      actionID = GetActionCode( -1, key, strAction);
  }
  // Now fill our action structure
  CAction action(actionID, strAction, key);
  return action;
}

int CButtonTranslator::GetActionCode(int window, const CKey &key, std::string &strAction) const
{
  uint32_t code = key.GetButtonCode();

  map<int, buttonMap>::const_iterator it = m_translatorMap.find(window);
  if (it == m_translatorMap.end())
    return 0;
  buttonMap::const_iterator it2 = (*it).second.find(code);
  int action = 0;
  if (it2 != (*it).second.end())
  {
    action = (*it2).second.id;
    strAction = (*it2).second.strID;
  }
#ifdef TARGET_POSIX
  // Some buttoncodes changed in Hardy
  if (action == 0 && (code & KEY_VKEY) == KEY_VKEY && (code & 0x0F00))
  {
    CLog::Log(LOGDEBUG, "%s: Trying Hardy keycode for %#04x", __FUNCTION__, code);
    code &= ~0x0F00;
    it2 = (*it).second.find(code);
    if (it2 != (*it).second.end())
    {
      action = (*it2).second.id;
      strAction = (*it2).second.strID;
    }
  }
#endif
  return action;
}

void CButtonTranslator::MapAction(uint32_t buttonCode, const char *szAction, buttonMap &map)
{
  int action = ACTION_NONE;
  if (!TranslateActionString(szAction, action) || !buttonCode)
    return;   // no valid action, or an invalid buttoncode

  // have a valid action, and a valid button - map it.
  // check to see if we've already got this (button,action) pair defined
  buttonMap::iterator it = map.find(buttonCode);
  if (it == map.end() || (*it).second.id != action || (*it).second.strID != szAction)
  {
    // NOTE: This multimap is only being used as a normal map at this point (no support
    //       for multiple actions per key)
    if (it != map.end())
      map.erase(it);
    CButtonAction button;
    button.id = action;
    button.strID = szAction;
    map.insert(pair<uint32_t, CButtonAction>(buttonCode, button));
  }
}

bool CButtonTranslator::HasDeviceType(TiXmlNode *pWindow, std::string type)
{
  return pWindow->FirstChild(type) != NULL;
}

void CButtonTranslator::MapWindowActions(TiXmlNode *pWindow, int windowID)
{
  if (!pWindow || windowID == WINDOW_INVALID) 
    return;

  TiXmlNode* pDevice;

  const char* types[] = {"gamepad", "remote", "universalremote", "keyboard", "mouse", "appcommand", NULL};
  for (int i = 0; types[i]; ++i)
  {
    std::string type(types[i]);
    if (HasDeviceType(pWindow, type))
    {
      buttonMap map;
      std::map<int, buttonMap>::iterator it = m_translatorMap.find(windowID);
      if (it != m_translatorMap.end())
      {
        map = it->second;
        m_translatorMap.erase(it);
      }

      pDevice = pWindow->FirstChild(type);

      TiXmlElement *pButton = pDevice->FirstChildElement();

      while (pButton)
      {
        uint32_t buttonCode=0;
        if (type == "gamepad")
            buttonCode = TranslateGamepadString(pButton->Value());
        else if (type == "remote")
            buttonCode = TranslateRemoteString(pButton->Value());
        else if (type == "universalremote")
            buttonCode = TranslateUniversalRemoteString(pButton->Value());
        else if (type == "keyboard")
            buttonCode = TranslateKeyboardButton(pButton);
        else if (type == "mouse")
            buttonCode = TranslateMouseCommand(pButton);
        else if (type == "appcommand")
            buttonCode = TranslateAppCommand(pButton->Value());

        if (buttonCode && pButton->FirstChild())
          MapAction(buttonCode, pButton->FirstChild()->Value(), map);
        pButton = pButton->NextSiblingElement();
      }

      // add our map to our table
      if (!map.empty())
        m_translatorMap.insert(pair<int, buttonMap>( windowID, map));
    }
  }

#if defined(HAS_SDL_JOYSTICK) || defined(HAS_EVENT_SERVER)
  if ((pDevice = pWindow->FirstChild("joystick")) != NULL)
  {
    // map joystick actions
    while (pDevice)
    {
      MapJoystickActions(windowID, pDevice);
      pDevice = pDevice->NextSibling("joystick");
    }
  }
#endif

  if ((pDevice = pWindow->FirstChild("touch")) != NULL)
  {
    // map touch actions
    while (pDevice)
    {
      MapTouchActions(windowID, pDevice);
      pDevice = pDevice->NextSibling("touch");
    }
  }
}

bool CButtonTranslator::TranslateActionString(const char *szAction, int &action)
{
  action = ACTION_NONE;
  std::string strAction = szAction;
  StringUtils::ToLower(strAction);
  if (CBuiltins::HasCommand(strAction)) 
    action = ACTION_BUILT_IN_FUNCTION;

  for (unsigned int index=0;index < ARRAY_SIZE(actions);++index)
  {
    if (strAction == actions[index].name)
    {
      action = actions[index].action;
      break;
    }
  }

  if (action == ACTION_NONE)
  {
    CLog::Log(LOGERROR, "Keymapping error: no such action '%s' defined", strAction.c_str());
    return false;
  }

  return true;
}

std::string CButtonTranslator::TranslateWindow(int windowID)
{
  for (unsigned int index = 0; index < ARRAY_SIZE(windows); ++index)
  {
    if (windows[index].action == windowID)
      return windows[index].name;
  }
  return "";
}

int CButtonTranslator::TranslateWindow(const std::string &window)
{
  std::string strWindow(window);
  if (strWindow.empty()) 
    return WINDOW_INVALID;
  StringUtils::ToLower(strWindow);
  // eliminate .xml
  if (StringUtils::EndsWith(strWindow, ".xml"))
    strWindow = strWindow.substr(0, strWindow.size() - 4);

  // window12345, for custom window to be keymapped
  if (strWindow.length() > 6 && StringUtils::StartsWithNoCase(strWindow, "window"))
    strWindow = strWindow.substr(6);
  if (StringUtils::StartsWithNoCase(strWindow, "my"))  // drop "my" prefix
    strWindow = strWindow.substr(2);
  if (StringUtils::IsNaturalNumber(strWindow))
  {
    // allow a full window id or a delta id
    int iWindow = atoi(strWindow.c_str());
    if (iWindow > WINDOW_INVALID)
      return iWindow;
    return WINDOW_HOME + iWindow;
  }

  // run through the window structure
  for (unsigned int index = 0; index < ARRAY_SIZE(windows); ++index)
  {
    if (strWindow == windows[index].name)
      return windows[index].action;
  }

  CLog::Log(LOGERROR, "Window Translator: Can't find window %s", strWindow.c_str());
  return WINDOW_INVALID;
}

uint32_t CButtonTranslator::TranslateGamepadString(const char *szButton)
{
  if (!szButton) 
    return 0;
  uint32_t buttonCode = 0;
  std::string strButton = szButton;
  StringUtils::ToLower(strButton);
  if (strButton == "a") buttonCode = KEY_BUTTON_A;
  else if (strButton == "b") buttonCode = KEY_BUTTON_B;
  else if (strButton == "x") buttonCode = KEY_BUTTON_X;
  else if (strButton == "y") buttonCode = KEY_BUTTON_Y;
  else if (strButton == "white") buttonCode = KEY_BUTTON_WHITE;
  else if (strButton == "black") buttonCode = KEY_BUTTON_BLACK;
  else if (strButton == "start") buttonCode = KEY_BUTTON_START;
  else if (strButton == "back") buttonCode = KEY_BUTTON_BACK;
  else if (strButton == "leftthumbbutton") buttonCode = KEY_BUTTON_LEFT_THUMB_BUTTON;
  else if (strButton == "rightthumbbutton") buttonCode = KEY_BUTTON_RIGHT_THUMB_BUTTON;
  else if (strButton == "leftthumbstick") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK;
  else if (strButton == "leftthumbstickup") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_UP;
  else if (strButton == "leftthumbstickdown") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_DOWN;
  else if (strButton == "leftthumbstickleft") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_LEFT;
  else if (strButton == "leftthumbstickright") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_RIGHT;
  else if (strButton == "rightthumbstick") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK;
  else if (strButton == "rightthumbstickup") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_UP;
  else if (strButton =="rightthumbstickdown") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_DOWN;
  else if (strButton == "rightthumbstickleft") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_LEFT;
  else if (strButton == "rightthumbstickright") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_RIGHT;
  else if (strButton == "lefttrigger") buttonCode = KEY_BUTTON_LEFT_TRIGGER;
  else if (strButton == "righttrigger") buttonCode = KEY_BUTTON_RIGHT_TRIGGER;
  else if (strButton == "leftanalogtrigger") buttonCode = KEY_BUTTON_LEFT_ANALOG_TRIGGER;
  else if (strButton == "rightanalogtrigger") buttonCode = KEY_BUTTON_RIGHT_ANALOG_TRIGGER;
  else if (strButton == "dpadleft") buttonCode = KEY_BUTTON_DPAD_LEFT;
  else if (strButton == "dpadright") buttonCode = KEY_BUTTON_DPAD_RIGHT;
  else if (strButton == "dpadup") buttonCode = KEY_BUTTON_DPAD_UP;
  else if (strButton == "dpaddown") buttonCode = KEY_BUTTON_DPAD_DOWN;
  else CLog::Log(LOGERROR, "Gamepad Translator: Can't find button %s", strButton.c_str());
  return buttonCode;
}

uint32_t CButtonTranslator::TranslateRemoteString(const char *szButton)
{
  if (!szButton) 
    return 0;
  uint32_t buttonCode = 0;
  std::string strButton = szButton;
  StringUtils::ToLower(strButton);
  if (strButton == "left") buttonCode = XINPUT_IR_REMOTE_LEFT;
  else if (strButton =="right") buttonCode = XINPUT_IR_REMOTE_RIGHT;
  else if (strButton =="up") buttonCode = XINPUT_IR_REMOTE_UP;
  else if (strButton == "down") buttonCode = XINPUT_IR_REMOTE_DOWN;
  else if (strButton == "select") buttonCode = XINPUT_IR_REMOTE_SELECT;
  else if (strButton == "back") buttonCode = XINPUT_IR_REMOTE_BACK;
  else if (strButton == "menu") buttonCode = XINPUT_IR_REMOTE_MENU;
  else if (strButton == "info") buttonCode = XINPUT_IR_REMOTE_INFO;
  else if (strButton == "display") buttonCode = XINPUT_IR_REMOTE_DISPLAY;
  else if (strButton == "title") buttonCode = XINPUT_IR_REMOTE_TITLE;
  else if (strButton == "play") buttonCode = XINPUT_IR_REMOTE_PLAY;
  else if (strButton == "pause") buttonCode = XINPUT_IR_REMOTE_PAUSE;
  else if (strButton == "reverse") buttonCode = XINPUT_IR_REMOTE_REVERSE;
  else if (strButton == "forward") buttonCode = XINPUT_IR_REMOTE_FORWARD;
  else if (strButton == "skipplus") buttonCode = XINPUT_IR_REMOTE_SKIP_PLUS;
  else if (strButton == "skipminus") buttonCode = XINPUT_IR_REMOTE_SKIP_MINUS;
  else if (strButton == "stop") buttonCode = XINPUT_IR_REMOTE_STOP;
  else if (strButton == "zero") buttonCode = XINPUT_IR_REMOTE_0;
  else if (strButton == "one") buttonCode = XINPUT_IR_REMOTE_1;
  else if (strButton == "two") buttonCode = XINPUT_IR_REMOTE_2;
  else if (strButton == "three") buttonCode = XINPUT_IR_REMOTE_3;
  else if (strButton == "four") buttonCode = XINPUT_IR_REMOTE_4;
  else if (strButton == "five") buttonCode = XINPUT_IR_REMOTE_5;
  else if (strButton == "six") buttonCode = XINPUT_IR_REMOTE_6;
  else if (strButton == "seven") buttonCode = XINPUT_IR_REMOTE_7;
  else if (strButton == "eight") buttonCode = XINPUT_IR_REMOTE_8;
  else if (strButton == "nine") buttonCode = XINPUT_IR_REMOTE_9;
  // additional keys from the media center extender for xbox remote
  else if (strButton == "power") buttonCode = XINPUT_IR_REMOTE_POWER;
  else if (strButton == "mytv") buttonCode = XINPUT_IR_REMOTE_MY_TV;
  else if (strButton == "mymusic") buttonCode = XINPUT_IR_REMOTE_MY_MUSIC;
  else if (strButton == "mypictures") buttonCode = XINPUT_IR_REMOTE_MY_PICTURES;
  else if (strButton == "myvideo") buttonCode = XINPUT_IR_REMOTE_MY_VIDEOS;
  else if (strButton == "record") buttonCode = XINPUT_IR_REMOTE_RECORD;
  else if (strButton == "start") buttonCode = XINPUT_IR_REMOTE_START;
  else if (strButton == "volumeplus") buttonCode = XINPUT_IR_REMOTE_VOLUME_PLUS;
  else if (strButton == "volumeminus") buttonCode = XINPUT_IR_REMOTE_VOLUME_MINUS;
  else if (strButton == "channelplus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_PLUS;
  else if (strButton == "channelminus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS;
  else if (strButton == "pageplus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_PLUS;
  else if (strButton == "pageminus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS;
  else if (strButton == "mute") buttonCode = XINPUT_IR_REMOTE_MUTE;
  else if (strButton == "recordedtv") buttonCode = XINPUT_IR_REMOTE_RECORDED_TV;
  else if (strButton == "guide") buttonCode = XINPUT_IR_REMOTE_GUIDE;
  else if (strButton == "livetv") buttonCode = XINPUT_IR_REMOTE_LIVE_TV;
  else if (strButton == "liveradio") buttonCode = XINPUT_IR_REMOTE_LIVE_RADIO;
  else if (strButton == "epgsearch") buttonCode = XINPUT_IR_REMOTE_EPG_SEARCH;
  else if (strButton == "star") buttonCode = XINPUT_IR_REMOTE_STAR;
  else if (strButton == "hash") buttonCode = XINPUT_IR_REMOTE_HASH;
  else if (strButton == "clear") buttonCode = XINPUT_IR_REMOTE_CLEAR;
  else if (strButton == "enter") buttonCode = XINPUT_IR_REMOTE_ENTER;
  else if (strButton == "xbox") buttonCode = XINPUT_IR_REMOTE_DISPLAY; // same as display
  else if (strButton == "playlist") buttonCode = XINPUT_IR_REMOTE_PLAYLIST;
  else if (strButton == "teletext") buttonCode = XINPUT_IR_REMOTE_TELETEXT;
  else if (strButton == "red") buttonCode = XINPUT_IR_REMOTE_RED;
  else if (strButton == "green") buttonCode = XINPUT_IR_REMOTE_GREEN;
  else if (strButton == "yellow") buttonCode = XINPUT_IR_REMOTE_YELLOW;
  else if (strButton == "blue") buttonCode = XINPUT_IR_REMOTE_BLUE;
  else if (strButton == "subtitle") buttonCode = XINPUT_IR_REMOTE_SUBTITLE;
  else if (strButton == "language") buttonCode = XINPUT_IR_REMOTE_LANGUAGE;
  else if (strButton == "eject") buttonCode = XINPUT_IR_REMOTE_EJECT;
  else if (strButton == "contentsmenu") buttonCode = XINPUT_IR_REMOTE_CONTENTS_MENU;
  else if (strButton == "rootmenu") buttonCode = XINPUT_IR_REMOTE_ROOT_MENU;
  else if (strButton == "topmenu") buttonCode = XINPUT_IR_REMOTE_TOP_MENU;
  else if (strButton == "dvdmenu") buttonCode = XINPUT_IR_REMOTE_DVD_MENU;
  else if (strButton == "print") buttonCode = XINPUT_IR_REMOTE_PRINT;
  else CLog::Log(LOGERROR, "Remote Translator: Can't find button %s", strButton.c_str());
  return buttonCode;
}

uint32_t CButtonTranslator::TranslateUniversalRemoteString(const char *szButton)
{
  if (!szButton || strlen(szButton) < 4 || strnicmp(szButton, "obc", 3)) 
    return 0;
  const char *szCode = szButton + 3;
  // Button Code is 255 - OBC (Original Button Code) of the button
  uint32_t buttonCode = 255 - atol(szCode);
  if (buttonCode > 255) 
    buttonCode = 0;
  return buttonCode;
}

uint32_t CButtonTranslator::TranslateKeyboardString(const char *szButton)
{
  uint32_t buttonCode = 0;
  XBMCKEYTABLE keytable;

  // Look up the key name
  if (KeyTableLookupName(szButton, &keytable))
  {
    buttonCode = keytable.vkey;
  }

  // The lookup failed i.e. the key name wasn't found
  else
  {
    CLog::Log(LOGERROR, "Keyboard Translator: Can't find button %s", szButton);
  }

  buttonCode |= KEY_VKEY;

  return buttonCode;
}

uint32_t CButtonTranslator::TranslateKeyboardButton(TiXmlElement *pButton)
{
  uint32_t button_id = 0;
  const char *szButton = pButton->Value();

  if (!szButton) 
    return 0;
  const std::string strKey = szButton;
  if (strKey == "key")
  {
    std::string strID;
    if (pButton->QueryValueAttribute("id", &strID) == TIXML_SUCCESS)
    {
      const char *str = strID.c_str();
      char *endptr;
      long int id = strtol(str, &endptr, 0);
      if (endptr - str != (int)strlen(str) || id <= 0 || id > 0x00FFFFFF)
        CLog::Log(LOGDEBUG, "%s - invalid key id %s", __FUNCTION__, strID.c_str());
      else
        button_id = (uint32_t) id;
    }
    else
      CLog::Log(LOGERROR, "Keyboard Translator: `key' button has no id");
  }
  else
    button_id = TranslateKeyboardString(szButton);

  // Process the ctrl/shift/alt modifiers
  std::string strMod;
  if (pButton->QueryValueAttribute("mod", &strMod) == TIXML_SUCCESS)
  {
    StringUtils::ToLower(strMod);

    vector<string> modArray = StringUtils::Split(strMod, ",");
    for (vector<string>::const_iterator i = modArray.begin(); i != modArray.end(); ++i)
    {
      string substr = *i;
      StringUtils::Trim(substr);

      if (substr == "ctrl" || substr == "control")
        button_id |= CKey::MODIFIER_CTRL;
      else if (substr == "shift")
        button_id |= CKey::MODIFIER_SHIFT;
      else if (substr == "alt")
        button_id |= CKey::MODIFIER_ALT;
      else if (substr == "super" || substr == "win")
        button_id |= CKey::MODIFIER_SUPER;
      else if (substr == "meta" || substr == "cmd")
        button_id |= CKey::MODIFIER_META;
      else
        CLog::Log(LOGERROR, "Keyboard Translator: Unknown key modifier %s in %s", substr.c_str(), strMod.c_str());
     }
  }

  return button_id;
}

uint32_t CButtonTranslator::TranslateAppCommand(const char *szButton)
{
#ifdef TARGET_WINDOWS
  std::string strAppCommand = szButton;
  StringUtils::ToLower(strAppCommand);

  for (int i = 0; i < ARRAY_SIZE(appcommands); i++)
    if (strAppCommand == appcommands[i].name)
      return appcommands[i].action | KEY_APPCOMMAND;

  CLog::Log(LOGERROR, "%s: Can't find appcommand %s", __FUNCTION__, szButton);
#endif

  return 0;
}

uint32_t CButtonTranslator::TranslateMouseCommand(TiXmlElement *pButton)
{
  uint32_t buttonId = 0;

  if (pButton)
  {
    std::string szKey = pButton->ValueStr();
    if (!szKey.empty())
    {
      StringUtils::ToLower(szKey);
      for (unsigned int i = 0; i < ARRAY_SIZE(mousekeys); i++)
      {
        if (szKey == mousekeys[i].name)
        {
          buttonId = mousekeys[i].action;
          break;
        }
      }
      if (!buttonId)
      {
        CLog::Log(LOGERROR, "Unknown mouse action (%s), skipping", pButton->Value());
      }
      else
      {
        int id = 0;
        if ((pButton->QueryIntAttribute("id", &id) == TIXML_SUCCESS) && id>=0 && id<MOUSE_MAX_BUTTON)
        {
          buttonId += id;
        }
      }
    }
  }

  return buttonId;
}

void CButtonTranslator::Clear()
{
  m_translatorMap.clear();
#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE)
  ClearLircButtonMapEntries();
  lircRemotesMap.clear();
#endif

#if defined(HAS_SDL_JOYSTICK) || defined(HAS_EVENT_SERVER)
  m_joystickButtonMap.clear();
  m_joystickAxisMap.clear();
  m_joystickHatMap.clear();
#endif

  m_Loaded = false;
}

uint32_t CButtonTranslator::TranslateTouchCommand(TiXmlElement *pButton, CButtonAction &action)
{
  const char *szButton = pButton->Value();
  if (szButton == NULL || pButton->FirstChild() == NULL)
    return ACTION_NONE;

  const char *szAction = pButton->FirstChild()->Value();
  if (szAction == NULL)
    return ACTION_NONE;

  std::string strTouchCommand = szButton;
  StringUtils::ToLower(strTouchCommand);

  const char *attrVal = pButton->Attribute("direction");
  if (attrVal != NULL)
    strTouchCommand += attrVal;

  uint32_t actionId = ACTION_NONE;
  for (unsigned int i = 0; i < ARRAY_SIZE(touchcommands); i++)
  {
    if (strTouchCommand == touchcommands[i].name)
    {
      actionId = touchcommands[i].action;
      break;
    }
  }

  if (actionId <= ACTION_NONE)
  {
    CLog::Log(LOGERROR, "%s: Can't find touch command %s", __FUNCTION__, szButton);
    return ACTION_NONE;
  }

  attrVal = pButton->Attribute("pointers");
  if (attrVal != NULL)
  {
    int pointers = (int)strtol(attrVal, NULL, 0);
    if (pointers >= 1)
      actionId += pointers - 1;
  }

  action.strID = szAction;
  if (!TranslateActionString(szAction, action.id) || action.id <= ACTION_NONE)
    return ACTION_NONE;

  return actionId | KEY_TOUCH;
}

void CButtonTranslator::MapTouchActions(int windowID, TiXmlNode *pTouch)
{
  if (pTouch == NULL)
    return;

  buttonMap map;
  // check if there already is a touch map for the window ID
  std::map<int, buttonMap>::iterator it = m_touchMap.find(windowID);
  if (it != m_touchMap.end())
  {
    // get the existing touch map and remove it from the window mapping
    // as it will be inserted later on
    map = it->second;
    m_touchMap.erase(it);
  }

  uint32_t actionId = 0;
  TiXmlElement *pTouchElem = pTouch->ToElement();
  if (pTouchElem == NULL)
    return;

  TiXmlElement *pButton = pTouchElem->FirstChildElement();
  while (pButton != NULL)
  {
    CButtonAction action;
    actionId = TranslateTouchCommand(pButton, action);
    if (actionId > 0)
    {
      // check if there already is a mapping for the parsed action
      // and remove it if necessary
      buttonMap::iterator actionIt = map.find(actionId);
      if (actionIt != map.end())
        map.erase(actionIt);

      map.insert(std::make_pair(actionId, action));
    }

    pButton = pButton->NextSiblingElement();
  }

  // add the modified touch map with the window ID
  if (!map.empty())
    m_touchMap.insert(std::pair<int, buttonMap>(windowID, map));
}

int CButtonTranslator::GetTouchActionCode(int window, int action, std::string &actionString)
{
  std::map<int, buttonMap>::const_iterator windowIt = m_touchMap.find(window);
  if (windowIt == m_touchMap.end())
    return ACTION_NONE;

  buttonMap::const_iterator touchIt = windowIt->second.find(action);
  if (touchIt == windowIt->second.end())
    return ACTION_NONE;

  actionString = touchIt->second.strID;
  return touchIt->second.id;
}
