# -*- 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 customizes the AJinja2View class to meet the needs of arena

from Plugins.View.Jinja2Views import AJinja2View
from Library.UI.Views import AViewError
from Local.Configuration import Site
from Library.String.Utilities import String, Time
from Library.UI.Channels import chMetaItemsSeparator, \
    chMetaKeysValuesSeparator
from Plugins.User.Subscriptions import Right
import math, os.path, sys, time, urllib

## The session variable used to keep track of the last view displayed for the session
avSessionVariable = "/view/last"

## A list of channel fields to skip when setting up channel variables for the 
#  view
uaChannelFieldsToSkip = "channelName", "meta"
## A list of user fields to skip when setting up user variables for the view
uaUserFieldsToSkip = "userName", "userPassword", "challengePrompt", \
    "challengeReply"

## Represents an error that may occur while displaying an arena view
class AnArenaViewError(AViewError):
    pass

## A view manager customized to meet the needs of arena
class AnArenaView(AJinja2View):
    ## Constructor
    #
    #  In addition to the parameters expected by AJinja2View.__init__(), this
    #  routine expects that \p params will contain the following items:
    #  - \p Mediator -- A reference to the content mediator that is driving
    #                   the display of content.
    def __init__(Self, **params):        
        AJinja2View.__init__(Self, **params)
        
        if "Mediator" in Self._params:
            Self._Mediator = Self._params["Mediator"]
            del Self._params["Mediator"]
            
            Self._Channel = Self._Mediator.Channel()
            Self._Request = Self._Mediator.Request            
            
            # We test this here because this class is also used to display
            # errors and, in the event of an error, not all variables are
            # available.
            if Self._Mediator.ContentProvider() is not None:
                Self._Session = Self._Mediator.ContentProvider().Session()
                Self._User = Self._Mediator.ContentProvider().User()
                
            if Site.Views.cache:
                Self._params["cache"] = Self._Mediator.path("local",
                    Site.Views.path + "/cache")

    ## Initializer        
    def init(Self):
        result = AJinja2View.init(Self)
        
        if result:
            # Establish private attributes
            # The mediator driving the display of content
            Self._Mediator = None
            # The session to which the view applies
            Self._Session = None
            # The user for whom the view is being displayed
            Self._User = None
            # The channel to which the view applies
            Self._Channel = None
            # The HTTP request sent by the client
            Self._Request = None
            # The name of the view currently loaded
            Self._view = ""            
            
        return result
        
    def __del__(Self):
        if Self._User is not None:
            del Self._User
            
        if Self._Session is not None:
            del Self._Session
            
        if Self._Request is not None:
            del Self._Request
            
        if Self._Channel is not None:
            del Self._Channel
            
        if Self._Mediator is not None:
            del Self._Mediator
            
        AJinja2View.__del__(Self)
              
    ## Return a path for a template
    def path(Self, appendix=None):
        return Self._Mediator.path("client", appendix) 
              
    ## Establish script variables for the view.
    def establishScriptVariables(Self):
        Self.variables.update({
            "Script": {
              "engine": Self._Mediator.toString(),
              "name": Self._Mediator.script,
              "path": Self.path,              
              "Request": Self._Request
            },
            "Time": Time,
            "String": String
        })
        
    ## Establish site variables for the view.
    def establishSiteVariables(Self):
        Self.variables.update({
            "Site": {
                "name": Site.name,
                "administrator": Site.administrator,
                "icon": {},
                "host": Self._Request.Server.name,
                "address": Self._Request.Server.address,
                "url": Self._Mediator.url,
                "Logging": Site.Logging,
                "Sessions": Site.Sessions,
                "Users": Site.Users,
                "Groups": Site.Groups,
                "Subscriptions": Site.Subscriptions,
                "Channels": Site.Channels,
                "Views": Site.Views,
                "Posts": Site.Posts,
                "Attachments": Site.Attachments,
                "Statistics": Site.Statistics
            }
        })
        
        # If the icon paths do not appear to specify an absolute path, then
        # construct an absolute path to the icons
        Self.variables["Site"]["icon"]["small"] = \
            Self._Mediator.path("client", Site.icon["small"])
        
        Self.variables["Site"]["icon"]["large"] = \
            Self._Mediator.path("client", Site.icon["large"])
            
    ## Establish session variables.
    def establishSessionVariables(Self):
        Self.variables["Session"] = {
            "id": Self._Session.id(),
            "variables": Self._Session.Variables()
        }
        
        Self._Session[avSessionVariable] = Self.name()
        
    ## Establish view variables for the view.
    def establishViewVariables(Self):
        Self.variables.update({
            "View": {
                "name": Self.name(),
                "path": Self._Mediator.path("client", Site.Views.path) + "/" + \
                    Self._view,
                "common": {
                    "local": Self._Mediator.path("local", Site.Views.path) + \
                        "/common",
                    "url": Self._Mediator.path("client", Site.Views.path) + \
                        "/common"
                },
                "libs": {
                    "local": Self._Mediator.path("local") + "/" + \
                        Site.Views.path + "/libs",
                    "url": Self._Mediator.path("client", Site.Views.path) + \
                        "/libs"
                }
            }
        })
         
    ## Establish channel variables for the view. 
    def establishChannelVariables(Self):
        Self.variables["Channel"] = {}
        if Self._Channel is not None:
            for key, value in Self._Channel.iteritems():
                if key not in uaChannelFieldsToSkip:
                    Self.variables["Channel"][key] = value
                     
            Self.variables["Channel"].update({
                "name": Self._Channel["channelName"],
                "urlname": urllib.quote_plus(Self._Channel["channelName"]),
                "url": Self._Mediator.url(
                  channel=Self._Channel["channelName"]),                  
                "meta": Self._Channel["meta"]
            })
            
            # If no channel footer is specified, use the site footer
            if "footer" in Self._Channel["meta"]:
                Self.variables["Channel"]["footer"] = \
                    Self._Channel["meta"]["footer"]
                
            else:
                Self.variables["Channel"]["footer"] = Site.footer
                
            # If no channel language is specified, use the site language
            if "language" not in Self._Channel["meta"]:
                Self.variables["Channel"]["language"] = Site.language
                
            # If no channel icon is specified, use the site icon
            if "icon" in Self._Channel["meta"]:
                Self.variables["Channel"]["icon"] = \
                    Self._Mediator.path("client", Self._Channel["meta"]["icon"])
                
            else:
                Self.variables["Channel"]["icon"] = \
                  Self.variables["Site"]["icon"]["small"]
    
    ## Determine how many posts have been authored by the specified user
    def postsAuthored(Self, userName=None):
        if Self._Channel is None:
            return 0
        
        if userName is None:
            if Self._User is not None:
                userName = Self._User.name()
                
            else:
                return 0
                
        # Query the data source for the number of posts authored            
        return Self._Channel.Source().count(Site.Posts.category,
            criteria = [
              (("{field:author} = {}", userName)),
              (("AND {field:draft} = {}", "n"))
            ]
        )
        
    ## Determine how many posts have been edited by the specified user
    def postsEdited(Self, userName=None):
        if Self._Channel is None:
            return 0
        
        if userName is None:
            if Self._User is not None:
                userName = Self._User.name()
                
            else:
                return 0
                
        # Query the data source for the number of posts authored            
        return Self._Channel.Source().count(Site.Posts.category,
            criteria = [
              (("{field:editor} = {}", userName)),
              (("AND {field:draft} = {}", "n"))
            ]
        )
        
    ## Round the specified value to the nearest integer value
    def iround(Self, value):
        return math.trunc(round(value))
            
                  
    ## Establish variables related to the user.
    def establishUserVariables(Self):
        if Self._User is not None:                 
            Self.variables["User"] = {}
            
            for key, value in Self._User.iteritems():
                if key not in uaUserFieldsToSkip:
                    Self.variables["User"][key] = value
            
            Self.variables["User"].update({
                "name": Self._User["userName"],
                "link": {
                    "login":  {
                        "url": "",
                        "tip": "Log in",
                        "action": "login"
                    },
                    "profile": {
                        "url": Self._Mediator.url(channel="$site/user/profile",
                            title=Self._User["userName"])
                    }
                },
                "postsAuthored": Self.postsAuthored,
                "postsEdited": Self.postsEdited
            })
          
            # Establish login/logout links for the user
            Self.variables["User"]["link"]["login"]["url"] = \
                Self._Mediator.url(channel=Site.Channels.authentication, 
                    title=urllib.quote_plus(Self._Channel.name()))
              
            if Self._Session.isAnonymous() == False:
                Self.variables["User"]["link"]["login"]["url"] = \
                  Self._Mediator.url(channel=Site.Channels.authentication,
                      title=urllib.quote_plus(Self._Channel.name()), logout="y")
                Self.variables["User"]["link"]["login"]["tip"] = \
                  "Log out"
                Self.variables["User"]["link"]["login"]["action"] = \
                  "logout"
            
            # Establish a way to validate user rights from within the template
            Self.variables["Right"] = Right
            Self.variables["User"]["has"] = Self._User.has                     
                            
    ## Establish filters for the view; these filters are common across all of
    #  the views rendered by arena.
    def establishFilters(Self):
        AJinja2View.establishFilters(Self)

        Self._Environment.filters['iround'] = Self.iround
                            
    ## Establish variables for the view; these variables are common across
    #  all of the views rendered by arena.
    def establishVariables(Self):
        AJinja2View.establishVariables(Self)
        
        Self.establishScriptVariables()
        Self.establishSiteVariables()
        Self.establishViewVariables()               
        Self.establishChannelVariables()
        
        # We test these two here because this class is also used to display
        # errors and, in the event of an error, not all variables will be 
        # available.
        if Self._Session is not None:
            Self.establishSessionVariables()
        
        if Self._User is not None:
            Self.establishUserVariables()
        
    ## Fetch the specified view.
    def fetch(Self, *theseTemplates):
        templates = []
        
        for template in theseTemplates:
            templates.append(template)
        
        # Check to see if a view was requested by the client; this supercedes
        # the view specified on construction.
        if "view" in Self._Request:
            Self._params["view"] = Self._Request["view"].value
            
        # Check to see if templates were requested by the client
        if "template" in Self._Request:
            Self._params["template"] = Self._Request["template"].value
                
        if "view" in Self._params:            
            # Add the view path for the site to the search path
            Self._paths.append(Self._Mediator.path("local", Site.Views.path) + \
                "/" + Self._params["view"])
                
            # Add the common view path to the search path
            Self._paths.append(Self._Mediator.path("local", Site.Views.path))
                
            # If a specific template is requested by the channel, add it to 
            # the list of templates
            if "template" in Self._params:
                templates.append(Self._params["template"])
            
        AJinja2View.fetch(Self, *templates)
        
    ## Render the template.
    def render(Self, content):
        Self.fetch()
        
        # Check the template extension
        templateExtension = os.path.splitext(Self._Template.filename)
        # Get just the extension part and change it to lowercase
        templateExtension = templateExtension[1].lower()
        
        # Change the output content type if the template is not an HTML 
        # document...
        if templateExtension == ".xml":
            Self._Mediator.Output().headers["Content-type"] = "text/xml"
        
        result = AJinja2View.render(Self, content)        
        
        # Ensure the user record is updated after the view is rendered
        if Self._User is not None:
            Self._User.flush()
        
        # Ensure the session is closed after the view is rendered
        if Self._Session is not None:
            Self._Session.close()
            
        return result
            
    # Properties ---------------------------------------------------------------
    ## Retrieve the session to which the view applies.
    def Session(Self):
        return Self._Session
        
    ## Retrieve the channel to which the view applies.
    def Channel(Self):
        return Self._Channel
        
    ## Retrieve the name of the view.
    def name(Self):
        if "view" in Self._params:
            return Self._params["view"]
            
        return AJinja2View.name(Self)
        
    ## Retrieve the user for whom the view was constructed
    def User(Self):
        return Self._User
