# -*- coding:utf-8 -*-
'''
缓存模块
'''

import copy
import functools
import pickle
import random


class degrade_strategy(object):
    def __init__(self, level=0):
        self.level = level

    def __get__(self, instance, owner):
        # todo get global level
        return self.level

    def __set__(self, instance, value):
        self.level = value


class cache_wrap(object):
    level = degrade_strategy()
    def __init__(self, cache_serve, pre_fix='', expire=3600, default_level=0, step=2, key_func=None, time_range=600):
        self.cache_serve = cache_serve
        self.pre_fix = pre_fix
        self._expire = expire
        self.default_level = default_level
        self.step = step
        self.key_func = key_func or (lambda *args: ':'.join([str(a) for a in args]))
        self.time_range = time_range

    @property
    def expire(self):
        level = self.level - self.default_level
        if level >= 0:
            return self._expire * (self.step ** level)
        else:
            return 0

    @property
    def random_expire(self):
        expire = self.expire
        if expire > 0:
            if self.time_range:
                return expire + random.randint(0, self.time_range)
            return expire
        return 0

    def _set_cache(self, key, value):
        value = pickle.dumps(value)
        if self.cached:
            self.cache_serve.setex(key, self.random_expire, value)

    def _get_cache(self, key):
        _get = False
        value = self.cache_serve.get(key)
        if value:
            _get = True
            try:
                value = pickle.loads(value)
            except:
                pass
        return _get, value

    def _mset_cache(self, arg_params):
        for k, v in arg_params.items():
            if v is None:
                continue
            d_v = pickle.dumps(v)
            f_k = self._get_cache_key(k)
            self.cache_serve.setex(f_k, self.random_expire, d_v)

    def _mget_cache(self, keys):
        result = []
        if not keys:
            return result
        value_list = self.cache_serve.mget(keys)
        for v in value_list:
            if v:
                v = pickle.loads(v)
            result.append(v)
        return result

    @property
    def cached(self):
        return self.expire > 0

    def _get_cache_key(self, *args, **kwargs):
        t_args = list(copy.deepcopy(args))
        t_kwargs = copy.deepcopy(kwargs)
        key = self.key_func(*t_args, **t_kwargs)
        return ':'.join([str(self.pre_fix), key])

    def clear_cache(self, arg_list):
        for arg in arg_list:
            cache_key = self._get_cache_key(arg)
            self.cache_serve.delete(cache_key)

    def __call__(self, func):
        @functools.wraps(func)
        def wrapped_func(*args, **kwargs):
            key = self._get_cache_key(*args, **kwargs)
            if self.cached:
                _get, value = self._get_cache(key)
                if value:
                    return value
            value = func(*args)
            if self.cached and value:
                self._set_cache(key, value)
            return value

        return wrapped_func

    def batch(self, func):
        @functools.wraps(func)
        def wrapped_func(arg_list, *ar, **kwargs):
            if not (isinstance(arg_list, (list, tuple)) and self.cached):
                return func(arg_list, *ar, **kwargs)
            result = {}
            cache_keys = []

            for x in arg_list:
                cache_key = self._get_cache_key(x)
                cache_keys.append(cache_key)

            data_list = self._mget_cache(cache_keys)
            loss_cache_args = []
            for index, arg in enumerate(arg_list):
                data = data_list[index]
                if data is None:
                    loss_cache_args.append(arg)
                else:
                    result[arg] = data
            if loss_cache_args:
                loss_data = func(loss_cache_args, *ar, **kwargs)
                for arg, v in loss_data.items():
                    result[arg] = v
                self._mset_cache(loss_data)
            return result

        return wrapped_func
