# coding=utf-8
from __future__ import unicode_literals, absolute_import, print_function


import redis
import json
import random
from rpc.exceptions import GaiaRPCFaultException


class CacheControl(object):

    def __init__(self, redis_connection, prefix, normal_expire, error_expire, random_renew_range, random_renew_prob):
        assert isinstance(redis_connection, redis.StrictRedis)
        self._prefix = prefix
        self._normal_expire = int(normal_expire)
        self._error_expire = int(error_expire)
        self._random_renew_range = int(random_renew_range)
        self._random_renew_prob = float(random_renew_prob)
        self._redis = redis_connection

    def call_raw(self, func, key_func, cond_func, name, args, kwargs):
        use_cache = True
        if cond_func and not cond_func(*args, **kwargs):
            use_cache = False

        if use_cache:
            key = key_func(*args, **kwargs)
            full_key = self._prefix + ':' + name + ':' + key
            stored_value_json = self._redis.get(full_key)
            if stored_value_json is not None:
                ttl = self._redis.ttl(full_key)
                renew = False
                if ttl < self._random_renew_range:
                    if random.random() < self._random_renew_prob:
                        renew = True
                if not renew:
                    return json.loads(stored_value_json)

        try:
            value = {
                'error': 0,
                'message': '',
                'data': func(*args, **kwargs),
            }
            expire = self._normal_expire
        except GaiaRPCFaultException as e:
            value = {
                'error': e.error,
                'message': e.message,
                'data': None,
            }
            expire = self._error_expire

        if use_cache:
            value_json = json.dumps(value)
            self._redis.setex(full_key, expire, value_json)

        return value

    def call(self, func, key_func, cond_func, name, args, kwargs):
        value = self.call_raw(func, key_func, cond_func, name, args, kwargs)
        if value['error'] == 0:
            return value['data']
        raise GaiaRPCFaultException(
            error=value['error'],
            message=value['message'],
            data=None,
        )

    def decorate(self, name, key_func, cond_func=None):
        def decorator(func):
            def wrapper(*args, **kwargs):
                return self.call(func, key_func, cond_func, name, args, kwargs)
            return wrapper
        return decorator

