"""
Proxy Backends
------------------

Provides a utility and a decorator class that allow for modifying the behavior
of different backends without altering the class itself or having to extend the
base backend.

.. versionadded:: 0.5.0  Added support for the :class:`.ProxyBackend` class.

"""

from __future__ import annotations

from typing import Mapping
from typing import Optional
from typing import Sequence

from .api import BackendFormatted
from .api import BackendSetType
from .api import CacheBackend
from .api import CacheMutex
from .api import KeyType
from .api import SerializedReturnType
from ..util.typing import Self


class ProxyBackend(CacheBackend):
    """A decorator class for altering the functionality of backends.

    Basic usage::

        from dogpile.cache import make_region
        from dogpile.cache.proxy import ProxyBackend

        class MyFirstProxy(ProxyBackend):
            def get_serialized(self, key):
                # ... custom code goes here ...
                return self.proxied.get_serialized(key)

            def get(self, key):
                # ... custom code goes here ...
                return self.proxied.get(key)

            def set(self, key, value):
                # ... custom code goes here ...
                self.proxied.set(key)

        class MySecondProxy(ProxyBackend):
            def get_serialized(self, key):
                # ... custom code goes here ...
                return self.proxied.get_serialized(key)

            def get(self, key):
                # ... custom code goes here ...
                return self.proxied.get(key)


        region = make_region().configure(
            'dogpile.cache.dbm',
            expiration_time = 3600,
            arguments = {
                "filename":"/path/to/cachefile.dbm"
            },
            wrap = [ MyFirstProxy, MySecondProxy ]
        )

    Classes that extend :class:`.ProxyBackend` can be stacked
    together.  The ``.proxied`` property will always
    point to either the concrete backend instance or
    the next proxy in the chain that a method can be
    delegated towards.

    .. versionadded:: 0.5.0

    """

    def __init__(self, *arg, **kw):
        pass

    def wrap(self, backend: CacheBackend) -> Self:
        """Take a backend as an argument and setup the self.proxied property.
        Return an object that be used as a backend by a :class:`.CacheRegion`
        object.
        """
        assert isinstance(backend, CacheBackend) or isinstance(
            backend, ProxyBackend
        )
        self.proxied = backend
        return self

    #
    # Delegate any functions that are not already overridden to
    # the proxies backend
    #
    def get(self, key: KeyType) -> BackendFormatted:
        return self.proxied.get(key)

    def set(self, key: KeyType, value: BackendSetType) -> None:
        self.proxied.set(key, value)

    def delete(self, key: KeyType) -> None:
        self.proxied.delete(key)

    def get_multi(self, keys: Sequence[KeyType]) -> Sequence[BackendFormatted]:
        return self.proxied.get_multi(keys)

    def set_multi(self, mapping: Mapping[KeyType, BackendSetType]) -> None:
        self.proxied.set_multi(mapping)

    def delete_multi(self, keys: Sequence[KeyType]) -> None:
        self.proxied.delete_multi(keys)

    def get_mutex(self, key: KeyType) -> Optional[CacheMutex]:
        return self.proxied.get_mutex(key)

    def get_serialized(self, key: KeyType) -> SerializedReturnType:
        return self.proxied.get_serialized(key)

    def get_serialized_multi(
        self, keys: Sequence[KeyType]
    ) -> Sequence[SerializedReturnType]:
        return self.proxied.get_serialized_multi(keys)

    def set_serialized(self, key: KeyType, value: bytes) -> None:
        self.proxied.set_serialized(key, value)

    def set_serialized_multi(self, mapping: Mapping[KeyType, bytes]) -> None:
        self.proxied.set_serialized_multi(mapping)
