import uuid
import math
import time
import logging
import redis as engine
from zencore.errors import WrongParameterTypeError
from .types import smart_force_to_string


logger = logging.getLogger(__name__)


class RedisLock(object):

    def __init__(self, url, name=None, app_name=None, expire=None, prefix="zencore:lock:", tick=5, **kwargs):
        self.url = url
        self.connection = engine.Redis.from_url(url, **kwargs)
        self.app_name = app_name or str(uuid.uuid4())
        self.prefix = prefix
        self.expire = expire
        self.tick = tick
        if name:
            self.setup(name)

    def setup(self, name):
        self.lock_name = ":".join([self.prefix, name])
        self.signal_name = ":".join([self.prefix, name, "signal"])

    def acquire(self, blocking=True, timeout=-1):
        stime = time.clock()
        while True:
            result = self.acquire_nowait()
            if result:
                return True
            if not blocking:
                return False
            if timeout == 0:
                return False
            if timeout > 0:
                delta = math.ceil(timeout - (time.clock() - stime))
                if delta < 0:
                    return False
                if delta > self.tick:
                    delta = self.tick
            else:
                delta = self.tick
            event = self.connection.blpop(self.signal_name, timeout=delta)
            if event is None:
                return False

    def acquire_nowait(self):
        result = self.connection.setnx(self.lock_name, self.app_name)
        if result:
            if self.expire:
                self.connection.expire(self.lock_name, self.expire)
            self.connection.delete(self.signal_name)
            return True
        return False

    def release(self):
        if self.is_lock_owner():
            self.connection.delete(self.lock_name)
            self.connection.rpush(self.signal_name, 1)

    def force_clean(self):
        self.connection.delete(self.lock_name)
        self.connection.rpush(self.signal_name, 1)

    def get_current_lock_owner(self, ):
        return smart_force_to_string(self.connection.get(self.lock_name))

    def is_lock_owner(self):
        return self.get_current_lock_owner() == self.app_name


class Counter(object):

    def __init__(self, connection, namespace):
        self.connection = connection
        self.namespace = namespace

    def incr(self, name):
        key = self.make_counter_key(name)
        self.connection.incr(key)

    def get(self, name):
        key = self.make_counter_key(name)
        return int(self.connection.get(key))

    def getall(self):
        keys = self.connection.keys(self.make_counter_pattern())
        if not keys:
            return {}
        keys = [key.decode("utf-8") for key in keys]
        values = [int(value) for value in self.connection.mget(*keys)]
        return dict(zip(keys, values))

    def make_counter_key(self, name):
        return "{}:{}".format(self.namespace, name)

    def make_counter_pattern(self):
        return "{}:*".format(self.namespace)


def get_redis(config):
    """
    从配置文件获取redis对象。
    """
    if isinstance(config, engine.StrictRedis):
        return config
    if isinstance(config, str):
        return engine.Redis.from_url(config)
    if isinstance(config, dict):
        url = config.get("url")
        host = config.get("host")
        if url:
            db = config.get("db", None)
            options = config.get("options", {})
            return engine.Redis.from_url(url, db, **options)
        if host:
            return engine.Redis(**config)
    logger.error("get_redis got wrong parameter type error.")
    raise WrongParameterTypeError()

# ###########################################################################
# 重复或不推荐使用
# ###########################################################################
make_redis_instance = get_redis
