"""Filter for caching parts of templates.

>>> from genshi.core import Stream
>>> from genshi.template import MarkupTemplate
>>> tmpl = MarkupTemplate('''<html xmlns:py="http://genshi.edgewall.org/"
...       xmlns:cache="http://genshi.edgewall.org/cache">
...   <ul cache:key="list">
...    <li py:for="item in items">Item ${item}</li>
...   </ul>
... </html>''')
>>> cache = Cache()
>>> tmpl.filters.insert(0, cache.retrieve)
>>> print tmpl.generate(items=range(3)) | cache.store
<html>
  <ul>
   <li>Item 0</li><li>Item 1</li><li>Item 2</li>
  </ul>
</html>
>>> 'list' in cache.cache
True

:since: version 0.5
"""

from datetime import datetime, timedelta

from genshi.core import Attrs, Namespace, QName, StreamEventKind, START, END, \
                        START_NS, END_NS
from genshi.util import LRUCache

__all__ = ['Cache']
__docformat__ = 'restructuredtext en'


class Cache(object):

    NAMESPACE = Namespace('http://genshi.edgewall.org/cache')

    def __init__(self, capacity=20):
        self.cache = LRUCache(capacity)

    def retrieve(self, stream, ctxt=None):
        cache_key = self.NAMESPACE['key']
        cache_maxage = self.NAMESPACE['maxage']
        now = datetime.utcnow()
        skip = 0

        for kind, data, pos in stream:

            # skip parts that have been retrieved from the cache
            if skip:
                if kind is START:
                    skip += 1
                elif kind is END:
                    skip -= 1
                continue

            if kind is START:
                if cache_key in data[1]:
                    tag, attrs = data
                    key = attrs.get(cache_key)
                    maxage = attrs.get(cache_maxage, 9999999999)
                    entry = None
                    if key in self.cache:
                        entry = self.cache[key]
                    if not entry or entry[0] < now - timedelta(seconds=maxage):
                        if entry:
                            del self.cache[key]
                        yield kind, (tag, attrs), pos
                    else:
                        attrs -= (cache_key, cache_maxage)
                        for event in entry[1]:
                            yield event
                        skip = 1
                else:
                    yield kind, data, pos

            else:
                yield kind, data, pos

    def store(self, stream, ctxt=None):
        cache_key = self.NAMESPACE['key']
        cache_maxage = self.NAMESPACE['maxage']
        ns_prefixes = []
        now = datetime.utcnow()
        key = None
        entry = None
        depth = 0

        for kind, data, pos in stream:

            if kind is START:
                tag, attrs = data
                if depth:
                    depth += 1
                    entry.append((kind, data, pos))
                elif cache_key in data[1]:
                    key = attrs.get(cache_key)
                    attrs -= (cache_key, cache_maxage)
                    entry = [(kind, data, pos)]
                    depth = 1
                yield kind, (tag, attrs), pos

            elif kind is END and depth:
                depth -= 1
                entry.append((kind, data, pos))
                if not depth:
                    self.cache[key] = (now, entry)
                yield kind, data, pos

            elif kind is START_NS and data[1] == self.NAMESPACE:
                ns_prefixes.append(data[0])

            elif kind is END_NS and data in ns_prefixes:
                ns_prefixes.remove(data)

            else:
                if depth:
                    entry.append((kind, data, pos))
                yield kind, data, pos


