# Copyright 2015 Sean Vig
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from typing import TYPE_CHECKING
from weakref import WeakKeyDictionary, WeakValueDictionary

from pywayland import ffi

from .message import Message

if TYPE_CHECKING:
    from collections.abc import Callable
    from typing import Any, ParamSpec, TypeVar

    from .argument import Argument
    from .globals import Global
    from .proxy import Proxy
    from .resource import Resource

    P = ParamSpec("P")
    R = TypeVar("R")

weakkeydict: WeakKeyDictionary[ffi.WlInterface, tuple[object, ...]] = (
    WeakKeyDictionary()
)


class InterfaceMeta(type):
    """Metaclass for Interfaces

    Initializes empty lists for events and requests for the given class.
    """

    def __init__(self, name: str, bases: tuple[Any], dct: dict[Any, Any]):
        self.events: list[Message] = []
        self.requests: list[Message] = []

        # Initialize the interface cdata
        self._ptr: ffi.WlInterface = ffi.new("struct wl_interface *")


class Interface(metaclass=InterfaceMeta):
    """Wrapper class for wl_wayland structs

    Base class for interfaces that are defined by the wayland.xml class and
    generated by the scanner.  Sub-classes should use the
    :class:`InterfaceMeta` metaclass, which will define subclass.events and
    subclass.requests, the lists of the methods on this interface.  These class
    variables are populated by the :func:`Interface.event` and
    :func:`Interface.request` decorators.
    """

    _ptr: ffi.WlInterface
    name: str
    version: int
    proxy_class: type[Proxy[Any]]
    resource_class: type[Resource[Any]]
    global_class: type[Global[Any]]
    registry: WeakValueDictionary[ffi.WlProxy, Proxy[Any]]

    @classmethod
    def event(
        cls, *arguments: Argument, version: int | None = None
    ) -> Callable[[Callable[P, R]], Callable[P, R]]:
        """Decorator for interface events

        Adds the decorated method to the list of events of the interface
        (server-side method).

        :param arguments: Arguments for the event
        :type arguments: :class:`~pywayland.protocol_core.Argument`
        :param version: Version of the event (optional)
        :type version: int or None
        """

        def wrapper(func: Callable[P, R]) -> Callable[P, R]:
            cls.events.append(Message(func, arguments, version))
            return func

        return wrapper

    @classmethod
    def request(
        cls, *arguments: Argument, version: int | None = None
    ) -> Callable[[Callable[P, R]], Callable[P, R]]:
        """Decorator for interface requests

        Adds the decorated method to the list of requests of the interface
        (client-side method).

        :param arguments: Arguments for the request
        :type arguments: :class:`~pywayland.protocol_core.Argument`
        :param version: Version of the request (optional)
        :type version: int or None
        """

        def wrapper(func: Callable[P, R]) -> Callable[P, R]:
            cls.requests.append(Message(func, arguments, version))
            return func

        return wrapper

    @classmethod
    def _gen_c(cls) -> None:
        """Creates the wl_interface C struct

        Generates the CFFI cdata for the wl_interface struct given by the
        interface.
        """
        cls.registry = WeakValueDictionary()

        name: ffi.CharCData = ffi.new("char[]", cls.name.encode())
        cls._ptr.name = name
        cls._ptr.version = cls.version

        keep_alive: list[tuple[ffi.CharCData, ffi.CharCData, ffi.WlInterface]] = []
        # Determine the number of methods to assign and assign them
        cls._ptr.method_count = len(cls.requests)
        methods_ptr: ffi.WlMessage = ffi.new("struct wl_message[]", len(cls.requests))
        cls._ptr.methods = methods_ptr

        # Iterate over the methods
        for i, message in enumerate(cls.requests):
            keep_alive.append(message.build_message_struct(methods_ptr[i]))

        cls._ptr.event_count = len(cls.events)
        events_ptr: ffi.WlMessage = ffi.new("struct wl_message[]", len(cls.events))
        cls._ptr.events = events_ptr
        # Iterate over the methods
        for i, message in enumerate(cls.events):
            keep_alive.append(message.build_message_struct(events_ptr[i]))

        weakkeydict[cls._ptr] = (name, methods_ptr, events_ptr, *tuple(keep_alive))
