import operator
from . import util

class NoValue(object):
    """Describe a missing cache value.
    
    The :attr:`.NO_VALUE` module global
    should be used.
    
    """
    @property
    def payload(self):
        return self

    if util.py3k:
        def __bool__(self):
            return False
    else:
        def __nonzero__(self):
            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):
        """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 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):
        """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 set(self, key, value):
        """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 delete(self, key):
        """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()
