import pygetwindow as gw

from src.utils import humanize
from .ino import sendCommandArduino


# Tastencodes der Arduino-Keyboard-Bibliothek (Keyboard.h). Falsche Werte
# hier senden lautlos die falsche Taste - 'tab' stand faelschlich auf 180
# statt 179, darum kam der Feldwechsel im Login nie an.
SPECIAL_KEYS = {
    'space': 32,
    'enter': 176,       # KEY_RETURN     0xB0
    'esc': 177,         # KEY_ESC        0xB1
    'backspace': 178,   # KEY_BACKSPACE  0xB2
    'tab': 179,         # KEY_TAB        0xB3
    'insert': 209,      # KEY_INSERT     0xD1
    'home': 210,        # KEY_HOME       0xD2
    'pageup': 211,      # KEY_PAGE_UP    0xD3
    'delete': 212,      # KEY_DELETE     0xD4
    'end': 213,         # KEY_END        0xD5
    'pagedown': 214,    # KEY_PAGE_DOWN  0xD6
    'right': 215,       # KEY_RIGHT_ARROW 0xD7
    'left': 216,        # KEY_LEFT_ARROW  0xD8
    'down': 217,        # KEY_DOWN_ARROW  0xD9
    'up': 218,          # KEY_UP_ARROW    0xDA
    'ctrl': 128,        # KEY_LEFT_CTRL  0x80
    'shift': 129,       # KEY_LEFT_SHIFT 0x81
    'alt': 130,         # KEY_LEFT_ALT   0x82
    **{f'f{index}': 193 + index for index in range(1, 13)},
}


def _focusTibia():
    try:
        windows = [
            window for window in gw.getWindowsWithTitle('Tibia')
            if window.title == 'Tibia' or window.title.startswith('Tibia - ')
        ]
        if windows and not windows[0].isActive:
            windows[0].activate()
            humanize.pause(0.05)
    except Exception:
        # ponytail: focus is best effort; input itself always remains Arduino-HID.
        return


def _normalize(keys):
    if len(keys) == 1 and isinstance(keys[0], (list, tuple)):
        return keys[0]
    return keys


def _keyCode(key):
    name = key.lower()
    if name in SPECIAL_KEYS:
        return SPECIAL_KEYS[name]
    return ord(name) if len(name) == 1 else 0


def _requireCode(key):
    code = _keyCode(key)
    if not code:
        raise ValueError(f'Nicht unterstützte Taste: {key}')
    return code


def hotkey(*args):
    _focusTibia()
    codes = [_requireCode(key) for key in _normalize(args)]
    try:
        for code in codes:
            sendCommandArduino(f'keyDown,{code}')
            humanize.pause(0.05)
    finally:
        for code in reversed(codes):
            sendCommandArduino(f'keyUp,{code}')
            humanize.pause(0.04)


def keyDown(key: str):
    _focusTibia()
    sendCommandArduino(f'keyDown,{_requireCode(key)}')


def keyUp(key: str):
    _focusTibia()
    sendCommandArduino(f'keyUp,{_requireCode(key)}')


def releaseAll():
    """Alle gehaltenen Tasten und Maustasten loslassen (Notfall).

    Nach einer Panne (Ausnahme mitten in keyDown/keyUp) koennte eine Taste
    physisch gedrueckt bleiben - fuer den Client verdaechtig. Dieser Befehl
    setzt am Chip alles zurueck. Bewusst OHNE _focusTibia, damit er auch
    dann durchgeht, wenn das Fenster gerade nicht gefunden wird."""
    try:
        sendCommandArduino('releaseAll')
    except Exception:
        # Ein Aufraeum-Befehl darf nie selbst eine neue Ausnahme werfen.
        pass


def press(*args):
    _focusTibia()
    for key in _normalize(args):
        humanize.reaction()
        sendCommandArduino(f'press,{_requireCode(key)}')


def write(phrase: str):
    _focusTibia()
    if not isinstance(phrase, str) or len(phrase) > 2048:
        raise ValueError('Ungültiger Text für Arduino-Eingabe.')
    humanize.reaction()
    sendCommandArduino(f'writeHex,{phrase.encode("utf-8").hex()}')
