""":mod:`flask.ext.volatile.transaction` --- Key-level transactions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:copyright: (c) 2011 by Hong Minhee, StyleShare
:license: MIT License, see :file:`LICENSE` for more details.

"""
import time
from werkzeug.contrib.cache import BaseCache

__all__ = 'Transaction', 'Reference'

    :class:`~collections.Iterable`
       If it is used in :keyword:`for` loop, the operations inside
       :keyword:`for` block become committed atomically::

           for ref in Transaction(cache, 'cache_key'):
               value = ref()
               value = value + ['append element']
               ref(value)

       The yielding object is :class:`Reference`.

    :class:`~collections.Callable`
       If a function is passed into the transaction object,
       the operations inside the function are committed atomically::

           def block(value):
               return value + ['append element']
           t = Transaction(cache, 'cache_key')
           t(block)

       The block function takes a cached value and a return value will be
       committed.

       Of course it can be used as decorator also::

           @Transaction(cache, 'cache_key')
           def block(value):
               return value + ['append element']

    :param cache: the cache client to use
    :type cache: :class:`werkzeug.contrib.cache.BaseCache`
    :param key: the key to operate atomically
    :param version_key: the key for versioning. by default ``__ver`` suffix
                        appended to ``key``
    :type timeout: the cache timeout for the key (if not specified,
                   it uses the default timeout)

    """

    def __init__(self, cache, key, version_key=None, timeout=None):
        if not isinstance(cache, BaseCache):
            raise TypeError('cache must be a werkzeug.contrib.cache.BaseCache '
                            'object, but %r passed' % cache)
        self.cache = cache
        self.key = key
        if version_key is None:
            version_key = key + '__ver'
        self.version_key = version_key
        self.timeout = timeout

    def __iter__(self):
        i = 0
        while True:
            ref = Reference(self, i)
            yield ref
            if ref.commit():
                break
            i += 1

    def __call__(self, block):
        for ref in self:
            ref.set(block(ref.value))


class Reference(object):
    """The reference to key.  It provides atomic :meth:`get`/:meth:`set`
    operations for the key.

    There redundant ways to :meth:`get`/:meth:`set` the value:

    By property
       You can get or set the :attr:`value` property.

    By methods
       You can use :meth:`get()` and :meth:`set` methods.

    By call
       It is callable. You can get the value by calling the reference without
       any arguments and set the value by calling the reference with
       an argument of the value to set.

    :param transaction: the transaction block
    :type transaction: :class:`Transaction`
    :param tried_number: the retried number in a transaction.
                         default is 0

    .. note::

       This object is automatically made by :class:`Transaction`.
       You don't have to instantiate it directly.

    """

    def __init__(self, transaction, tried_number=0):
        if not isinstance(transaction, Transaction):
            raise TypeError('expected a flask.ext.volatile.transaction.'
                            'Transaction, but %r passed' % transaction)
        self.transaction = transaction
        self.cache = transaction.cache
        self.key = transaction.key
        self.version_key = transaction.version_key
        self.timeout = transaction.timeout
        self.version = None
        self.tried_number = tried_number

    @property
    def value(self):
        """The read/write property for the value inside the key."""
        self.version = time.time()
        self.cache.set(self.version_key, self.version, self.timeout)
        val = self.cache.get(self.key)
        if val:
            return val[1]

    @value.setter
    def value(self, value):
        self._val = value

    def get(self):
        """Gets the value inside the key.

        :returns: the value inside the key

        """
        return self.value

    def set(self, value):
        """Sets the value into the key.

        :param value: the new value to set into the key

        """
        self.value = value

    def commit(self):
        """Tries committing the operations and its result.

        :returns: ``False`` if it conflicted
        :rtype: :class:`bool`

        """
        try:
            val = self._val
        except AttributeError:
            return True
        self.cache.set(self.key, (self.version, val), self.timeout)
        check = self.cache.get(self.key)
        return check and check[0] == self.version

    def __call__(self, *args):
        if len(args) > 1:
            raise TypeError('too many arguments')
        elif args:
            self.set(args[0])
        return self.value


class Transaction(object):
    """The key-level transaction block.  It implements two interfaces: