#!/usr/bin/env python
# coding=utf8
from ._compat import binary_type, quote_plus, text_type, unquote_plus


class StoreDecorator(object):
    """Base class for store decorators.

    The default implementation will use :func:`getattr` to pass through all
    attribute/method requests to an underlying object stored as
    :attr:`_dstore`. It will also pass through the :attr:`__getattr__` and
    :attr:`__contains__` python special methods.
    """

    def __init__(self, store):
        self._dstore = store

    def __getattr__(self, attr):
        store = object.__getattribute__(self, "_dstore")
        return getattr(store, attr)

    def __contains__(self, *args, **kwargs):
        return self._dstore.__contains__(*args, **kwargs)

    def __iter__(self, *args, **kwargs):
        return self._dstore.__iter__(*args, **kwargs)


class KeyTransformingDecorator(StoreDecorator):
    # currently undocumented (== not advertised as a feature)
    def _map_key(self, key):
        return key

    def _map_key_prefix(self, key_prefix):
        return key_prefix

    def _unmap_key(self, key):
        return key

    def _filter(self, key):
        return True

    def __contains__(self, key):
        return self._map_key(key) in self._dstore

    def __iter__(self):
        return self.iter_keys()

    def delete(self, key):
        return self._dstore.delete(self._map_key(key))

    def get(self, key, *args, **kwargs):
        return self._dstore.get(self._map_key(key), *args, **kwargs)

    def get_file(self, key, *args, **kwargs):
        return self._dstore.get_file(self._map_key(key), *args, **kwargs)

    def iter_keys(self, prefix=u""):
        return (
            self._unmap_key(k)
            for k in self._dstore.iter_keys(self._map_key_prefix(prefix))
            if self._filter(k)
        )

    def iter_prefixes(self, delimiter, prefix=u""):
        dlen = len(delimiter)
        plen = len(prefix)
        memory = set()

        for k in self.iter_keys(prefix):
            pos = k.find(delimiter, plen)
            if pos >= 0:
                k = k[: pos + dlen]

            if k not in memory:
                yield k
                memory.add(k)

    def keys(self, prefix=u""):
        """Return a list of keys currently in store, in any order

        :raises IOError: If there was an error accessing the store.
        """
        return list(self.iter_keys(prefix))

    def open(self, key):
        return self._dstore.open(self._map_key(key))

    def put(self, key, *args, **kwargs):
        return self._unmap_key(self._dstore.put(self._map_key(key), *args, **kwargs))

    def put_file(self, key, *args, **kwargs):
        return self._unmap_key(
            self._dstore.put_file(self._map_key(key), *args, **kwargs)
        )

    # support for UrlMixin
    def url_for(self, key, *args, **kwargs):
        return self._dstore.url_for(self._map_key(key), *args, **kwargs)

    # support for CopyMixin
    def copy(self, source, dest):
        return self._dstore.copy(self._map_key(source), self._map_key(dest))


class PrefixDecorator(KeyTransformingDecorator):
    """Prefixes any key with a string before passing it on the decorated
    store. Automatically strips the prefix upon key retrieval.

    :param store: The store to pass keys on to.
    :param prefix: Prefix to add.
    """

    def __init__(self, prefix, store):
        super(PrefixDecorator, self).__init__(store)
        self.prefix = prefix

    def _filter(self, key):
        return key.startswith(self.prefix)

    def _map_key(self, key):
        self._check_valid_key(key)
        return self.prefix + key

    def _map_key_prefix(self, key_prefix):
        return self.prefix + key_prefix

    def _unmap_key(self, key):
        assert key.startswith(self.prefix)

        return key[len(self.prefix) :]


class URLEncodeKeysDecorator(KeyTransformingDecorator):
    """URL-encodes keys before passing them on to the underlying store."""

    def _map_key(self, key):
        if not isinstance(key, text_type):
            raise ValueError("%r is not a unicode string" % key)
        quoted = quote_plus(key.encode("utf-8"))
        if isinstance(quoted, binary_type):
            quoted = quoted.decode("utf-8")
        return quoted

    def _map_key_prefix(self, key_prefix):
        return self._map_key(key_prefix)

    def _unmap_key(self, key):
        return unquote_plus(key)


class ReadOnlyDecorator(StoreDecorator):
    """
    A read-only view of an underlying minimalkv store

    Provides only access to the following methods/attributes of the
    underlying store: get, iter_keys, keys, open, get_file.
    It also forwards __contains__.
    Accessing any other method will raise AttributeError.

    Note that the original store for r/w can still be accessed,
    so using this class as a wrapper only provides protection
    against bugs and other kinds of unintentional writes;
    it is not meant to be a real security measure.
    """

    def __getattr__(self, attr):
        if attr in ("get", "iter_keys", "keys", "open", "get_file"):
            return super(ReadOnlyDecorator, self).__getattr__(attr)
        else:
            raise AttributeError
