"""Extensible memoizing collections and decorators."""

from __future__ import absolute_import

from functools import update_wrapper

from . import keys
from .keys import hash_params, register_hash_function
from .cache import Cache
from .lfu import LFUCache
from .lru import LRUCache
from .rr import RRCache
from .sqlite3 import SQLite3Cache
from .ttl import TTLCache

__all__ = (
    "Cache",
    "LFUCache",
    "LRUCache",
    "RRCache",
    "SQLite3Cache",
    "TTLCache",
    "cached",
    "cachedmethod",
)

__project__ = "eidetic"
__version__ = "2018.12.2"


def cached(cache, key=hash_params, lock=None):
    """Decorator to wrap a function with a memoizing callable that saves
    results in a cache.
    """

    def decorator(func):

        if cache is None:

            def wrapper(*args, **kwargs):
                return func(*args, **kwargs)

        elif lock is None:

            def wrapper(*args, **kwargs):
                k = key(func, *args, **kwargs)
                try:
                    return cache[k]
                except KeyError:
                    pass  # key not found
                v = func(*args, **kwargs)
                try:
                    cache[k] = v
                except ValueError:
                    pass  # value too large
                return v

        else:

            def wrapper(*args, **kwargs):
                k = key(func, *args, **kwargs)
                try:
                    with lock:
                        return cache[k]
                except KeyError:
                    pass  # key not found
                v = func(*args, **kwargs)
                try:
                    with lock:
                        cache[k] = v
                except ValueError:
                    pass  # value too large
                return v

        return update_wrapper(wrapper, func)

    return decorator


def cachedmethod(cache, key=hash_params, lock=None):
    """Decorator to wrap a class or instance method with a memoizing
    callable that saves results in a cache.

    """

    def decorator(method):
        if lock is None:

            def wrapper(self, *args, **kwargs):
                c = cache(self)
                if c is None:
                    return method(self, *args, **kwargs)
                k = key(*args, **kwargs)
                try:
                    return c[k]
                except KeyError:
                    pass  # key not found
                v = method(self, *args, **kwargs)
                try:
                    c[k] = v
                except ValueError:
                    pass  # value too large
                return v

        else:

            def wrapper(self, *args, **kwargs):
                c = cache(self)
                if c is None:
                    return method(self, *args, **kwargs)
                k = key(*args, **kwargs)
                try:
                    with lock(self):
                        return c[k]
                except KeyError:
                    pass  # key not found
                v = method(self, *args, **kwargs)
                try:
                    with lock(self):
                        c[k] = v
                except ValueError:
                    pass  # value too large
                return v

        return update_wrapper(wrapper, method)

    return decorator
