import lru
import logging

logger = logging.getLogger(__name__)

class ormcache(object):
    """ LRU cache decorator for orm methods,
    """

    def __init__(self, skiparg=2, size=8192, multi=None, timeout=None):
        self.skiparg = skiparg
        self.size = size
        self.method = None
        self.stat_miss = 0
        self.stat_hit = 0
        self.stat_err = 0

    def __call__(self,m):
        self.method = m
        def lookup(self2, cr, *args, **argv):
            r = self.lookup(self2, cr, *args, **argv)
            return r
        lookup.clear_cache = self.clear
        return lookup

    def stat(self):
        return "lookup-stats hit=%s miss=%s err=%s ratio=%.1f" % (self.stat_hit,self.stat_miss,self.stat_err, (100*float(self.stat_hit))/(self.stat_miss+self.stat_hit) )

    def lru(self, self2):
        try:
            ormcache = getattr(self2, '_ormcache')
        except AttributeError:
            ormcache = self2._ormcache = {}
        try:
            d = ormcache[self.method]
        except KeyError:
            d = ormcache[self.method] = lru.LRU(self.size)
        return d

    def lookup(self, self2, cr, *args, **argv):
        d = self.lru(self2)
        key = args[self.skiparg-2:]
        try:
           r = d[key]
           self.stat_hit += 1
           return r
        except KeyError:
           self.stat_miss += 1
           value = d[key] = self.method(self2, cr, *args)
           return value
        except TypeError:
           self.stat_err += 1
           return self.method(self2, cr, *args)

    def clear(self, self2, *args):
        """ Remove *args entry from the cache or all keys if *args is undefined 
        """
        d = self.lru(self2)
        if args:
            logger.warn("ormcache.clear arguments are deprecated and ignored "
                        "(while clearing caches on (%s).%s)",
                        self2._name, self.method.__name__)
        d.clear()
        self2.pool._any_cache_cleared = True

class ormcache_context(ormcache):
    def __init__(self, skiparg=2, size=8192, accepted_keys=()):
        super(ormcache_context,self).__init__(skiparg,size)
        self.accepted_keys = accepted_keys

    def lookup(self, self2, cr, *args, **argv):
        d = self.lru(self2)

        context = argv.get('context', {})
        ckey = filter(lambda x: x[0] in self.accepted_keys, context.items())
        ckey.sort()

        d = self.lru(self2)
        key = args[self.skiparg-2:]+tuple(ckey)
        try:
           r = d[key]
           self.stat_hit += 1
           return r
        except KeyError:
           self.stat_miss += 1
           value = d[key] = self.method(self2, cr, *args, **argv)
           return value
        except TypeError:
           self.stat_err += 1
           return self.method(self2, cr, *args, **argv)


class ormcache_multi(ormcache):
    def __init__(self, skiparg=2, size=8192, multi=3):
        super(ormcache_multi,self).__init__(skiparg,size)
        self.multi = multi - 2

    def lookup(self, self2, cr, *args, **argv):
        d = self.lru(self2)
        args = list(args)
        multi = self.multi
        ids = args[multi]
        r = {}
        miss = []

        for i in ids:
            args[multi] = i
            key = tuple(args[self.skiparg-2:])
            try:
               r[i] = d[key]
               self.stat_hit += 1
            except Exception:
               self.stat_miss += 1
               miss.append(i)

        if miss:
            args[multi] = miss
            r.update(self.method(self2, cr, *args))

        for i in miss:
            args[multi] = i
            key = tuple(args[self.skiparg-2:])
            d[key] = r[i]

        return r

class dummy_cache(object):
    """ Cache decorator replacement to actually do no caching.
    """
    def __init__(self, *l, **kw):
        pass
    def __call__(self, fn):
        fn.clear_cache = self.clear
        return fn
    def clear(self, *l, **kw):
        pass

if __name__ == '__main__':

    class A():
        @ormcache()
        def m(self,a,b):
            print  "A::m(", self,a,b
            return 1

        @ormcache_multi(multi=3)
        def n(self,cr,uid,ids):
            print  "m", self,cr,uid,ids
            return dict([(i,i) for i in ids])

    a=A()
    r=a.m(1,2)
    r=a.m(1,2)
    r=a.n("cr",1,[1,2,3,4])
    r=a.n("cr",1,[1,2])
    print r
    for i in a._ormcache:
        print a._ormcache[i].d
    a.m.clear_cache()
    a.n.clear_cache(a,1,1)
    r=a.n("cr",1,[1,2])
    print r
    r=a.n("cr",1,[1,2])

# For backward compatibility
cache = ormcache

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
