import operator
from ..util.compat import py3k


class NoValue(object):
    """Describe a missing cache value.

    The :attr:`.NO_VALUE` module global
    should be used.

    """
    @property
    def payload(self):
        return self

    def __repr__(self):
        """Ensure __repr__ is a consistent value in case NoValue is used to
        fill another cache key.

        """
        return '<dogpile.cache.api.NoValue object>'

    if py3k:
        def __bool__(self):  # pragma NO COVERAGE
            return False
    else:
        def __nonzero__(self):  # pragma NO COVERAGE
            return False


NO_VALUE = NoValue()
"""Value returned from ``get()`` that describes
a  key not present."""


class CachedValue(tuple):
    """Represent a value stored in the cache.

    :class:`.CachedValue` is a two-tuple of
    ``(payload, metadata)``, where ``metadata``
    is dogpile.cache's tracking information (
    currently the creation time).  The metadata
    and tuple structure is pickleable, if
    the backend requires serialization.

    """
    payload = property(operator.itemgetter(0))
    """Named accessor for the payload."""

    metadata = property(operator.itemgetter(1))
    """Named accessor for the dogpile.cache metadata dictionary."""

    def __new__(cls, payload, metadata):
        return tuple.__new__(cls, (payload, metadata))

    def __reduce__(self):
        return CachedValue, (self.payload, self.metadata)


class CacheBackend(object):
    """Base class for backend implementations."""

    key_mangler = None
    """Key mangling function.

    May be None, or otherwise declared
    as an ordinary instance method.

    """

    def __init__(self, arguments):  # pragma NO COVERAGE
        """Construct a new :class:`.CacheBackend`.

        Subclasses should override this to
        handle the given arguments.

        :param arguments: The ``arguments`` parameter
         passed to :func:`.make_registry`.

        """
        raise NotImplementedError()

    @classmethod
    def from_config_dict(cls, config_dict, prefix):
        prefix_len = len(prefix)
        return cls(
            dict(
                (key[prefix_len:], config_dict[key])
                for key in config_dict
                if key.startswith(prefix)
            )
        )

    def has_lock_timeout(self):
        return False

    def get_mutex(self, key):
        """Return an optional mutexing object for the given key.

        This object need only provide an ``acquire()``
        and ``release()`` method.

        May return ``None``, in which case the dogpile
        lock will use a regular ``threading.Lock``
        object to mutex concurrent threads for
        value creation.   The default implementation
        returns ``None``.

        Different backends may want to provide various
        kinds of "mutex" objects, such as those which
        link to lock files, distributed mutexes,
        memcached semaphores, etc.  Whatever
        kind of system is best suited for the scope
        and behavior of the caching backend.

        A mutex that takes the key into account will
        allow multiple regenerate operations across
        keys to proceed simultaneously, while a mutex
        that does not will serialize regenerate operations
        to just one at a time across all keys in the region.
        The latter approach, or a variant that involves
        a modulus of the given key's hash value,
        can be used as a means of throttling the total
        number of value recreation operations that may
        proceed at one time.

        """
        return None

    def get(self, key):  # pragma NO COVERAGE
        """Retrieve a value from the cache.

        The returned value should be an instance of
        :class:`.CachedValue`, or ``NO_VALUE`` if
        not present.

        """
        raise NotImplementedError()

    def get_multi(self, keys):  # pragma NO COVERAGE
        """Retrieve multiple values from the cache.

        The returned value should be a list, corresponding
        to the list of keys given.

        .. versionadded:: 0.5.0

        """
        raise NotImplementedError()

    def set(self, key, value):  # pragma NO COVERAGE
        """Set a value in the cache.

        The key will be whatever was passed
        to the registry, processed by the
        "key mangling" function, if any.
        The value will always be an instance
        of :class:`.CachedValue`.

        """
        raise NotImplementedError()

    def set_multi(self, mapping):  # pragma NO COVERAGE
        """Set multiple values in the cache.

        ``mapping`` is a dict in which
        the key will be whatever was passed
        to the registry, processed by the
        "key mangling" function, if any.
        The value will always be an instance
        of :class:`.CachedValue`.

        When implementing a new :class:`.CacheBackend` or cutomizing via
        :class:`.ProxyBackend`, be aware that when this method is invoked by
        :meth:`.Region.get_or_create_multi`, the ``mapping`` values are the
        same ones returned to the upstream caller. If the subclass alters the
        values in any way, it must not do so 'in-place' on the ``mapping`` dict
        -- that will have the undesirable effect of modifying the returned
        values as well.

        .. versionadded:: 0.5.0

        """
        raise NotImplementedError()

    def delete(self, key):  # pragma NO COVERAGE
        """Delete a value from the cache.

        The key will be whatever was passed
        to the registry, processed by the
        "key mangling" function, if any.

        The behavior here should be idempotent,
        that is, can be called any number of times
        regardless of whether or not the
        key exists.
        """
        raise NotImplementedError()

    def delete_multi(self, keys):  # pragma NO COVERAGE
        """Delete multiple values from the cache.

        The key will be whatever was passed
        to the registry, processed by the
        "key mangling" function, if any.

        The behavior here should be idempotent,
        that is, can be called any number of times
        regardless of whether or not the
        key exists.

        .. versionadded:: 0.5.0

        """
        raise NotImplementedError()
