from __future__ import annotations

import traceback

import win32api
import win32con
import win32ui

from . import (  # nopycln: import # Injects fast_readline into the IDLE auto-indent extension
    IDLEenvironment,
)

HANDLER_ARGS_GUESS = 0
HANDLER_ARGS_NATIVE = 1
HANDLER_ARGS_IDLE = 2
HANDLER_ARGS_EXTENSION = 3

next_id = 5000

event_to_commands: dict[str, int] = {}  # dict of event names to IDs
command_to_events: dict[int, str] = {}  # dict of IDs to event names


def assign_command_id(event, id=0):
    global next_id
    if id == 0:
        id = event_to_commands.get(event, 0)
        if id == 0:
            id = next_id
            next_id += 1
        # Only map the ones we allocated - specified ones are assumed to have a handler
        command_to_events[id] = event
    event_to_commands[event] = id
    return id


class SendCommandHandler:
    def __init__(self, cmd):
        self.cmd = cmd

    def __call__(self, *args):
        win32ui.GetMainFrame().SendMessage(win32con.WM_COMMAND, self.cmd)


class Binding:
    def __init__(self, handler, handler_args_type):
        self.handler = handler
        self.handler_args_type = handler_args_type


class BindingsManager:
    def __init__(self, parent_view):
        self.parent_view = parent_view
        self.bindings = {}  # dict of Binding instances.
        self.keymap = {}

    def prepare_configure(self):
        self.keymap = {}

    def complete_configure(self):
        for id in command_to_events:
            self.parent_view.HookCommand(self._OnCommand, id)

    def close(self):
        self.parent_view = self.bindings = self.keymap = None

    def report_error(self, problem):
        try:
            win32ui.SetStatusText(problem, 1)
        except win32ui.error:
            # No status bar!
            print(problem)

    def update_keymap(self, keymap):
        self.keymap.update(keymap)

    def bind(self, event, handler, handler_args_type=HANDLER_ARGS_GUESS, cid=0):
        if handler is None:
            handler = SendCommandHandler(cid)
        self.bindings[event] = self._new_binding(handler, handler_args_type)
        self.bind_command(event, cid)

    def bind_command(self, event, id=0):
        "Binds an event to a Windows control/command ID"
        id = assign_command_id(event, id)
        return id

    def get_command_id(self, event):
        id = event_to_commands.get(event)
        if id is None:
            # See if we even have an event of that name!?
            if event not in self.bindings:
                return None
            id = self.bind_command(event)
        return id

    def _OnCommand(self, id, code):
        event = command_to_events.get(id)
        if event is None:
            self.report_error("No event associated with event ID %d" % id)
            return 1
        return self.fire(event)

    def _new_binding(self, event, handler_args_type):
        return Binding(event, handler_args_type)

    def _get_IDLE_handler(self, ext, handler):
        try:
            instance = self.parent_view.idle.IDLEExtension(ext)
            name = handler.replace("-", "_") + "_event"
            return getattr(instance, name)
        except (ImportError, AttributeError):
            msg = f"Can not find event '{handler}' in IDLE extension '{ext}'"
            self.report_error(msg)
            return None

    def fire(self, event, event_param=None):
        # Fire the specified event.  Result is native Pythonwin result
        # (ie, 1==pass one, 0 or None==handled)

        # First look up the event directly - if there, we are set.
        binding = self.bindings.get(event)
        if binding is None:
            # If possible, find it!
            # A native method name
            handler = getattr(self.parent_view, event + "Event", None)
            if handler is None:
                # Can't decide if I should report an error??
                self.report_error("The event name '%s' can not be found." % event)
                # Either way, just let the default handlers grab it.
                return 1
            binding = self._new_binding(handler, HANDLER_ARGS_NATIVE)
            # Cache it.
            self.bindings[event] = binding

        handler_args_type = binding.handler_args_type
        # Now actually fire it.
        if handler_args_type == HANDLER_ARGS_GUESS:
            # Can't be native, as natives are never added with "guess".
            # Must be extension or IDLE.
            if event[0] == "<":
                handler_args_type = HANDLER_ARGS_IDLE
            else:
                handler_args_type = HANDLER_ARGS_EXTENSION
        try:
            if handler_args_type == HANDLER_ARGS_EXTENSION:
                args = self.parent_view.idle, event_param
            else:
                args = (event_param,)
            rc = binding.handler(*args)
            if handler_args_type == HANDLER_ARGS_IDLE:
                # Convert to our return code.
                if rc in (None, "break"):
                    rc = 0
                else:
                    rc = 1
        except:
            message = "Firing event '%s' failed." % event
            print(message)
            traceback.print_exc()
            self.report_error(message)
            rc = 1  # Let any default handlers have a go!
        return rc

    def fire_key_event(self, msg):
        key = msg[2]
        keyState = 0
        if win32api.GetKeyState(win32con.VK_CONTROL) & 0x8000:
            keyState |= win32con.RIGHT_CTRL_PRESSED | win32con.LEFT_CTRL_PRESSED
        if win32api.GetKeyState(win32con.VK_SHIFT) & 0x8000:
            keyState |= win32con.SHIFT_PRESSED
        if win32api.GetKeyState(win32con.VK_MENU) & 0x8000:
            keyState |= win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED
        keyinfo = key, keyState
        # Special hacks for the dead-char key on non-US keyboards.
        # (XXX - which do not work :-(
        event = self.keymap.get(keyinfo)
        if event is None:
            return 1
        return self.fire(event, None)
