import gc

from kmk.consts import UnicodeMode
from kmk.handlers.stock import passthrough
from kmk.keys import KC, make_key
from kmk.types import AttrDict, KeySequenceMeta


def get_wide_ordinal(char):
    if len(char) != 2:
        return ord(char)

    return 0x10000 + (ord(char[0]) - 0xD800) * 0x400 + (ord(char[1]) - 0xDC00)


def sequence_press_handler(key, keyboard, KC, *args, **kwargs):
    oldkeys_pressed = keyboard.keys_pressed
    keyboard.keys_pressed = set()

    for ikey in key.meta.seq:
        if not getattr(ikey, 'no_press', None):
            keyboard.process_key(ikey, True)
            keyboard._send_hid()
        if not getattr(ikey, 'no_release', None):
            keyboard.process_key(ikey, False)
            keyboard._send_hid()

    keyboard.keys_pressed = oldkeys_pressed

    return keyboard


def simple_key_sequence(seq):
    return make_key(
        meta=KeySequenceMeta(seq),
        on_press=sequence_press_handler,
        on_release=passthrough,
    )


def send_string(message):
    seq = []

    for char in message:
        kc = getattr(KC, char.upper())

        if char.isupper():
            kc = KC.LSHIFT(kc)

        seq.append(kc)

    return simple_key_sequence(seq)


IBUS_KEY_COMBO = simple_key_sequence((KC.LCTRL(KC.LSHIFT(KC.U)),))
RALT_KEY = simple_key_sequence((KC.RALT,))
U_KEY = simple_key_sequence((KC.U,))
ENTER_KEY = simple_key_sequence((KC.ENTER,))
RALT_DOWN_NO_RELEASE = simple_key_sequence((KC.RALT(no_release=True),))
RALT_UP_NO_PRESS = simple_key_sequence((KC.RALT(no_press=True),))


def compile_unicode_string_sequences(string_table):
    '''
    Destructively convert ("compile") unicode strings into key sequences. This
    will, for RAM saving reasons, empty the input dictionary and trigger
    garbage collection.
    '''
    target = AttrDict()

    for k, v in string_table.items():
        target[k] = unicode_string_sequence(v)

    # now loop through and kill the input dictionary to save RAM
    for k in target.keys():
        del string_table[k]

    gc.collect()

    return target


def unicode_string_sequence(unistring):
    '''
    Allows sending things like (╯°□°)╯︵ ┻━┻ directly, without
    manual conversion to Unicode codepoints.
    '''
    return unicode_codepoint_sequence([hex(get_wide_ordinal(s))[2:] for s in unistring])


def generate_codepoint_keysym_seq(codepoint, expected_length=4):
    # To make MacOS and Windows happy, always try to send
    # sequences that are of length 4 at a minimum
    # On Linux systems, we can happily send longer strings.
    # They will almost certainly break on MacOS and Windows,
    # but this is a documentation problem more than anything.
    # Not sure how to send emojis on Mac/Windows like that,
    # though, since (for example) the Canadian flag is assembled
    # from two five-character codepoints, 1f1e8 and 1f1e6
    seq = [KC.N0 for _ in range(max(len(codepoint), expected_length))]

    for idx, codepoint_fragment in enumerate(reversed(codepoint)):
        seq[-(idx + 1)] = KC.__getattr__(codepoint_fragment.upper())

    return seq


def unicode_codepoint_sequence(codepoints):
    kc_seqs = (generate_codepoint_keysym_seq(codepoint) for codepoint in codepoints)

    kc_macros = [simple_key_sequence(kc_seq) for kc_seq in kc_seqs]

    def _unicode_sequence(key, keyboard, *args, **kwargs):
        if keyboard.unicode_mode == UnicodeMode.IBUS:
            keyboard.process_key(
                simple_key_sequence(_ibus_unicode_sequence(kc_macros, keyboard)), True
            )
        elif keyboard.unicode_mode == UnicodeMode.RALT:
            keyboard.process_key(
                simple_key_sequence(_ralt_unicode_sequence(kc_macros, keyboard)), True
            )
        elif keyboard.unicode_mode == UnicodeMode.WINC:
            keyboard.process_key(
                simple_key_sequence(_winc_unicode_sequence(kc_macros, keyboard)), True
            )

    return make_key(on_press=_unicode_sequence)


def _ralt_unicode_sequence(kc_macros, keyboard):
    for kc_macro in kc_macros:
        yield RALT_DOWN_NO_RELEASE
        yield kc_macro
        yield RALT_UP_NO_PRESS


def _ibus_unicode_sequence(kc_macros, keyboard):
    for kc_macro in kc_macros:
        yield IBUS_KEY_COMBO
        yield kc_macro
        yield ENTER_KEY


def _winc_unicode_sequence(kc_macros, keyboard):
    '''
    Send unicode sequence using WinCompose:

    http://wincompose.info/
    https://github.com/SamHocevar/wincompose
    '''
    for kc_macro in kc_macros:
        yield RALT_KEY
        yield U_KEY
        yield kc_macro
        yield ENTER_KEY