#!/usr/bin/env python3
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>

import string
from typing import Dict, NamedTuple, Optional

from . import fast_data_types as defines
from .key_names import key_name_aliases

# ENCODING {{{
ENCODING = {
    '0': 'G',
    '1': 'H',
    '2': 'I',
    '3': 'J',
    '4': 'K',
    '5': 'L',
    '6': 'M',
    '7': 'N',
    '8': 'O',
    '9': 'P',
    'A': 'S',
    'APOSTROPHE': 'B',
    'B': 'T',
    'BACKSLASH': 't',
    'BACKSPACE': '1',
    'C': 'U',
    'CAPS LOCK': ':',
    'COMMA': 'C',
    'D': 'V',
    'DELETE': '3',
    'DOWN': '6',
    'E': 'W',
    'END': '-',
    'ENTER': 'z',
    'EQUAL': 'R',
    'ESCAPE': 'y',
    'F': 'X',
    'F1': '/',
    'F10': ']',
    'F11': '{',
    'F12': '}',
    'F13': '@',
    'F14': '%',
    'F15': '$',
    'F16': '#',
    'F17': 'BA',
    'F18': 'BB',
    'F19': 'BC',
    'F2': '*',
    'F20': 'BD',
    'F21': 'BE',
    'F22': 'BF',
    'F23': 'BG',
    'F24': 'BH',
    'F25': 'BI',
    'F3': '?',
    'F4': '&',
    'F5': '<',
    'F6': '>',
    'F7': '(',
    'F8': ')',
    'F9': '[',
    'G': 'Y',
    'GRAVE ACCENT': 'v',
    'H': 'Z',
    'HOME': '.',
    'I': 'a',
    'INSERT': '2',
    'J': 'b',
    'K': 'c',
    'KP 0': 'BJ',
    'KP 1': 'BK',
    'KP 2': 'BL',
    'KP 3': 'BM',
    'KP 4': 'BN',
    'KP 5': 'BO',
    'KP 6': 'BP',
    'KP 7': 'BQ',
    'KP 8': 'BR',
    'KP 9': 'BS',
    'KP ADD': 'BX',
    'KP DECIMAL': 'BT',
    'KP DIVIDE': 'BU',
    'KP ENTER': 'BY',
    'KP EQUAL': 'BZ',
    'KP MULTIPLY': 'BV',
    'KP SUBTRACT': 'BW',
    'L': 'd',
    'LEFT': '5',
    'LEFT ALT': 'Bc',
    'LEFT BRACKET': 's',
    'LEFT CONTROL': 'Bb',
    'LEFT SHIFT': 'Ba',
    'LEFT SUPER': 'Bd',
    'M': 'e',
    'MINUS': 'D',
    'N': 'f',
    'NUM LOCK': '=',
    'O': 'g',
    'P': 'h',
    'PAGE DOWN': '9',
    'PAGE UP': '8',
    'PAUSE': '!',
    'PERIOD': 'E',
    'PRINT SCREEN': '^',
    'Q': 'i',
    'R': 'j',
    'RIGHT': '4',
    'RIGHT ALT': 'Bg',
    'RIGHT BRACKET': 'u',
    'RIGHT CONTROL': 'Bf',
    'RIGHT SHIFT': 'Be',
    'RIGHT SUPER': 'Bh',
    'S': 'k',
    'SCROLL LOCK': '+',
    'SEMICOLON': 'Q',
    'SLASH': 'F',
    'SPACE': 'A',
    'T': 'l',
    'TAB': '0',
    'U': 'm',
    'UP': '7',
    'V': 'n',
    'W': 'o',
    'WORLD 1': 'w',
    'WORLD 2': 'x',
    'X': 'p',
    'Y': 'q',
    'Z': 'r',
    'PLUS': 'Bi',
    'UNDERSCORE': 'Bj',
    'MENU': 'Bk',
    'EXCLAM': 'Bl',
    'DOUBLE QUOTE': 'Bm',
    'NUMBER SIGN': 'Bn',
    'DOLLAR': 'Bo',
    'AMPERSAND': 'Bp',
    'PARENTHESIS LEFT': 'Bq',
    'PARENTHESIS RIGHT': 'Br',
    'COLON': 'Bs',
    'LESS': 'Bt',
    'GREATER': 'Bu',
    'AT': 'Bv',
    'PARAGRAPH': 'Bw',
    'MASCULINE': 'Bx',
    'A GRAVE': 'By',
    'A DIAERESIS': 'Bz',
    'A RING': 'B0',
    'AE': 'B1',
    'C CEDILLA': 'B2',
    'E GRAVE': 'B3',
    'E ACUTE': 'B4',
    'I GRAVE': 'B5',
    'N TILDE': 'B6',
    'O GRAVE': 'B7',
    'O DIAERESIS': 'B8',
    'O SLASH': 'B9',
    'U GRAVE': 'B.',
    'U DIAERESIS': 'B-',
    'S SHARP': 'B:',
    'CYRILLIC A': 'B+',
    'CYRILLIC BE': 'B=',
    'CYRILLIC VE': 'B^',
    'CYRILLIC GHE': 'B!',
    'CYRILLIC DE': 'B/',
    'CYRILLIC IE': 'B*',
    'CYRILLIC ZHE': 'B?',
    'CYRILLIC ZE': 'B&',
    'CYRILLIC I': 'B<',
    'CYRILLIC SHORT I': 'B>',
    'CYRILLIC KA': 'B(',
    'CYRILLIC EL': 'B)',
    'CYRILLIC EM': 'B[',
    'CYRILLIC EN': 'B]',
    'CYRILLIC O': 'B{',
    'CYRILLIC PE': 'B}',
    'CYRILLIC ER': 'B@',
    'CYRILLIC ES': 'B%',
    'CYRILLIC TE': 'B$',
    'CYRILLIC U': 'B#',
    'CYRILLIC EF': 'CA',
    'CYRILLIC HA': 'CB',
    'CYRILLIC TSE': 'CC',
    'CYRILLIC CHE': 'CD',
    'CYRILLIC SHA': 'CE',
    'CYRILLIC SHCHA': 'CF',
    'CYRILLIC HARD SIGN': 'CG',
    'CYRILLIC YERU': 'CH',
    'CYRILLIC SOFT SIGN': 'CI',
    'CYRILLIC E': 'CJ',
    'CYRILLIC YU': 'CK',
    'CYRILLIC YA': 'CL',
    'CYRILLIC IO': 'CM',
    'CIRCUMFLEX': 'CN'
}
KEY_MAP = {
    32: 'A',
    33: 'Bl',
    34: 'Bm',
    35: 'Bn',
    36: 'Bo',
    38: 'Bp',
    39: 'B',
    40: 'Bq',
    41: 'Br',
    43: 'Bi',
    44: 'C',
    45: 'D',
    46: 'E',
    47: 'F',
    48: 'G',
    49: 'H',
    50: 'I',
    51: 'J',
    52: 'K',
    53: 'L',
    54: 'M',
    55: 'N',
    56: 'O',
    57: 'P',
    58: 'Bs',
    59: 'Q',
    60: 'Bt',
    61: 'R',
    62: 'Bu',
    64: 'Bv',
    65: 'S',
    66: 'T',
    67: 'U',
    68: 'V',
    69: 'W',
    70: 'X',
    71: 'Y',
    72: 'Z',
    73: 'a',
    74: 'b',
    75: 'c',
    76: 'd',
    77: 'e',
    78: 'f',
    79: 'g',
    80: 'h',
    81: 'i',
    82: 'j',
    83: 'k',
    84: 'l',
    85: 'm',
    86: 'n',
    87: 'o',
    88: 'p',
    89: 'q',
    90: 'r',
    91: 's',
    92: 't',
    93: 'u',
    94: 'CN',
    95: 'Bj',
    96: 'v',
    161: 'w',
    162: 'x',
    167: 'Bw',
    186: 'Bx',
    192: 'By',
    196: 'Bz',
    197: 'B0',
    198: 'B1',
    199: 'B2',
    200: 'B3',
    201: 'B4',
    204: 'B5',
    209: 'B6',
    210: 'B7',
    214: 'B8',
    216: 'B9',
    217: 'B.',
    220: 'B-',
    222: 'B:',
    223: 'B+',
    224: 'B=',
    225: 'B^',
    226: 'B!',
    227: 'B/',
    228: 'B*',
    229: 'B?',
    230: 'B&',
    231: 'B<',
    232: 'B>',
    233: 'B(',
    234: 'B)',
    235: 'B[',
    236: 'B]',
    237: 'B{',
    238: 'B}',
    239: 'B@',
    240: 'B%',
    241: 'B$',
    242: 'B#',
    243: 'CA',
    244: 'CB',
    245: 'CC',
    246: 'CD',
    247: 'CE',
    248: 'CF',
    249: 'CG',
    250: 'CH',
    251: 'CI',
    252: 'CJ',
    253: 'CK',
    254: 'CL',
    255: 'CM',
    256: 'y',
    257: 'z',
    258: '0',
    259: '1',
    260: '2',
    261: '3',
    262: '4',
    263: '5',
    264: '6',
    265: '7',
    266: '8',
    267: '9',
    268: '.',
    269: '-',
    280: ':',
    281: '+',
    282: '=',
    283: '^',
    284: '!',
    290: '/',
    291: '*',
    292: '?',
    293: '&',
    294: '<',
    295: '>',
    296: '(',
    297: ')',
    298: '[',
    299: ']',
    300: '{',
    301: '}',
    302: '@',
    303: '%',
    304: '$',
    305: '#',
    306: 'BA',
    307: 'BB',
    308: 'BC',
    309: 'BD',
    310: 'BE',
    311: 'BF',
    312: 'BG',
    313: 'BH',
    314: 'BI',
    320: 'BJ',
    321: 'BK',
    322: 'BL',
    323: 'BM',
    324: 'BN',
    325: 'BO',
    326: 'BP',
    327: 'BQ',
    328: 'BR',
    329: 'BS',
    330: 'BT',
    331: 'BU',
    332: 'BV',
    333: 'BW',
    334: 'BX',
    335: 'BY',
    336: 'BZ',
    340: 'Ba',
    341: 'Bb',
    342: 'Bc',
    343: 'Bd',
    344: 'Be',
    345: 'Bf',
    346: 'Bg',
    347: 'Bh',
    348: 'Bk'
}
# END_ENCODING }}}

text_keys = (
    string.ascii_uppercase + string.ascii_lowercase + string.digits +
    '`~!@#$%^&*()_-+=[{]}\\|<,>./?;:\'" '
    'ÄäÖöÜüß§ºàåæçèéìñòøùабвгдежзийклмнопрстуфхцчшщъыьэюяё'
)


def text_match(key: str) -> Optional[str]:
    if key.upper() == 'SPACE':
        return ' '
    if key not in text_keys:
        return None
    return key


def encode(
    integer: int,
    chars: str = string.ascii_uppercase + string.ascii_lowercase + string.digits +
    '.-:+=^!/*?&<>()[]{}@%$#'
) -> str:
    ans = ''
    d = len(chars)
    while True:
        integer, remainder = divmod(integer, d)
        ans = chars[remainder] + ans
        if integer == 0:
            break
    return ans


def symbolic_name(glfw_name: str) -> str:
    return glfw_name[9:].replace('_', ' ')


def glfw_key_name(symbolic_name: str) -> str:
    return 'GLFW_KEY_' + symbolic_name.replace(' ', '_')


def update_encoding() -> None:
    import re
    import subprocess
    keys = {a for a in dir(defines) if a.startswith('GLFW_KEY_')}
    ans = ENCODING
    key_map = {}
    i = len(ans)
    for k in sorted(keys, key=lambda k: int(getattr(defines, k))):
        if k in ('GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE'):
            continue
        val = getattr(defines, k)
        name = symbolic_name(k)
        if val <= defines.GLFW_KEY_LAST and val != defines.GLFW_KEY_UNKNOWN:
            if name not in ans:
                ans[name] = encode(i)
                i += 1
            key_map[val] = ans[name]
    with open(__file__, 'r+') as f:
        raw = f.read()
        nraw = re.sub(
            r'^ENCODING = {.+^# END_ENCODING',
            'ENCODING = {!r}\nKEY_MAP={!r}\n# END_ENCODING'.format(
                ans, key_map
            ),
            raw,
            flags=re.MULTILINE | re.DOTALL
        )
        if raw == nraw:
            raise SystemExit('Failed to replace ENCODING dict')
        f.seek(0), f.truncate()
        f.write(nraw)
    subprocess.check_call(['yapf', '-i', __file__])


class KeyEvent(NamedTuple):
    type: int
    mods: int
    key: str


PRESS: int = 1
REPEAT: int = 2
RELEASE: int = 4
SHIFT, ALT, CTRL, SUPER = 1, 2, 4, 8
type_map = {'p': PRESS, 't': REPEAT, 'r': RELEASE}
rtype_map = {v: k for k, v in type_map.items()}
mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
rmod_map = {v: k for k, v in mod_map.items()}
key_rmap = {}
key_defs: Dict[str, str] = {}
config_key_map = {}
config_mod_map = {
    'SHIFT': SHIFT,
    'ALT': ALT,
    'OPTION': ALT,
    '⌥': ALT,
    '⌘': SUPER,
    'CMD': SUPER,
    'SUPER': SUPER,
    'CTRL': CTRL,
    'CONTROL': CTRL
}
for key_name, enc in ENCODING.items():
    key_name = key_name.replace(' ', '_')
    key_defs[key_name] = config_key_map[key_name] = key_name
    key_rmap[enc] = key_name
config_key_map.update({k: key_defs[v] for k, v in key_name_aliases.items() if v in key_defs})

enter_key = KeyEvent(PRESS, 0, key_defs['ENTER'])
backspace_key = KeyEvent(PRESS, 0, key_defs['BACKSPACE'])
globals().update(key_defs)
del key_name, enc


def decode_key_event(text: str) -> KeyEvent:
    typ = type_map[text[0]]
    mods = mod_map[text[1]]
    key = key_rmap[text[2:4]]
    return KeyEvent(typ, mods, key)


def encode_key_event(key_event: KeyEvent) -> str:
    typ = rtype_map[key_event.type]
    mods = rmod_map[key_event.mods]
    key = ENCODING[key_event.key.replace('_', ' ')]
    return typ + mods + key


class WindowSystemKeyEvent(NamedTuple):
    code: int
    mods: int
    action: int


def decode_key_event_as_window_system_key(text: str) -> Optional[WindowSystemKeyEvent]:
    k = decode_key_event(text)
    glfw_name = glfw_key_name(k.key)
    glfw_code = getattr(defines, glfw_name, None)
    if glfw_code is None:
        return None
    action = defines.GLFW_PRESS
    if k.type is RELEASE:
        action = defines.GLFW_RELEASE
    elif k.type is REPEAT:
        action = defines.GLFW_REPEAT
    mods = 0
    if k.mods & CTRL:
        mods |= defines.GLFW_MOD_CONTROL
    if k.mods & ALT:
        mods |= defines.GLFW_MOD_ALT
    if k.mods & SUPER:
        mods |= defines.GLFW_MOD_SUPER
    if k.mods & SHIFT:
        mods |= defines.GLFW_MOD_SHIFT
    return WindowSystemKeyEvent(glfw_code, mods, action)
