# encoding: utf-8
import logging
import os
import signal
from time import time
from functools import wraps
from inspect import isgeneratorfunction
from collections import defaultdict
from multiprocessing import RLock
from tornado.gen import Return, sleep
from tornado.ioloop import IOLoop
from tornado.locks import Lock


try:
    import cPickle as pickle
except ImportError:
    import pickle


FunctionType = type(lambda: None)
log = logging.getLogger("cache")


class Result(object):
    def __init__(self, result):
        self.__result = result
        self.__ts = time()

    @property
    def result(self):
        return self.__result

    @property
    def ts(self):
        return self.__ts


class Cache(object):
    __slots__ = ('timeout', 'ignore_self', 'oid', 'files_cache')

    CACHE_DIR = None
    CACHE = {}
    FUTURE_LOCKS = defaultdict(Lock)
    RLOCKS = defaultdict(RLock)

    def __init__(self, timeout, ignore_self=False, oid=None, files_cache=False):
        self.timeout = timeout
        self.ignore_self = ignore_self
        self.oid = oid
        self.files_cache = files_cache

    @staticmethod
    def hash_func(key):
        if isinstance(key, FunctionType):
            return ".".join((key.__module__, key.__name__))
        else:
            return str(key)

    @classmethod
    def invalidate(cls, func):
        fkey = cls.hash_func(func)

        hash_fkey = hash(fkey)
        for key in filter(lambda x: hash(x[0]) == hash_fkey, cls.CACHE):
            log.debug('INVALIDATING Cache for %r', key)
            cls.CACHE.pop(key, -1)
            cls.FUTURE_LOCKS.pop(key, -1)
            cls.RLOCKS.pop(key, -1)

    def get_cache(self, key):
        if self.files_cache:
            fname = self.get_cache_file(key)

            if not os.path.exists(fname):
                return None

            with open(fname, 'rb') as f:
                result = pickle.load(f)

            if result.ts < (time() - self.timeout):
                IOLoop.current().add_callback(os.remove, fname)
                return None

            return result

        return self.CACHE.get(key)

    def set_cache(self, key, value):
        if self.files_cache:
            fname = self.get_cache_file(key)

            with open(fname, 'wb+') as f:
                pickle.dump(value, f)

        else:
            self.CACHE[key] = value

    @classmethod
    def invalidate_all(cls, *args, **kwargs):
        log.warning("Invalidating all memory cache.")
        cls.CACHE.clear()
        cls.FUTURE_LOCKS.clear()
        cls.RLOCKS.clear()

        log.warning("Invalidating all disk cache.")
        files = filter(
            lambda x: os.path.isfile(x),
            (os.path.join(cls.CACHE_DIR, f) for f in os.listdir(cls.CACHE_DIR))
        )

        for file in files:
            try:
                os.remove(file)
            except Exception as e:
                log.exception(e)

    def __call__(self, func):
        is_generator = isgeneratorfunction(func)
        key = self.oid or self.hash_func(func)

        def get_hash(func, args, kwargs):
            return tuple(
                map(
                    hash,
                    (