import abc
import logging
import typing

import Xlib.protocol.event
import Xlib.X


class EngineAction:
    def __init__(self, target_engine_index: typing.Optional[int] = None):
        self._target_engine_index = target_engine_index

    @abc.abstractmethod
    def execute_on_window(
        self,
        overlay: "rescriptoon.Overlay",
        xkeyevent: typing.Union[
            Xlib.protocol.event.KeyPress, Xlib.protocol.event.KeyRelease
        ],
        engine_window: "Xlib.display.Window",
    ):
        raise NotImplementedError()

    def execute(
        self,
        overlay: "rescriptoon.Overlay",
        xkeyevent: typing.Union[
            Xlib.protocol.event.KeyPress, Xlib.protocol.event.KeyRelease
        ],
    ):
        if self._target_engine_index is None:
            for target_window in overlay.engine_windows:
                self.execute_on_window(overlay, xkeyevent, target_window)
        elif self._target_engine_index >= len(overlay.engine_windows):
            logging.warning("target engine index out of bounds")
        else:
            self.execute_on_window(
                overlay, xkeyevent, overlay.engine_windows[self._target_engine_index]
            )


class CenterClickAction(EngineAction):
    def __init__(self, target_engine_index: int, factor_x: float, factor_y: float):
        super().__init__(target_engine_index=target_engine_index,)
        self._button = Xlib.X.Button1
        self._factor_x = factor_x
        self._factor_y = factor_y

    def execute_on_window(
        self,
        overlay: "rescriptoon.Overlay",
        xkeyevent: typing.Union[
            Xlib.protocol.event.KeyPress, Xlib.protocol.event.KeyRelease
        ],
        engine_window: "Xlib.display.Window",
    ):
        engine_geometry = engine_window.get_geometry()
        smaller_dimension = min(engine_geometry.width, engine_geometry.height)
        attr = dict(
            window=engine_window,
            detail=self._button,
            state=xkeyevent.state,
            event_x=int(engine_geometry.width / 2 + smaller_dimension * self._factor_x),
            event_y=int(
                engine_geometry.height / 2 + smaller_dimension * self._factor_y
            ),
            # apparently root_x & root_y do not need to correspond with event_x/y.
            # attributes are still required to be set.
            root_x=0,  # xkeyevent.root_x,
            root_y=0,  # xkeyevent.root_y,
            child=xkeyevent.child,
            root=xkeyevent.root,
            time=xkeyevent.time,  # X.CurrentTime
            same_screen=xkeyevent.same_screen,
        )
        if isinstance(xkeyevent, Xlib.protocol.event.KeyPress):
            e = Xlib.protocol.event.ButtonPress(**attr)
        else:
            e = Xlib.protocol.event.ButtonRelease(**attr)
        engine_window.send_event(e)


class SelectGagAction(CenterClickAction):

    X_OFFSET = -0.286
    X_FACTOR = 0.081

    def __init__(self, target_engine_index: int, column_index: int, factor_y: float):
        super().__init__(
            target_engine_index=target_engine_index,
            factor_x=self.X_OFFSET + self.X_FACTOR * column_index,
            factor_y=factor_y,
        )


class RewriteKeyEventAction(EngineAction):
    def __init__(self, target_engine_index: typing.Optional[int] = None, keysym=None):
        super().__init__(target_engine_index=target_engine_index,)
        self._keysym = keysym

    def execute_on_window(
        self,
        overlay: "rescriptoon.Overlay",
        xkeyevent: typing.Union[
            Xlib.protocol.event.KeyPress, Xlib.protocol.event.KeyRelease
        ],
        engine_window: "Xlib.display.Window",
    ):
        engine_window.send_event(
            type(xkeyevent)(
                window=engine_window,
                detail=overlay.xdisplay.keysym_to_keycode(self._keysym)
                if self._keysym
                else xkeyevent.detail,
                state=xkeyevent.state,
                root_x=xkeyevent.root_x,
                root_y=xkeyevent.root_y,
                event_x=xkeyevent.event_x,
                event_y=xkeyevent.event_y,
                child=xkeyevent.child,
                root=xkeyevent.root,
                time=xkeyevent.time,  # X.CurrentTime
                same_screen=xkeyevent.same_screen,
            )
        )


class ToggleOverlayAction:
    def execute(self, overlay: "rescriptoon.Overlay", xkeyevent: "Xlib.display.Window"):
        if isinstance(xkeyevent, Xlib.protocol.event.KeyPress):
            overlay.toggle()
