# -*- coding: UTF-8 -*-
#    This file is part of arena.
#
#    arena is free software: you can redistribute it and/or modify it
#    under the terms of the GNU General Public License as published by the
#    Free Software Foundation; either version 3 of the License or, at your
#    option, any later version.
#
#    arena is distributed in the hope that it will be useful, but
#    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABLITY
#    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
#    License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with arena.  If not, see <http://www.gnu.org/licenses/>.

## @file
#  This file defines a class that manages a jinja view, which is a jinja 
#  template that formats the output retrieved from a channel.

from Library.UI.Views import AView, AViewError
from Library.String.Utilities import String

import sys
sys.path.append('Plugins/View')
from Plugins.View.jinja2 import (Environment, FileSystemLoader, 
    FileSystemBytecodeCache, TemplateError, TemplateNotFound, 
    TemplateSyntaxError)
  
## Events generated by AJinja2View
j2vEvents = {
    ## Before variables are established for the view
    "establishVariables": None,
    ## Before filters are established for the view
    "establishFilters": None
}

## The default templates used when retrieving a view
vDefaultTemplates = ("index.tpl", "index.tpl.html", "index.tpl.htm", 
    "index.tpl.xml")
    
## The error message used when failing to find the templates requested
vErrorMissingTemplates = "none of the templates specified (\"{}\") were found "\
    "in any of these paths: \"{}\""
## The error message used when failing to retrieve a view
vErrorRetrievingView = "{} in any of these paths: {}"
## The error message used when a syntax error occurs within a template
vErrorSyntax = "a syntax error occurred on line {} in \"{}\": {}"

## The format used to name bytecode cache files
j2vCacheFilenameFormat = "_%s.cache"

## This class represents a view, which is a jinja template that formats
#  the output retrieved from a channel.
class AJinja2View(AView):
    ## Construct a new instance of the view.
    #
    #  In addition to the parameters expected by AView.__init__(), this routine
    #  will accept the following items in \p params:
    #  - \p paths -- If provided, this is a list of template search paths,
    #                which will be used when the view is fetched.
    #  - \p cache -- If specified, and if the value does not evaluate to an
    #                empty string, then templates will be cached to the
    #                specified directory.  This can potentially improve page
    #                load times.
    def __init__(Self, **params):
        AView.__init__(Self, **params)
        
        if "paths" in Self._params:
            if len(Self._params["paths"]) > 0:
                for path in Self._params["paths"]:
                    Self._paths.append(path)
                    
            del Self._params["paths"]

    ## Initializer
    def init(Self):
        result = AView.init(Self)

        if result:
            # Establish private attributes
            # The template search paths used by the view
            Self._paths = ["."]
            # The template environment
            Self._Environment = None
            # The master template being used
            Self._Template = None
            # The cache being used
            Self._Cache = None
            
            # Update the events generated by the view
            Self._events.update(j2vEvents)

        return result

    ## Destructor
    def __del__(Self):
        del Self._Template
        del Self._Environment

        AView.__del__(Self)

    ## Establish variables for the view.
    #
    #  This routine generates the \p establishVariables event.
    #
    #  This method should be implemented by descendant classes.
    def establishVariables(Self):
        Event = Self.generate("establishVariables")
        
        del Event

    ## Establish filters for the view.
    #
    #  This routine generates the \p establishFilters event.
    #
    #  This method should be implemented by descendant classes.
    def establishFilters(Self):
        Event = Self.generate("establishFilters")
        
        del Event

    ## Fetch the specified view from a template in the file system.
    #
    #  This routine generates the \p fetch event.
    def fetch(Self, *theseTemplates):
        Event = Self.generate("fetch", templates=theseTemplates)
        if Event.handled():
            del Event
            return
            
        del Event
        
        templates = []
        if len(theseTemplates) > 0:
            for template in theseTemplates:
                templates.append(template)
        
        if len(vDefaultTemplates) > 0:
            for template in vDefaultTemplates:
                templates.append(template)
                
        if "cache" in Self._params:
            if String.isEmpty(Self._params["cache"]) == False:
                # Build a bytecode cache
                Self._Cache = FileSystemBytecodeCache(Self._params["cache"],
                    Self.name().replace("/", "$") + j2vCacheFilenameFormat)
            
        try:                            
            Self._Environment = Environment(loader=FileSystemLoader(Self._paths),
                trim_blocks=True,
                bytecode_cache=Self._Cache)
                
            Self.establishFilters()                             
            Self._Template = Self._Environment.select_template(templates)
            Self.establishVariables()  

        except TemplateSyntaxError, E:
            Self.error(AViewError(vErrorSyntax, E.lineno, E.filename,
                E.message))
              
        except TemplateNotFound, E:
            Self.error(AViewError(vErrorMissingTemplates,
                String.implode(templates, "\", \""),
                String.implode(Self._paths, "\", \""))
            )
              
        except TemplateNotFound, E:
            Self.error(AViewError(vErrorRetrievingView,
                E.message, String.implode(Self._paths, ", ")))

    ## Render the template.
    def render(Self, content):
        result = ""
        
        Event = Self.generate("render", content=content)
        if Event.handled():
            result = Event.result()
            del Event
            return result
            
        del Event
        
        Self.variables["Content"] = content
        return Self._Template.render(Self.variables)

    # Properties ---------------------------------------------------------------
    ## Retrieve the environment specified for the view.
    def Environment(Self):
        return Self._Environment

    ## Retrieve the master template used by the view.
    def Template(Self):
        return Self._Template
