"""Template processing for TurboGears view templates.
"""

import kid
import os, imp, sys, json
from cherrypy import config
import cherrypy
import pkg_resources
from itertools import chain
from urllib import quote_plus
import re
import turbogears

import logging
log = logging.getLogger("turbogears.view")

baseTemplates = []
sitetemplate = None

variableProviders = []

compiledTemplates = {}
serializer = kid.HTMLSerializer(encoding="utf-8")

def lookupTemplate(classname, loadingSite=False):
    """Searches for a template along the Python path.
    
    Template files must end in ".kid" and be in legitimate packages.
    If the templates are precompiled to ".pyc" files, you can
    set the "kid.precompiled" global config option to have TurboGears
    just do a straight import of the template.
    
    When in development mode, the templates are automatically
    checked for changes and reloaded as necessary.
    """
    global sitetemplate
    
    if not loadingSite and not sitetemplate:
        stname = cherrypy.config.get("tg.sitetemplate", None)
        if stname:
            lookupTemplate(stname, True)
            sys.modules["sitetemplate"] = sys.modules[stname]
        else:
            sys.modules["sitetemplate"] = \
                kid.load_template("<html></html>", name="sitetemplate")
        sitetemplate = sys.modules["sitetemplate"]
    
    ct = compiledTemplates
    log.debug("Recompiling template for %s" % classname)
    divider = classname.rfind(".")
    if divider > -1:
        package = classname[0:divider]
        basename = classname[divider+1:]
    else:
        raise ValueError, "All templates must be in a package"
        
    precompiled = cherrypy.config.get("kid.precompiled", False)
    
    if cherrypy.config.get('server.environment') == 'development' or \
        not precompiled:
        tfile = pkg_resources.resource_filename(package, "%s.kid" % basename)
        if ct.has_key(classname):
            mtime = os.stat(tfile).st_mtime
            if ct[classname] != mtime:
                del sys.modules[classname]
            ct[classname] = mtime
        mod = kid.load_template(tfile, name=classname)
        setattr(sys.modules[package], basename, mod)
    else:
        mod = __import__(classname, dict(), dict(), [basename])
    tempclass = mod.Template
    tempclass.serializer = serializer
    return tempclass

def render(info, format="html", fragment=False, template=None):
    """Renders data in the desired format.
    
    @param info: the data itself
    @type info: dict
    @param format: "html" or "json"
    @type format: string
    @param fragment: passed through to tell the template if only a
                     fragment of a page is desired
    @type fragment: bool
    @param template: name of the template to use
    @type template: string
    """
    if format == "json":
        return json.write(info)
    else:
        tclass = lookupTemplate(template)
        log.debug("Applying template %s" % (tclass.__module__))
        if not info.has_key("tg_flash"):
            info["tg_flash"] = None
        t = tclass(**info)
        t.std = stdvars()
        return t.serialize(encoding="utf-8", output=format, fragment=fragment)
        

def loadBaseTemplates():
    """Loads base templates for use by other templates.
    By listing templates in turbogears.view.baseTemplates, these templates will
    automatically be loaded so that the "import" statement in a template
    will work.
    """
    log.debug("Loading base templates")
    for template in baseTemplates:
        if sys.modules.has_key(template):
            del sys.modules[template]
        lookupTemplate(template)
        
NOTGIVEN = []
        
def selector(expression):
    """If the expression is true, return the string 'selected'. Useful for
    HTML <option>s."""
    if expression:
        return "selected"
    else:
        return None
        
def checker(expression):
    """If the expression is true, return the string "checked". This is useful
    for checkbox inputs.
    """
    if expression:
        return "checked"
    else:
        return None

def ipeek(it):
    """Lets you look at the first item in an iterator. This is a good way
    to verify that the iterator actually contains something. This is useful
    for cases where you will choose not to display a list or table if there
    is no data present.
    """
    it = iter(it)
    try:
        item = it.next()
        return chain([item], it)
    except StopIteration:
        return None
        
class attrdict(dict):
    """A dictionary whose elements are also accessible as attributes.
    """
    def __getattr__(self, attribute):
        return self[attribute]
        
safariua = re.compile(r"Safari/(\d+)")
    
class UserAgent:
    """Representation of the user's browser.
    
    Provides information about the type of browser, browser version,
    etc. This currently contains only the information needed for work
    thus far. (msie, firefox, safari browser types, plus safari version
    info.)
    """
    def __init__(self, useragent=None):
        self.majorVersion = None
        self.minorVersion = None
        if not useragent:
            useragent = "unknown"
        if useragent.find("MSIE") > -1:
            self.browser = "msie"
        elif useragent.find("Firefox") > -1:
            self.browser = "firefox"
        else:
            isSafari = safariua.search(useragent)
            if isSafari:
                self.browser = "safari"
                build = int(isSafari.group(1))
                # this comes from:
                # http://developer.apple.com/internet/safari/uamatrix.html
                if build >= 412:
                    self.majorVersion = "2"
                    self.minorVersion = "0"
                elif build >= 312:
                    self.majorVersion = "1"
                    self.minorVersion = "3"
                elif build >= 125:
                    self.majorVersion = "1"
                    self.minorVersion = "2"
                elif build >= 85:
                    self.majorVersion = "1"
                    self.minorVersion = "0"
            elif useragent == "unknown" or useragent is None:
                self.browser = "unknown"
            else:
                self.browser = "unknown: %s" % useragent

def stdvars():
    """Creates an attrdict with variables that should be available in all
    templates. These variables are:
    
    useragent
        a UserAgent object with information about the browser
    selector
        the selector function
    checker
        the checker function
    tg_js
        the path to the JavaScript libraries
    ipeek
        the ipeek function
    quote_plus
        the urllib quote_plus function
    url
        the turbogears.url function for creating flexible URLs
        
    Additionally, you can add a callable to turbogears.view.variableProviders
    that can add more variables to this list. The callable will be
    called with the vars attrdict after these standard variables have
    been set up.
    """
    try:
        useragent = cherrypy.request.headerMap["User-Agent"]
        useragent = UserAgent(useragent)
    except KeyError:
        useragent = UserAgent()
    vars = attrdict(useragent=useragent, selector=selector, 
        tg_js="/" + turbogears.startup.webpath + "tg_js",
        ipeek=ipeek, quote_plus=quote_plus, checker=checker,
        url = turbogears.url)
    for provider in variableProviders:
        provider(vars)
    return vars
        

loadBaseTemplates()
