import enum
import typing


class Vk(enum.IntEnum):
    """Win32 virtual key code

    Ref: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes

    """

    LBUTTON = 0x01  # Left mouse button
    RBUTTON = 0x02  # Right mouse button
    CANCEL = 0x03  # Control-break processing
    MBUTTON = 0x04  # Middle mouse button (three-button mouse)
    XBUTTON1 = 0x05  # X1 mouse button
    XBUTTON2 = 0x06  # X2 mouse button
    MS_BOUND = 0x07  # Maximum number of mouse buttons supported
    #  0x07  # Undefined
    BACK = 0x08  # BACKSPACE key
    TAB = 0x09  # TAB key
    #  0x0A-0B  # Reserved
    CLEAR = 0x0C  # CLEAR key
    RETURN = 0x0D  # ENTER key
    #  0x0E-0F  # Undefined
    WIN = 0x0E  # THIS UNDEFINED, hacked for shortcut
    SHIFT = 0x10  # SHIFT key
    CONTROL = 0x11  # CTRL key
    MENU = 0x12  # ALT key
    PAUSE = 0x13  # PAUSE key
    CAPITAL = 0x14  # CAPS LOCK key
    KANA = 0x15  # IME Kana mode
    HANGUEL = 0x15  # IME Hanguel mode (maintained for compatibility; use VK_HANGUL)
    HANGUL = 0x15  # IME Hangul mode
    IME_ON = 0x16  # IME On
    JUNJA = 0x17  # IME Junja mode
    FINAL = 0x18  # IME final mode
    HANJA = 0x19  # IME Hanja mode
    KANJI = 0x19  # IME Kanji mode
    IME_OFF = 0x1A  # IME Off
    ESCAPE = 0x1B  # ESC key
    CONVERT = 0x1C  # IME convert
    NONCONVERT = 0x1D  # IME nonconvert
    ACCEPT = 0x1E  # IME accept
    MODECHANGE = 0x1F  # IME mode change request
    SPACE = 0x20  # SPACEBAR
    PRIOR = 0x21  # PAGE UP key
    NEXT = 0x22  # PAGE DOWN key
    END = 0x23  # END key
    HOME = 0x24  # HOME key
    LEFT = 0x25  # LEFT ARROW key
    UP = 0x26  # UP ARROW key
    RIGHT = 0x27  # RIGHT ARROW key
    DOWN = 0x28  # DOWN ARROW key
    SELECT = 0x29  # SELECT key
    PRINT = 0x2A  # PRINT key
    EXECUTE = 0x2B  # EXECUTE key
    SNAPSHOT = 0x2C  # PRINT SCREEN key
    INSERT = 0x2D  # INS key
    DELETE = 0x2E  # DEL key
    HELP = 0x2F  # HELP key

    # Digits, the last 4 bits of the code represent the corresponding digit

    KEY_0 = 0x30  # '0' key
    KEY_1 = 0x31  # '1' key
    KEY_2 = 0x32  # '2' key
    KEY_3 = 0x33  # '3' key
    KEY_4 = 0x34  # '4' key
    KEY_5 = 0x35  # '5' key
    KEY_6 = 0x36  # '6' key
    KEY_7 = 0x37  # '7' key
    KEY_8 = 0x38  # '8' key
    KEY_9 = 0x39  # '9' key

    #  = 0x3A to 0x40 are undefined

    # The alphabet, the code corresponds to the capitalized letter in the ASCII code

    A = 0x41  # 'A' key
    B = 0x42  # 'B' key
    C = 0x43  # 'C' key
    D = 0x44  # 'D' key
    E = 0x45  # 'E' key
    F = 0x46  # 'F' key
    G = 0x47  # 'G' key
    H = 0x48  # 'H' key
    I = 0x49  # 'I' key
    J = 0x4A  # 'J' key
    K = 0x4B  # 'K' key
    L = 0x4C  # 'L' key
    M = 0x4D  # 'M' key
    N = 0x4E  # 'N' key
    O = 0x4F  # 'O' key
    P = 0x50  # 'P' key
    Q = 0x51  # 'Q' key
    R = 0x52  # 'R' key
    S = 0x53  # 'S' key
    T = 0x54  # 'T' key
    U = 0x55  # 'U' key
    V = 0x56  # 'V' key
    W = 0x57  # 'W' key
    X = 0x58  # 'X' key
    Y = 0x59  # 'Y' key
    Z = 0x5A  # 'Z' key

    LWIN = 0x5B  # Left Windows key (Natural keyboard)
    RWIN = 0x5C  # Right Windows key (Natural keyboard)
    APPS = 0x5D  # Applications key (Natural keyboard)
    # - = 0x5E  # Reserved
    SLEEP = 0x5F  # Computer Sleep key
    NUMPAD0 = 0x60  # Numeric keypad 0 key
    NUMPAD1 = 0x61  # Numeric keypad 1 key
    NUMPAD2 = 0x62  # Numeric keypad 2 key
    NUMPAD3 = 0x63  # Numeric keypad 3 key
    NUMPAD4 = 0x64  # Numeric keypad 4 key
    NUMPAD5 = 0x65  # Numeric keypad 5 key
    NUMPAD6 = 0x66  # Numeric keypad 6 key
    NUMPAD7 = 0x67  # Numeric keypad 7 key
    NUMPAD8 = 0x68  # Numeric keypad 8 key
    NUMPAD9 = 0x69  # Numeric keypad 9 key
    MULTIPLY = 0x6A  # Multiply key
    ADD = 0x6B  # Add key
    SEPARATOR = 0x6C  # Separator key
    SUBTRACT = 0x6D  # Subtract key
    DECIMAL = 0x6E  # Decimal key
    DIVIDE = 0x6F  # Divide key
    F1 = 0x70  # F1 key
    F2 = 0x71  # F2 key
    F3 = 0x72  # F3 key
    F4 = 0x73  # F4 key
    F5 = 0x74  # F5 key
    F6 = 0x75  # F6 key
    F7 = 0x76  # F7 key
    F8 = 0x77  # F8 key
    F9 = 0x78  # F9 key
    F10 = 0x79  # F10 key
    F11 = 0x7A  # F11 key
    F12 = 0x7B  # F12 key
    F13 = 0x7C  # F13 key
    F14 = 0x7D  # F14 key
    F15 = 0x7E  # F15 key
    F16 = 0x7F  # F16 key
    F17 = 0x80  # F17 key
    F18 = 0x81  # F18 key
    F19 = 0x82  # F19 key
    F20 = 0x83  # F20 key
    F21 = 0x84  # F21 key
    F22 = 0x85  # F22 key
    F23 = 0x86  # F23 key
    F24 = 0x87  # F24 key
    # - = 0x88-8F  # Unassigned
    NUMLOCK = 0x90  # NUM LOCK key
    SCROLL = 0x91  # SCROLL LOCK key
    # 0x92-96 = OEM specific
    # - = 0x97-9F  # Unassigned
    LSHIFT = 0xA0  # Left SHIFT key
    RSHIFT = 0xA1  # Right SHIFT key
    LCONTROL = 0xA2  # Left CONTROL key
    RCONTROL = 0xA3  # Right CONTROL key
    LMENU = 0xA4  # Left ALT key
    RMENU = 0xA5  # Right ALT key
    BROWSER_BACK = 0xA6  # Browser Back key
    BROWSER_FORWARD = 0xA7  # Browser Forward key
    BROWSER_REFRESH = 0xA8  # Browser Refresh key
    BROWSER_STOP = 0xA9  # Browser Stop key
    BROWSER_SEARCH = 0xAA  # Browser Search key
    BROWSER_FAVORITES = 0xAB  # Browser Favorites key
    BROWSER_HOME = 0xAC  # Browser Start and Home key
    VOLUME_MUTE = 0xAD  # Volume Mute key
    VOLUME_DOWN = 0xAE  # Volume Down key
    VOLUME_UP = 0xAF  # Volume Up key
    MEDIA_NEXT_TRACK = 0xB0  # Next Track key
    MEDIA_PREV_TRACK = 0xB1  # Previous Track key
    MEDIA_STOP = 0xB2  # Stop Media key
    MEDIA_PLAY_PAUSE = 0xB3  # Play/Pause Media key
    LAUNCH_MAIL = 0xB4  # Start Mail key
    LAUNCH_MEDIA_SELECT = 0xB5  # Select Media key
    LAUNCH_APP1 = 0xB6  # Start Application 1 key
    LAUNCH_APP2 = 0xB7  # Start Application 2 key
    # - = 0xB8-B9  # Reserved
    OEM_1 = 0xBA  # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key
    OEM_PLUS = 0xBB  # For any country/region, the '+' key
    OEM_COMMA = 0xBC  # For any country/region, the ',' key
    OEM_MINUS = 0xBD  # For any country/region, the '-' key
    OEM_PERIOD = 0xBE  # For any country/region, the '.' key
    OEM_2 = 0xBF  # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key
    OEM_3 = 0xC0  # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key
    # - = 0xC1-D7  # Reserved
    # - = 0xD8-DA  # Unassigned
    OEM_4 = 0xDB  # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key
    OEM_5 = 0xDC  # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key
    OEM_6 = 0xDD  # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key
    OEM_7 = 0xDE  # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key
    OEM_8 = 0xDF  # Used for miscellaneous characters; it can vary by keyboard.
    # - = 0xE0  # Reserved
    # 0xE1 = OEM specific
    OEM_102 = 0xE2  # The <> keys on the US standard keyboard, or the \\| key on the non-US 102-key keyboard
    # 0xE3-E4 = OEM specific
    PROCESSKEY = 0xE5  # IME PROCESS key
    # 0xE6 = OEM specific
    PACKET = 0xE7  # Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP
    # - = 0xE8  # Unassigned
    # 0xE9-F5 = OEM specific
    ATTN = 0xF6  # Attn key
    CRSEL = 0xF7  # CrSel key
    EXSEL = 0xF8  # ExSel key
    EREOF = 0xF9  # Erase EOF key
    PLAY = 0xFA  # Play key
    ZOOM = 0xFB  # Zoom key
    NONAME = 0xFC  # Reserved
    PA1 = 0xFD  # PA1 key
    OEM_CLEAR = 0xFE  # Clear key
    KB_BOUND = 0xFF  # Clear key

    UNKNOWN = 0x00

    WHEEL_UP = 0x1000
    WHEEL_DOWN = 0x1001

    @classmethod
    def _missing_(cls, value):
        return cls.UNKNOWN


VkAliases: typing.Dict[str, Vk] = {
    "LCTRL": Vk.LCONTROL,
    "LCTL": Vk.LCONTROL,
    "LALT": Vk.LMENU,
    "LSFT": Vk.LSHIFT,
    "LSUPER": Vk.LWIN,
    "RCTRL": Vk.RCONTROL,
    "RCTL": Vk.RCONTROL,
    "RALT": Vk.RMENU,
    "RSFT": Vk.RSHIFT,
    "RSUPER": Vk.RWIN,
    "CTRL": Vk.CONTROL,
    "MENU": Vk.MENU,
    "ALT": Vk.MENU,
    "SFT": Vk.SHIFT,
    "SUPER": Vk.WIN,
    "-": Vk.OEM_MINUS,
    "=": Vk.OEM_PLUS,
    ";": Vk.OEM_1,
    "/": Vk.OEM_2,
    "`": Vk.OEM_3,
    "[": Vk.OEM_4,
    "\\": Vk.OEM_5,
    "]": Vk.OEM_6,
    "'": Vk.OEM_7,
    ",": Vk.OEM_COMMA,
    ".": Vk.OEM_PERIOD,
}


class Vka:
    LCTRL = Vk.LCONTROL
    LCTL = Vk.LCONTROL
    LALT = Vk.LMENU
    LSFT = Vk.LSHIFT
    LSUPER = Vk.LWIN
    RCTRL = Vk.RCONTROL
    RCTL = Vk.RCONTROL
    RALT = Vk.RMENU
    RSFT = Vk.RSHIFT
    RSUPER = Vk.RWIN
    CTRL = Vk.CONTROL
    MENU = Vk.MENU
    ALT = Vk.MENU
    SFT = Vk.SHIFT
    SUPER = Vk.WIN
    DASH = Vk.OEM_MINUS
    EQUAL = Vk.OEM_PLUS
    SEMICOLON = Vk.OEM_1
    SLASH = Vk.OEM_2
    BACKQUOTE = Vk.OEM_3
    LBRACKET = Vk.OEM_4
    BACKSLASH = Vk.OEM_5
    RBRACKET = Vk.OEM_6
    SINGLEQUOTE = Vk.OEM_7
    COMMA = Vk.OEM_COMMA
    PERIOD = Vk.OEM_PERIOD


Modifers = {
    Vk.LCONTROL,
    Vk.RCONTROL,
    Vk.CONTROL,
    Vk.LSHIFT,
    Vk.RSHIFT,
    Vk.SHIFT,
    Vk.LMENU,
    Vk.RMENU,
    Vk.MENU,
    Vk.LWIN,
    Vk.RWIN,
    Vk.WIN,
}


def parse_key(key: str) -> Vk:
    key_name = key.strip().upper()
    # try alias
    key = VkAliases.get(key_name)
    # try name
    if key is None:
        if key_name not in Vk.__members__:
            raise Exception(f"invalid key: {key_name}")
        return Vk[key_name]
    return key


def parse_combination(combkeys: str) -> typing.Sequence[Vk]:
    """Converts combination in plain text ("Ctrl+s") to Sequence[Vk] ([Vk.CONTROL, Vk.S])"""
    parsed = []
    if not combkeys:
        return parsed
    for key_name in combkeys.split("+"):
        parsed.append(parse_key(key_name))
    return parsed


_key_expansions = {
    Vk.CONTROL: [Vk.LCONTROL, Vk.RCONTROL],
    Vk.MENU: [Vk.LMENU, Vk.RMENU],
    Vk.SHIFT: [Vk.LSHIFT, Vk.RSHIFT],
    Vk.WIN: [Vk.LWIN, Vk.RWIN],
}


def expand_combination(
    combkeys: typing.Sequence[Vk],
    index: typing.Optional[int] = 0,
) -> typing.Iterator[typing.Sequence[Vk]]:
    """Expand `Ctrl+s` to `LCtrl+s` and `RCtrl+s`, so on and so forth"""
    key = combkeys[index]
    expansions = _key_expansions.get(key)
    if expansions:
        is_last = index + 1 == len(combkeys)
        for mk in expansions:
            new_combkeys = combkeys[:index] + [mk]
            if is_last:
                yield new_combkeys
            else:
                yield from expand_combination(
                    new_combkeys + combkeys[index + 1 :], index + 1
                )
    else:
        yield combkeys
