"""
Modified version of https://github.com/mailgun/expiringdict with max_len removed
which works around http://bugs.python.org/issue27275.

Included primarily pypi version of expiringdict is not python3 compatible

Dictionary with auto-expiring values for caching purposes.
Expiration happens on any access, object is locked during cleanup from expired
values.
>>> ExpiringDict(max_age_seconds=10)
The values stored in the following way:
{
    key1: (value1, created_time1),
    key2: (value2, created_time2)
}
NOTE: iteration over dict and also keys() do not remove expired values!

"""

import time
from collections import OrderedDict
from threading import RLock


class ExpiringDict(OrderedDict):
    def __init__(self, max_age_seconds):
        assert max_age_seconds >= 0

        OrderedDict.__init__(self)
        self.max_age = max_age_seconds
        self.lock = RLock()

    def __contains__(self, key):
        """ Return True if the dict has a key, else return False. """
        try:
            with self.lock:
                item = OrderedDict.__getitem__(self, key)
                if time.time() - item[1] < self.max_age:
                    return True
                else:
                    del self[key]
        except KeyError:
            pass
        return False

    def __getitem__(self, key, with_age=False):
        """ Return the item of the dict.
        Raises a KeyError if key is not in the map.
        """
        with self.lock:
            item = OrderedDict.__getitem__(self, key)
            item_age = time.time() - item[1]
            if item_age < self.max_age:
                if with_age:
                    return item[0], item_age
                else:
                    return item[0]
            else:
                del self[key]
                raise KeyError(key)

    def __setitem__(self, key, value):
        """ Set d[key] to value. """
        with self.lock:
            OrderedDict.__setitem__(self, key, (value, time.time()))

    def pop(self, key, default=None):
        """ Get item from the dict and remove it.
        Return default if expired or does not exist. Never raise KeyError.
        """
        with self.lock:
            try:
                item = OrderedDict.__getitem__(self, key)
                del self[key]
                return item[0]
            except KeyError:
                return default

    def ttl(self, key):
        """ Return TTL of the `key` (in seconds).
        Returns None for non-existent or expired keys.
        """
        key_value, key_age = self.get(key, with_age=True)
        if key_age:
            key_ttl = self.max_age - key_age
            if key_ttl > 0:
                return key_ttl
        return None

    def get(self, key, default=None, with_age=False):
        " Return the value for key if key is in the dictionary, else default. "
        try:
            return self.__getitem__(key, with_age)
        except KeyError:
            if with_age:
                return default, None
            else:
                return default

    def items(self):
        """ Return a copy of the dictionary's list of (key, value) pairs. """
        r = []
        for key in self:
            try:
                r.append((key, self[key]))
            except KeyError:
                pass
        return r

    def values(self):
        """ Return a copy of the dictionary's list of values.
        See the note for dict.items(). """
        r = []
        for key in self:
            try:
                r.append(self[key])
            except KeyError:
                pass
        return r