# 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 typing import Callable, Type, Optional
from weakref import WeakKeyDictionary, WeakValueDictionary

from pywayland import ffi
from .argument import Argument
from .message import Message
from .proxy import Proxy
from .resource import Resource
from .globals import Global

weakkeydict: WeakKeyDictionary = WeakKeyDictionary()


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

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

    def __init__(self, name, bases, dct):
        self.events = []
        self.requests = []

        # Initialize the interface cdata
        self._ptr = 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.InterfaceCData"
    name: str
    version: int
    proxy_class: Type[Proxy]
    resource_class: Type[Resource]
    global_class: Type[Global]

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

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

        :param signature: Encodes the types of the arguments to the decorated
                          function.
        :type signature: `string`
        :param types: List of the types of any objects included in the argument
                      list, None if otherwise.
        :type types: `list`
        """

        def wrapper(func):
            cls.events.append(Message(func, arguments, version))
            return func

        return wrapper

    @classmethod
    def request(cls, *arguments: Argument, version: Optional[int] = None):
        """Decorator for interface requests

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

        :param signature: Encodes the types of the arguments to the decorated
                          function.
        :type signature: `string`
        :param types: List of the types of any objects included in the argument
                      list, None if otherwise.
        :type types: list
        """

        def wrapper(func):
            cls.requests.append(Message(func, arguments, version))
            return func

        return wrapper

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

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

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

        keep_alive = []
        # Determine the number of methods to assign and assign them
        cls._ptr.method_count = len(cls.requests)
        cls._ptr.methods = methods_ptr = ffi.new(
            "struct wl_message[]", len(cls.requests)
        )
        # Iterate over the methods
        for i, message in enumerate(cls.requests):
            keep_alive.extend(message.build_message_struct(methods_ptr[i]))

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

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