from django.core.exceptions import ImproperlyConfigured
from django.utils.html import strip_spaces_between_tags

from jinja2 import nodes
from jinja2.ext import Extension
from jinja2.exceptions import TemplateSyntaxError

from compressor.conf import settings
from compressor.exceptions import OfflineGenerationError
from compressor.utils import get_class
from compressor.templatetags.compress import OUTPUT_FILE
from compressor.cache import (cache_get, cache_set,
                              get_templatetag_cachekey,
                              get_hexdigest, get_offline_manifest, get_offline_jinja_hexdigest)


class CompressorExtension(Extension):

    tags = set(['compress'])

    @property
    def compressors(self):
        return {
            'js': settings.COMPRESS_JS_COMPRESSOR,
            'css': settings.COMPRESS_CSS_COMPRESSOR,
        }

    def parse(self, parser):
        lineno = parser.stream.next().lineno
        kindarg = parser.parse_expression()
        # Allow kind to be defined as jinja2 name node
        if isinstance(kindarg, nodes.Name):
            kindarg = nodes.Const(kindarg.name)
        args = [kindarg]
        if args[0].value not in self.compressors:
            raise TemplateSyntaxError('compress kind may be one of: %s' %
                                      (', '.join(self.compressors.keys())),
                                       lineno)
        if parser.stream.skip_if('comma'):
            modearg = parser.parse_expression()
            # Allow mode to be defined as jinja2 name node
            if isinstance(modearg, nodes.Name):
                modearg = nodes.Const(modearg.name)
                args.append(modearg)
        else:
            args.append(nodes.Const('file'))
        body = parser.parse_statements(['name:endcompress'], drop_needle=True)
        args.append(nodes.Const(unicode(body)))
        return nodes.CallBlock(self.call_method('_compress', args), [], [],
            body).set_lineno(lineno)

    def _compress(self, kind, mode, nodelist_string, caller):
        mode = mode or OUTPUT_FILE
        Compressor = get_class(self.compressors.get(kind),
            exception=ImproperlyConfigured)
        original_content = caller()
        compressor = Compressor(original_content)

        forced = getattr(self.environment, '_django_compressor_offline_forced', False)

        # See if it has been rendered offline
        cached_offline = self.render_offline(kind, mode, compressor, forced, nodelist_string)
        if cached_offline:
            return cached_offline

        # Prepare the actual compressor and check cache
        cache_key, cache_content = self.render_cached(kind, mode, compressor,
            forced)
        if cache_content is not None:
            return cache_content

        # call compressor output method and handle exceptions
        try:
            rendered_output = compressor.output(mode, forced)
            if cache_key:
                cache_set(cache_key, rendered_output)
            return rendered_output
        except Exception, e:
            if settings.DEBUG:
                raise e

        # Or don't do anything in production
        return original_content

    def render_offline(self, kind, mode, compressor, forced, nodelist_string):
        """
        If enabled and in offline mode, and not forced or in debug mode
        check the offline cache and return the result if given
        """
        if (settings.COMPRESS_ENABLED and
                settings.COMPRESS_OFFLINE) and not forced:
            key = get_offline_jinja_hexdigest(nodelist_string)
            offline_manifest = get_offline_manifest()
            if key in offline_manifest:
                return offline_manifest[key]
            else:
                raise OfflineGenerationError('You have offline compression enabled but key "%s" is missing from offline manifest. You may need to run "python manage.py compress".' % key)

    def render_cached(self, kind, mode, compressor, forced):
        """
        If enabled checks the cache for the given compressor's cache key
        and return a tuple of cache key and output
        """
        if settings.COMPRESS_ENABLED and not forced:
            cache_key = get_templatetag_cachekey(compressor, mode, kind)
            cache_content = cache_get(cache_key)
            return cache_key, cache_content
        return None, None
