# -*- 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 extends AContentMediator to customize it for use with the default
#  arena setup.

from Plugins.Application.Mediators import AContentMediator, \
    AContentMediatorError
from cgi import MiniFieldStorage    
from Library.String.Utilities import String
from Local.Configuration import Site
from Library.UI.Channels import AChannel
from Library.DataSource.DataSources import ADataSource
import sys, os.path

## Events generated by AnArenaMediator
amEvents = {
    # Before connecting to the data source
    "connectToDataSource": None
}

## The error message used when an invalid data source is provided to the 
## mediator
amInvalidDataSource = "an invalid data source of type {} was provided"
## The error message used when failing to connect to the site datasource
amDataSourceError = "failed to connect to {} because: {}"
## The error message used when no content provider can be found for a channel
amContentProviderError = "failed to load content for \"{}\" because " + \
    "no content provider could be found"
## The error message used when no view provider can be found for a channel
amViewProviderError = "failed to render content for \"{}\" because " + \
    "no view provider was specified"

## Represents an error that might occur within the arena mediator itself.
class AnArenaMediatorError(AContentMediatorError):
    pass
    
## A content mediator customized for the default arena setup.
#
#  This content mediator loads content provider information from a data source
#  specified in the local configuration file.  This information is then used to
#  present content to the end user.
class AnArenaMediator(AContentMediator):
    ## Constructor
    def __init__(Self, **params):
        AContentMediator.__init__(Self, **params)   

        # Update the events generated by the mediator
        Self._Events.update(amEvents)

        # Construct a log to use for error and debugging output
        if (Self._Log is None) and (Site.Logging.enabled):
            Self._Log = Self.openLog()

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

        if result == True:
            # Establish private attributes
            # The data source used by the mediator
            Self._DataSource = None
            # The channel displayed by the mediator
            Self._Channel = None
            
        return result

    ## Destructor
    def __del__(Self):
        AContentMediator.__del__(Self)
                
        # Destroy the channel
        if Self._Channel is not None:
            del Self._Channel
            
        # Close the database connection
        if Self._DataSource is not None:
            Self._DataSource.disconnect()
            del Self._DataSource
        
    ## Construct a log file for the mediator.
    def openLog(Self, logFileName=None):
        # If no log file name is specified, then we generate one
        if logFileName is None:
            # Get the name of the script
            logFileName = sys.argv[0]
            # Separate the file name from the path name
            logFileName = os.path.split(logFileName)
            # Separate the file name from its extension
            logFileName = os.path.splitext(logFileName[1])
            # Add the configured path to the log file name
            logFileName = Site.Logging.folder + "/" + logFileName[0] + ".log"
            
        AContentMediator.openLog(Self, logFileName)

    ## Display an error
    def displayError(Self, Err):
        #print AContentMediator.displayError(Self, Err)
        #return        
        try:
            content = (str(Err), )
        
            Self.ViewProvider(*Site.Views.Error.provider, 
                **Site.Views.Error.parameters)
            Self.Request["view"] = Site.Views.Error.parameters["view"]
            
            print AContentMediator.renderView(Self, content)
            
        except Exception, E:
            AContentMediator.displayError(Self, E)

    ## Load the specified plugin.
    #
    #  The plugin MUST be located within the plugins directory specified by
    #  the site configuration, or the attempt will fail.
    def loadPlugin(Self, pluginName, identifier=None):
        # Ensure that the plugin must be located within the plugins directory.
        while pluginName[0] == ".":
            pluginName = pluginName[1:]
            
        pluginName = Site.Plugins.path + "." + pluginName
        
        return AContentMediator.loadPlugin(Self, pluginName, identifier)    

    ## Connect to the site data source. 
    def connectToDataSource(Self):
        Event = Self.generate("connectToDataSource")
        if Event.handled():
            del Event
            return
            
        del Event
        
        # Construct the data source
        Self.DataSource(*Site.Data.provider, **Site.Data.parameters)
        
        # Attempt to connect to the data source
        try:
            if len(Site.Data.password) > 0:
                Self._DataSource.connect(password=Site.Data.password)
                
            else:
                Self._DataSource.connect()
            
        except Exception, Err:
            Self.error(AnArenaMediatorError(amDataSourceError,
                str(Self._DataSource), str(Err)))
                
    ## Open the channel that specifies the content which will be displayed to the
    #  user.
    def openChannel(Self):
        # If no channel name was specified as part of the request URI,
        # use the default channel specified by the site configuration.
        if Site.Channels.uriQueryKey not in Self.Request:
            Self.Request.query[Site.Channels.uriQueryKey] = MiniFieldStorage(
                Site.Channels.uriQueryKey, Site.Channels.default)
        
        Self._Channel = AChannel(None,
            DataSource = Self._DataSource,
            category = Site.Channels.category
        )
        
        Self._Channel.fetch(Self.Request[Site.Channels.uriQueryKey].value)
        
        # Check the channel name -- did we succeed in finding the desired 
        # channel?
        if Self._Channel.name() != Self.Request[Site.Channels.uriQueryKey].value:
            # Nope!  It is likely that this was a channel "created" by a post, 
            # so we pull the default channel and temporarily rename it.  This
            # allows posts to dynamically define the hierarchy of the site.
            Self._Channel.fetch(Site.Channels.default)
            Self._Channel["channelName"] = \
                Self.Request[Site.Channels.uriQueryKey].value
                
    ## Fetch content from the content provider specified by the current channel.
    #
    #  This method constructs a content provider based on the information
    #  provided in the current channel record.  Any parameters specified 
    #  in \p params are passed to the content provider.
    def fetchContent(Self, **params):
        # Open the channel that specifies the content to display.
        Self.openChannel()

        # Set the content provider
        params.update(Channel=Self._Channel, DataSource=Self._DataSource)
        contentProvider = String.explode(Self._Channel["provider"])
        Self.ContentProvider(*contentProvider, **params)
        
        # Fetch content
        return AContentMediator.fetchContent(Self)  
        
    ## Render the view
    def renderView(Self, content): 
        # If no view provider was specified as part of the request URI,
        # use the default view provider specified by the channel
        if Site.Views.uriProviderQueryKey not in Self.Request:
            Self.Request[Site.Views.uriProviderQueryKey] = \
                Self._Channel["meta"][Site.Views.uriProviderQueryKey]
               
        # Set the view provider
        viewProvider = \
            String.explode(Self.Request[Site.Views.uriProviderQueryKey].value)
            
        if len(viewProvider) == 0:
            Self.error(AContentMediatorError(acmViewProviderError,
                Self._Channel.name()))
                
        viewParams = {}        
        if "viewParameters" in Self._Channel["meta"]:
            viewParams.update(String.explode(
                Self._Channel["meta"]["viewParameters"], dict
            ))                
        
        Self.ViewProvider(*viewProvider, **viewParams)

        # Render the view
        return AContentMediator.renderView(Self, content)

    ## Run the application
    def run(Self):
        try:
            # Connect to the data source
            Self.connectToDataSource()
            # Fetch content
            content = Self.fetchContent()    
            # Display the view
            print Self.renderView(content)
            
        except Exception, Err:
            Self.displayError(Err)

        Self.flush()
        return 0
        
    # Properties ---------------------------------------------------------------
    ## Retrieve or set the data source used by the mediator.
    #  If \p params is empty, this method simply returns the current data
    #  source.  Alternatively, \p params may contain one of the following:
    #  - A single class instance, which should either be a reference to an 
    #    instance of ADataSource (or its descendants); 
    #  - Two strings: the first should be the name of a plugin to load, 
    #    and the second should indicate the name of the class within
    #    that plugin to use as the data source.  The class will be instantiated 
    #    and passed any keyword parameters specified by \p keywordParams.  
    def DataSource(Self, *params, **keywordParams):
        PreviousSource = Self._DataSource
        
        if len(params) > 0:
            # Set the new data source
            NewDataSource = params[0]
            
            # If two strings were passed, then attempt to load the specified
            # plugin and find the specified identifier
            if (len(params) > 1) and \
              (isinstance(NewDataSource, basestring)):
                NewDataSource = Self.loadPlugin(params[0], params[1])
                
                # If the new provider is a descendant of ADataSource,
                # then instantiate it.
                if issubclass(NewDataSource, ADataSource):
                    # Include a reference to this class when instantiating
                    keywordParams.update(Mediator=Self)
                    NewDataSource = NewDataSource(**keywordParams)
            
            Self._DataSource = NewDataSource
                                
            if Self._DataSource is None:
                Self.error(AnArenaMediatorError(amInvalidDataSource,
                    str(NewDataSource)))
            
        return PreviousSource

    ## Retrieve the channel displayed by the mediator.
    def Channel(Self):
        return Self._Channel
        
