## InputUtil
##
## Utilities for dealing with mouse and keyboard input.
##
## Copyright (c) 2008 The BUG Mod.
##
## Author: EmperorFool

from CvPythonExtensions import *
import BugConfig
import BugDll
import BugUtil
import CvEventInterface
import types

KEY_MAP = {
    "'": "APOSTROPHE",
    "@": "AT",
    "\\": "BACKSLASH",
    "CAPS": "CAPSLOCK",
    ":": "COLON",
    ",": "COMMA",
    "DEL": "DELETE",
    "=": "EQUALS",
    "`": "GRAVE",
    "[": "LBRACKET",
    "-": "MINUS",
    ".": "PERIOD",
    "]": "RBRACKET",
    ";": "SEMICOLON",
    "/": "SLASH",
    " ": "SPACE",
    "_": "UNDERLINE",
    "+": "PLUS",  # not a valid key normally -- only with NUMPAD
}

ALT = 0
CONTROL = 1
SHIFT = 2

MODIFIER_MAP = {
    "ALT": ALT,
    "COMMAND": ALT,
    "CMD": ALT,
    "CONTROL": CONTROL,
    "CTRL": CONTROL,
    "SHIFT": SHIFT,
}

MODIFIER_KEYS = (
    InputTypes.KB_LALT, 
    InputTypes.KB_LCONTROL, 
    InputTypes.KB_LSHIFT, 
    InputTypes.KB_RALT, 
    InputTypes.KB_RCONTROL, 
    InputTypes.KB_RSHIFT,
    int(InputTypes.KB_LALT), 
    int(InputTypes.KB_LCONTROL), 
    int(InputTypes.KB_LSHIFT), 
    int(InputTypes.KB_RALT), 
    int(InputTypes.KB_RCONTROL), 
    int(InputTypes.KB_RSHIFT),
)

KEYS_BY_CODE = {}
CODES_BY_KEY = {}

ALT_HASH = 256
CONTROL_HASH = 512
SHIFT_HASH = 1024

def isModifier(key):
    return key in MODIFIER_KEYS

def codeToKey(code):
    return KEYS_BY_CODE[code]

def keyToCode(key):
    return CODES_BY_KEY[key]


def stringToKeystroke(key):
    """
    Returns a Keystroke created from the given string.
    
    It can contain modifiers (alt, control, shift) separated from the key with spaces.
    """
    if not isinstance(key, types.StringTypes):
        raise BugUtil.ConfigError("key must be a string")
    keys = key.split()
    input = None
    modifiers = [False, False, False]
    for part in keys:
        k = part.upper()
        if k in MODIFIER_MAP:
            modifiers[MODIFIER_MAP[k]] = True
        else:
            if k.startswith("NUMPAD") or k.startswith("NUM") or k.startswith("NP"):
                if k.startswith("NUMPAD"):
                    k = k[6:]
                elif k.startswith("NUM"):
                    k = k[3:]
                else:
                    k = k[2:]
                if k in KEY_MAP:
                    k = KEY_MAP[k]
                k = "KB_NUMPAD" + k
            else:
                if k in KEY_MAP:
                    k = KEY_MAP[k]
                k = "KB_" + k
            try:
                input = getattr(InputTypes, k)
            except KeyError:
                raise BugUtil.ConfigError("invalid key %s from %s" % (part, key))
    if input is None:
        raise BugUtil.ConfigError("invalid key %s (only modifiers)" % key)
    return Keystroke(input, *modifiers)

def stringToKeystrokes(keys):
    """
    Returns a list of Keystrokes created from the given string.
    
    Individual keystrokes must be separated by a vertical bar (|).
    """
    result = []
    for key in keys.split("|"):
        result.append(stringToKeystroke(key))
    return result


class Keystroke:
    """
    Holds the information necessary to recognize a single keystroke,
    including modifiers: alt, control, and shift.
    """
    def __init__(self, keyOrCode, alt=False, control=False, shift=False):
        if isinstance(keyOrCode, int):
            self.code = keyOrCode
        elif isinstance(keyOrCode, InputTypes):
            self.code = int(keyOrCode)
        else:
            raise BugUtil.ConfigError("key %r must be an InputTypes constant or int" % keyOrCode)
        self.alt = alt
        self.control = control
        self.shift = shift
        self.hash = None
    
    def __str__(self):
        s = ""
        if self.alt:
            s += "ALT + "
        if self.control:
            s += "CTRL + "
        if self.shift:
            s += "SHIFT + "
        return "%s%s" % (s, codeToKey(self.code))
    
    def __repr__(self):
        return "<key %s>" % str(self)
    
    def __hash__(self):
        if self.hash is None:
            self.hash = self.code
            if self.alt:
                self.hash ^= ALT_HASH
            if self.control:
                self.hash ^= CONTROL_HASH
            if self.shift:
                self.hash ^= SHIFT_HASH
        return self.hash
    
    def __eq__(self, other):
        if not isinstance(other, Keystroke):
            return NotImplemented
        return (self.code == other.code and self.alt == other.alt and
                self.control == other.control and self.shift == other.shift)
    
    def __ne__(self, other):
        if not isinstance(other, Keystroke):
            return NotImplemented
        return (self.code != other.code or self.alt != other.alt or
                self.control != other.control or self.shift != other.shift)


def init():
    for k, c in InputTypes.__dict__.iteritems():
        if k.startswith("KB_"):
            key = k[3:]
            code = int(c)
            KEYS_BY_CODE[code] = key
            CODES_BY_KEY[key] = code
            CODES_BY_KEY[k] = code

# initialize when the module is loaded
init()


## configuration handler

class ShortcutHandler(BugConfig.HandlerWithArgs):
    
    TAG = "shortcut"
    
    def __init__(self):
        BugConfig.HandlerWithArgs.__init__(self, ShortcutHandler.TAG, "key keys module function dll")
        self.addExcludedAttribute("key")
        self.addAttribute("keys", True, False, None, "key")
        self.addAttribute("module", True, True)
        self.addAttribute("function", True)
        self.addAttribute("dll")
    
    def handle(self, element, keys, module, function, dll):
        dll = BugDll.decode(dll)
        if self.isDllOkay(element, dll):
            CvEventInterface.getEventManager().addShortcutHandler(keys, BugUtil.getFunction(module, function, *element.args, **element.kwargs))
        else:
            BugUtil.info("InputUtil - ignoring <%s> %s, requires dll version %s", element.tag, keys, self.resolveDll(element, dll))
