# -*- coding: utf-8 -*-
import collections
import six
from functools import update_wrapper

from easy_cache.core import Cached, TaggedCached, DEFAULT_TIMEOUT, META_ACCEPTED_ATTR


# noinspection PyPep8Naming
class ecached(object):
    """ Caches result of decorated callable.
        Possible use-cases are:

        @cached()
        def func(...):

        @cached('cache_key')  # cache key only
        def func(...):

        @cached('cache_key', 300)  # cache key and timeout in seconds
        def func(...):

        @cached('cache_key', 300, ('user', 'books'))  # + tags
        def func(...):

        @cached('{a}:{b}')
        def func(a, b):  # cache keys based on method parameters

        @cached(['a', 'b'])
        def func(a, b):  # cache keys based on method parameters

        @cached(callable_with_parameters)
        def func(a, b):  # cache_key = callable_with_parameters(a, b)

    """
    def __init__(self, cache_key=None, timeout=DEFAULT_TIMEOUT, tags=(), prefix=None,
                 cache_instance=None, cache_alias=None):
        if tags or prefix:
            self.cache = TaggedCached(
                function=None,
                cache_key=cache_key,
                tags=tags,
                timeout=timeout,
                prefix=prefix,
                cache_instance=cache_instance,
                cache_alias=cache_alias,
            )
        else:
            self.cache = Cached(
                function=None,
                cache_key=cache_key,
                timeout=timeout,
                cache_instance=cache_instance,
                cache_alias=cache_alias,
            )

        self._instance = None
        self._class = None
        self._func = None
        self._wrapped = False

    def __get__(self, instance, owner):
        self._instance = instance
        self._class = owner
        return self.wrapper()

    def wrapper(self):
        if not self._wrapped:
            if self._instance or self._class:
                wrapped = self._func.__get__(self._instance, self._class)

                if isinstance(self._func, staticmethod):
                    # we don't need instance or class, however we need scope
                    self.cache.scope = self._instance or self._class
                    self._instance = None
                    self._class = None
                else:
                    wrapped = six.get_method_function(wrapped)
            else:
                wrapped = self._func

            update_wrapper(self.cache, wrapped)
            self.cache.function = wrapped
            self.cache.instance = self._instance
            self.cache.klass = self._class
            self._wrapped = True

        return self.cache

    def __call__(self, func):
        self._func = func

        if isinstance(func, collections.Callable):
            return self.wrapper()

        return self

    def __repr__(self):
        return repr(self.cache)


def ecached_property(cache_key=None, timeout=DEFAULT_TIMEOUT, tags=(), prefix=None,
                     cache_instance=None, cache_alias=None):
    """ Works the same as `cached` decorator, but intended to use
        for properties, e.g.:

        class User(object):

            @cached_property('{self.id}:friends_count', 120)
            def friends_count(self):
                return <calculated friends count>

    """
    def wrapper(func):
        if tags or prefix:
            cache = TaggedCached(
                function=func,
                cache_key=cache_key,
                tags=tags,
                timeout=timeout,
                prefix=prefix,
                cache_instance=cache_instance,
                cache_alias=cache_alias,
                as_property=True,
            )
        else:
            cache = Cached(
                function=func,
                cache_key=cache_key,
                timeout=timeout,
                cache_instance=cache_instance,
                cache_alias=cache_alias,
                as_property=True,
            )

        return cache

    return wrapper


def meta_accepted(func):

    if isinstance(func, (staticmethod, classmethod)):
        _func = func.__func__
    else:
        _func = func
    setattr(_func, META_ACCEPTED_ATTR, True)

    return func