# -*- coding: utf-8 -*-
import struct
import traceback
from time import time as now
from collections import namedtuple
from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP, normalize_name
from ._nixcommon import EV_KEY, aggregate_devices, ensure_root
from ._suppress import KeyTable

# TODO: start by reading current keyboard state, as to not missing any already pressed keys.
# See: http://stackoverflow.com/questions/3649874/how-to-get-keyboard-state-in-linux

def cleanup_key(name):
    """ Formats a dumpkeys format to our standard. """
    name = name.lstrip('+')
    is_keypad = name.startswith('KP_')
    for mod in ('Meta_', 'Control_', 'dead_', 'KP_'):
        if name.startswith(mod):
            name = name[len(mod):]

    # Dumpkeys is weird like that.
    if name == 'Remove':
        name = 'Delete'
    elif name == 'Delete':
        name = 'Backspace'

    return normalize_name(name), is_keypad

def cleanup_modifier(modifier):
    expected = ('alt', 'ctrl', 'shift', 'alt gr')
    modifier = normalize_name(modifier)
    if modifier in expected:
        return modifier
    if modifier[:-1] in expected:
        return modifier[:-1]
    raise ValueError('Unknown modifier {}'.format(modifier))

"""
Use `dumpkeys --keys-only` to list all scan codes and their names. We
then parse the output and built a table. For each scan code and modifiers we
have a list of names and vice-versa.
"""
from subprocess import check_output
import re

to_name = {}
from_name = {}
keypad_scan_codes = set()

def register_key(key_and_modifiers, name):
    to_name[key_and_modifiers] = name
    from_name[name] = key_and_modifiers

def build_tables():
    if to_name and from_name: return
    ensure_root()

    keycode_template = r'^(.*?)keycode\s+(\d+)\s+=(.*?)$'
    dump = check_output(['dumpkeys', '--keys-only'], universal_newlines=True)
    for str_modifiers, str_scan_code, str_names in re.findall(keycode_template, dump, re.MULTILINE):
        if not str_names: continue
        modifiers = tuple(sorted(set(cleanup_modifier(m) for m in str_modifiers.strip().split())))
        scan_code = int(str_scan_code)
        name, is_keypad = cleanup_key(str_names.strip().split()[0])
        to_name[(scan_code, modifiers)] = name
        if is_keypad:
            keypad_scan_codes.add(scan_code)
            from_name['keypad ' + name] = (scan_code, ())
        if name not in from_name or len(modifiers) < len(from_name[name][1]):
            from_name[name] = (scan_code, modifiers)

    # Assume Shift uppercases keys that are single characters.
    # Hackish, but a good heuristic so far.
    for name, (scan_code, modifiers) in list(from_name.items()):
        upper = name.upper()
        if len(name) == 1 and upper not in from_name:
            register_key((scan_code, modifiers + ('shift',)), upper)

    # dumpkeys consistently misreports the Windows key, sometimes
    # skipping it completely or reporting as 'alt. 125 = left win,
    # 126 = right win.
    if (125, ()) not in to_name or to_name[(125, ())] == 'alt':
        register_key((125, ()), 'windows')
    if (126, ()) not in to_name or to_name[(126, ())] == 'alt':
        register_key((126, ()), 'windows')

    # The menu key is usually skipped altogether, so we also add it manually.
    if (127, ()) not in to_name:
        register_key((127, ()), 'menu')

    synonyms_template = r'^(\S+)\s+for (.+)$'
    dump = check_output(['dumpkeys', '--long-info'], universal_newlines=True)
    for synonym_str, original_str in re.findall(synonyms_template, dump, re.MULTILINE):
        synonym, _ = cleanup_key(synonym_str)
        original, _ = cleanup_key(original_str)
        try:
            from_name[synonym] = from_name[original]
        except KeyError:
            # Dumpkeys reported a synonym to an unknown key.
            pass

device = None
def build_device():
    global device
    if device: return
    ensure_root()
    device = aggregate_devices('kbd')

def init():
    build_device()
    build_tables()

pressed_modifiers = set()

def listen(queue, is_allowed=lambda *args: True):
    build_device()
    build_tables()

    while True:
        time, type, code, value, device_id = device.read_event()
        if type != EV_KEY:
            continue

        scan_code = code
        event_type = KEY_DOWN if value else KEY_UP # 0 = UP, 1 = DOWN, 2 = HOLD

        try:
            name = to_name[(scan_code, tuple(sorted(pressed_modifiers)))]
        except KeyError:
            name = to_name.get((scan_code, ()), 'unknown')
            
        if name in ('alt', 'alt gr', 'ctrl', 'shift'):
            if event_type == KEY_DOWN:
                pressed_modifiers.add(name)
            else:
                pressed_modifiers.discard(name)

        is_keypad = scan_code in keypad_scan_codes
        queue.put(KeyboardEvent(event_type=event_type, scan_code=scan_code, name=name, time=time, device=device_id, is_keypad=is_keypad))

def write_event(scan_code, is_down):
    build_device()
    device.write_event(EV_KEY, scan_code, int(is_down))

def map_char(name):
    build_tables()
    if name in from_name:
        return from_name[name]

    parts = name.split(' ', 1)
    if (name.startswith('left ') or name.startswith('right ')) and parts[1] in from_name:
        return from_name[parts[1]]
    else:
        raise ValueError('Name {} is not mapped to any known key.'.format(repr(name)))

def press(scan_code):
    write_event(scan_code, True)

def release(scan_code):
    write_event(scan_code, False)

def type_unicode(character):
    codepoint = ord(character)
    hexadecimal = hex(codepoint)[len('0x'):]

    for key in ['ctrl', 'shift', 'u']:
        scan_code, _ = map_char(key)
        press(scan_code)

    for key in hexadecimal:
        scan_code, _ = map_char(key)
        press(scan_code)
        release(scan_code)

    for key in ['ctrl', 'shift', 'u']:
        scan_code, _ = map_char(key)
        release(scan_code)

if __name__ == '__main__':
    def p(e):
        print(e)
    listen(p)
