from __future__ import absolute_import

from .abc import DefaultMapping


class _DefaultSize(object):

  def __getitem__(self, _):
    return 1

  def __setitem__(self, _, value):
    assert value == 1

  def pop(self, _):
    return 1


class Cache(DefaultMapping):
  """Mutable mapping to serve as a simple cache or cache base class."""

  __size = _DefaultSize()

  def __init__(self, maxsize, getsizeof=None):
    if getsizeof:
      self.getsizeof = getsizeof
    if self.getsizeof is not Cache.getsizeof:
      self.__size = dict()
    self.__data = dict()
    self.__currsize = 0
    self.__maxsize = maxsize

  def __repr__(self):
    return '%s(%r, maxsize=%r, currsize=%r)' % (
        self.__class__.__name__,
        list(self.__data.items()),
        self.__maxsize,
        self.__currsize,
    )

  def __getitem__(self, key):
    try:
      return self.__data[key]
    except KeyError:
      return self.__missing__(key)

  def __setitem__(self, key, value):
    maxsize = self.__maxsize
    size = self.getsizeof(value)
    if size > maxsize:
      raise ValueError('value too large')
    if key not in self.__data or self.__size[key] < size:
      while self.__currsize + size > maxsize:
        self.popitem()
    if key in self.__data:
      diffsize = size - self.__size[key]
    else:
      diffsize = size
    self.__data[key] = value
    self.__size[key] = size
    self.__currsize += diffsize

  def __delitem__(self, key):
    size = self.__size.pop(key)
    del self.__data[key]
    self.__currsize -= size

  def __contains__(self, key):
    return key in self.__data

  def __missing__(self, key):
    raise KeyError(key)

  def __iter__(self):
    return iter(self.__data)

  def __len__(self):
    return len(self.__data)

  @property
  def maxsize(self):
    """The maximum size of the cache."""
    return self.__maxsize

  @property
  def currsize(self):
    """The current size of the cache."""
    return self.__currsize

  @staticmethod
  def getsizeof(value):
    """Return the size of a cache element's value."""
    return 1
