import pkgutil
import importlib
import inspect
import types
import sys
from unittest import case

import pywayland.protocol
import pywayland.protocol_core

import wl4pgi

from gi.repository import GObject, GLib, Gio

protocol_classes: dict[str, list[type]] = {}
wrappers_by_name: dict[str, type] = {}

# Find all protocols and the classes within
for finder, name, is_package in pkgutil.walk_packages(pywayland.protocol.__path__, "pywayland.protocol."):
    try:
        module = importlib.import_module(name)
    except Exception:
        continue

    classes = [cls for _, cls in inspect.getmembers(module, inspect.isclass)
               if cls.__module__ == module.__name__ and issubclass(cls, pywayland.protocol_core.Interface)]

    # Each class has its own module like pywayland.protocol.protocol_name.class_name
    protocol_name = name.rsplit(".", 2)[1]

    if protocol_name not in protocol_classes:
        protocol_classes[protocol_name] = []

    if classes:
        protocol_classes[protocol_name].extend(classes)


class GWaylandProxy(GObject.GObject):
    """Base class for PyGObject proxies for Wayland."""
    __gtype_name__ = "GWaylandProxy"

    def __new__(cls, proxy):
        if hasattr(proxy, "gproxy"):
            return proxy.gproxy

        self = super().__new__(cls)
        proxy.gproxy = self

        return self

    def __init__(self, proxy):
        if hasattr(self, "internal"):
            return

        GObject.GObject.__init__(self)
        self.internal = proxy

        for event in self.events:
            self.internal.dispatcher[event] = (lambda e:
                lambda *args: self.emit(e, *(args[1:]))
            )(event)


def make_gtype_for_pywayland(interface: type):
    """Generate a PyGObject proxy class for a PyWayland interface."""
    methods = {}
    request_names = []

    # Define requests
    for request in interface.requests:
        # print("->", request.name)
        # print(request.arguments)

        def make_request_method(req):
            def request_method(self, *args):
                raw_args = [(arg.internal if isinstance(arg, GWaylandProxy) else arg) for
                            arg in args]
                raw_result = getattr(self.internal, req.name)(*raw_args)

                if isinstance(raw_result, pywayland.protocol_core.Interface):
                    return wrappers_by_name[type(raw_result).__name__](raw_result)
                if isinstance(raw_result, pywayland.protocol_core.Proxy):
                    return wrappers_by_name[type(raw_result).__name__.removesuffix("Proxy")](raw_result)

                return raw_result

            return request_method

        methods[request.name] = make_request_method(request)
        request_names.append(request.name)

    proxy_type = interface.proxy_class

    # Define events
    signals = {}
    for event in interface.events:
        # print("<-", event.name)
        # print(event.arguments)
        signals[event.name] = (
            GObject.SignalFlags.RUN_FIRST,
            None,
            tuple([GObject.TYPE_PYOBJECT] * len(event.arguments)),
        )

    methods["__gsignals__"] = signals
    methods["requests"] = request_names
    methods["events"] = list(signals.keys())
    methods["proxy_type"] = proxy_type
    methods["interface_type"] = interface

    # Define constructor
    def __init__(self, proxy):
        if not isinstance(proxy, proxy_type):
            raise TypeError(f"Proxy instantiated with incorrect type {type(proxy)}")
        GWaylandProxy.__init__(self, proxy)

    methods["__init__"] = __init__

    return type(interface.__name__, (GWaylandProxy,), methods)


class VirtualSubModule(types.ModuleType):
    """Holder for a particular Wayland protocol."""
    def __init__(self, protocol_name):
        super().__init__(f"{__name__}.{protocol_name}")
        self.protocol_name = protocol_name
        self._generate()

    def _generate(self):
        self._class_list = protocol_classes[self.protocol_name]

        for klass in self._class_list:
            setattr(self, klass.__name__, make_gtype_for_pywayland(klass))
            wrappers_by_name[klass.__name__] = getattr(self, klass.__name__)


# Inject the protocols
for protocol, classes in protocol_classes.items():
    submodule = VirtualSubModule(protocol)
    sys.modules[submodule.__name__] = submodule
    setattr(wl4pgi, protocol, submodule)