import os.path, hashlib, subprocess, re, shutil
from django import template
from django.conf import settings
from django.core.cache import cache

register = template.Library()

js_or_css = re.compile("(.*)\.(js|css)")
is_path = re.compile("\"(.*)\"")

# Locations of things for YUI compressor
JAVA = "/usr/bin/java"
YUI_COMPRESSOR_JAR = "/path/to/yuicompressor-2.3.6/build/yuicompressor-2.3.6.jar"
MINIFY = True

class CacheableNode(template.Node):
  
  def __init__(self, rpath, path_literal=False):
    self.path_literal = path_literal
    if path_literal:
      self.rpath = self.cache_resource(rpath)
    else:
      self.rpath = template.Variable(rpath)
        
  def cache_resource(self, rpath):
    if not settings.DEBUG:
      cached_res = cache.get(rpath, False)
      if not cached_res:
        if rpath[0] == "/":
          rpath = rpath[1:]
        res_path = os.path.join(settings.DOCUMENT_ROOT, rpath)
        if os.path.exists(res_path):
            # Open the file for hashing
          fin = open(res_path, "r")
          s = fin.read()
          fin.close()
          # Update the MD5
          hash = hashlib.md5()
          hash.update(s)
          
          (path, fname) = os.path.split(res_path)
          cache_name = os.path.join(path, hash.hexdigest() + ".cache" + os.path.splitext(fname)[1])
          if not os.path.exists(cache_name):
            if js_or_css.match(fname) and MINIFY:        
              # Write out the cachable file
              minified = open(cache_name, "w")
              yuic = subprocess.Popen((JAVA, "-jar", YUI_COMPRESSOR_JAR, res_path), stdout=minified)
              rtncode = yuic.wait()
              minified.write("\n")
              minified.close()
              # If an error happened during minification, just copy the file over
              if rtncode != 0:
                print "ERROR Minifying %s" % res_path
                shutil.copyfile(res_path, cache_name)
            else:
              shutil.copyfile(res_path, cache_name)
          
          # Strip DOCUMENT_ROOT off file, leaving relative path
          cache_path = cache_name.replace(settings.DOCUMENT_ROOT, "")
          cache.set(rpath, cache_path, 3600) # Cache mapping for an hour
          if cache_path[0] != "/":
            cache_path = "/%s" % cache_path
          return cache_path
        else:
          return rpath
      # Return mapped path to cachable resource
      return cached_res
    return rpath
  
  def render(self, context):
    if self.path_literal:
      return self.rpath
    else:
      rpath = self.cache_resource(self.rpath.resolve(context))
      return rpath

@register.tag
def cachable(parser, token):
  try:
    tag_name, rpath = token.split_contents()
    if is_path.match(rpath):
      path_literal = True
    else:
      path_literal = False
    rpath = rpath.replace("\"", "")
  except ValueError:
    raise template.TemplateSyntaxError, "%r tag requires a valid path relative to settings.DOCUMENT_ROOT" % token.contents.split()[0]
  
  return CacheableNode(rpath, path_literal=path_literal)

