import os, re
from os import path

from django.template import Library, Node, TemplateSyntaxError
from django.conf import settings

register = Library()

class JSPacker:
    """ JS compressor. This commpress a javascript content
        This class code is adapted from http://plone.org/products/resourceregistries """

    def __init__(self):
        # protect this strings:
        #  match a single quote
        #  match anything but the single quote, a backslash and a newline "[^'\\\n]"
        #  or match a null escape (\0 not followed by another digit) "\\0(?![0-9])"
        #  or match a character escape (no newline) "\\[^\n]"
        self.patterns = []
        self.protect(r"""('(?:[^'\\\n]|\\0(?![0-9])|\\x[0-9a-fA-F]{2}|\\u[0-9a-fA-F]{4}|\\[^\n])*?'|"""
                     r""""(?:[^"\\\n]|\\0(?![0-9])|\\x[0-9a-fA-F]{2}|\\u[0-9a-fA-F]{4}|\\[^\n])*?")""")

        # protect regular expressions
        self.protect(r"""\s+(\/[^\/\n\r\*](?:\\/|[^\n\r])*\/g?i?)""")
        self.protect(r"""([^\w\$\/'"*)\?:]\/[^\/\n\r\*](?:\\/|[^\n\r])*\/g?i?)""")

        # protect IE conditional compilation
        self.protect(r'(/\*@.*?(?:\*/|\n|\*/(?!\n)))', re.DOTALL)

        # remove multiline comments
        self.sub(r'/\*.*?\*/', '', re.DOTALL)

        # strip whitespace at the beginning and end of each line
        self.sub(r'^[ \t\r\f\v]*(.*?)[ \t\r\f\v]*$', r'\1', re.MULTILINE)

        # after an equal sign a function definition is ok
        self.sub(r'=\s+(?=function)', r'=')

        # whitespace before some special chars
        self.sub(r'\s+([={},&|\?:\.()<>%!/\]])', r'\1')

        # whitespace before plus chars if no other plus char before i
        self.sub(r'(?<!\+)\s+\+', '+')

        # whitespace after plus chars if no other plus char after it
        self.sub(r'\+\s+(?!\+)', '+')
        # whitespace before minus chars if no other minus char before it
        self.sub(r'(?<!-)\s+-', '-')
        # whitespace after minus chars if no other minus char after it
        self.sub(r'-\s+(?!-)', '-')
        # remove redundant semi-colons
        self.sub(r';+\s*([};])', r'\1')
        # remove any excessive whitespace left except newlines
        self.sub(r'[ \t\r\f\v]+', ' ')
        # excessive newlines
        self.sub(r'\n+', '\n')
        # first newline
        self.sub(r'^\n', '')

    def protect(self, pattern, flags=None):
        if flags is None:
            self.patterns.append((re.compile(pattern), None))
        else:
            self.patterns.append((re.compile(pattern, flags), None))

    def sub(self, pattern, replacement, flags=None):
        if flags is None:
            self.patterns.append((re.compile(pattern), replacement))
        else:
            self.patterns.append((re.compile(pattern, flags), replacement))

    def copy(self):
        result = Packer()
        result.patterns = self.patterns[:]
        return result

    def _repl(self, match):
        # store protected part
        self.replacelist.append(match.group(1))
        # return escaped index
        return "\x00%i\x00" % len(self.replacelist)

    def pack(self, input):
        # list of protected parts
        self.replacelist = []
        output = input
        for regexp, replacement in self.patterns:
            if replacement is None:
                output = regexp.sub(self._repl, output)
            else:
                # substitute
                output = regexp.sub(replacement, output)
        # restore protected parts
        replacelist = list(enumerate(self.replacelist))
        replacelist.reverse() # from back to front, so 1 doesn't break 10 etc.
        for index, replacement in replacelist:
            # we use lambda in here, so the real string is used and no escaping
            # is done on it
            before = len(output)
            regexp = re.compile('\x00%i\x00' % (index+1))
            output = regexp.sub(lambda m:replacement, output)
        # done
        return output

# singleton object.
jspacker = JSPacker()


class JSMergeNode(Node):
    def __init__(self, js_name, js_files):
        self.js_name = '%s.js' % js_name
        self.js_files = js_files
        self.merge_filename = path.join(settings.MEDIA_ROOT, self.js_name)

    def render(self, context):
        if not path.exists(self.merge_filename) or not self.is_merge_updated():
            # we merge all javascript files
            merge_file = open(self.merge_filename, 'w')
            for js in self.js_files:
                jspath = path.join(settings.MEDIA_ROOT, js)
                if not path.isfile(jspath):
                    continue
                self.merge_js(js, jspath, merge_file)
            merge_file.close()
        return self.js_tag()

    def is_merge_updated(self):
        """ compares modification time of all js with merged js """
        last_mtime = 0 # last modification time of a js file 
        for js in self.js_files:
            jspath = path.join(settings.MEDIA_ROOT, js)
            jsstat = os.stat(jspath)
            mtime = jsstat[-2]
            if last_mtime < mtime:
                last_mtime = mtime
        merge_mtime = os.stat(self.merge_filename)[-2]
        return merge_mtime > last_mtime

    def merge_js(self, jsname, jspath, fd):
        """ do merging and compressing of javascript """
        global jspacker
        jsfile = open(jspath)
        jscontent = jsfile.read()
        jscontent = jspacker.pack(jscontent)
        fd.write('/* -- %s -- */\n\n%s\n\n\n' % (jsname, jscontent))

    def js_tag(self):
        """ write js tag for merged file inclusion """
        js_url = '%s%s' % (settings.MEDIA_URL, self.js_name)
        return '<script type="text/javascript" src="%s"></script>' % js_url


def do_jsmerge(parser, token):
    """
    This will merge javascript files in only one compressed javascript.

    Usage::

        {% load jsmerge %}
        {% jsmerge jsname [jsfile1] [jsfile2] .. %} 

    Example::

        {% load jsmerge %}
        {% jsmerge jsmergefile  js/file1.js js/file2.js js/file3.js %}

    This will create (if not exists) a /media/jsmergefile.js with three files merged. The HTML output for this will be::

        <script type="text/javascript" src="/media/jsmergefile.js"></script>
    """
    tokens = token.contents.split()
    if len(tokens) < 2:
        raise TemplateSyntaxError(u"'%r' tag requires at least 1 arguments." % tokens[0])
    js_name = tokens[1]
    return JSMergeNode(js_name, tokens[2:])

register.tag('jsmerge', do_jsmerge)