# -*- 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 AnObject with routines that allow the object to generate
#  and manage events.  This file was constructed when I decided that arena
#  needed to be more flexible; however, the plug-in system that was added to
#  the code at the same time has proved flexible enough for my needs.  This
#  code may therefore be deprecated at some point, as it presently adds overhead 
#  without providing value.

from Library.Base.Classwork import AnObject
from Library.String.Utilities import IsPrintable, String

## The format used to create a string representation of AnEvent
evStringRepresentation = "{name}:\n\t{params}"
## The format used to create a string representation of AnEventfulObject
eoStringRepresentation = "{name}:\n\t{params}\nEvents:\n\t{events}"

## The events generated by AnEventfulObject
eoEvents = {
  # Afer the object is initialized
  "init": None,
  # Before the object is deleted
  "delete": None,
  # On any error
  "error": None
}

## The events generated by ALoggingEventfulObject
leoEvents = {
  # Before an error or message is logged
  "log": None
}

## An event that can be emitted by a class instance.
class AnEvent(AnObject, IsPrintable):
    ## Constructor
    def __init__(Self, Originator, eventName, **params):
        AnObject.__init__(Self)
        
        # Store the originator of the event
        Self._Originator = Originator
        # Store the name of the event
        Self._name = eventName
        # Update any parameters included with the event
        Self._params.update(params)
        
    ## Initializer
    def init(Self):
        result = AnObject.init(Self)
        
        if result:
            # Establish private attributes
            # The originator of the event
            Self._Originator = None
            # The name of the event
            Self._name = ""
            # Parameters included when the event was generated
            Self._params = {}
            # The result of the event
            Self._result = None
            # Whether or not the event was handled
            Self._handled = False
            
        return result
        
    # IsPrintable implementation -----------------------------------------------
    ## Construct a string representation of the object.
    #    
    #  This representation uses the format provided in #eoStringRepresentation.
    def toString(Self):
        return evStringRepresentation.format(name=Self.displayName(),
            params=String.implode(Self._params, "\n\t", ": "))              
        
    # Properties ---------------------------------------------------------------
    ## Retrieve the originator of the event
    def Originator(Self):
        return Self._Originator
        
    ## Retrieve the name of the event
    def name(Self):
        return Self._name
        
    ## Set or retrieve the result of the event
    def result(Self, *results):
        if len(results) == 0:
            return Self._result
            
        if len(results) == 1:
            Self._result = results[0]
            
        else:
            Self._result = results
        
    ## Set or retrieve whether the event was handled
    def handled(Self, *handles):
        if len(handles) == 0:
            return Self._handled
            
        else:
            Self._handled = handles[0]
        
    ## Retrieve the value of the named event property
    def __getitem__(Self, key):
        if key in Self._params:
            return Self._params
            
        return None
        
    ## Set the value of the named event property
    def __setitem__(Self, key, value):
        Self._params[key] = value

## An object that can generate events.
#
#  When parsing events, this class assumes that the event is named using
#  camel case (i.e., "eventName").  When specifying event handlers as part
#  of the list of parameters passed to the constructor, this class assumes
#  that the event names are prefixed with "on" and the remainder of the event
#  name is given in camel case (i.e., "onEventName").  These assumptions are
#  designed to make code easier to read.
class AnEventfulObject(AnObject, IsPrintable):
    ## Constructor
    #
    #  The items passed in \p params are stored by the class for later use,
    #  although the base class itself does not make use of any parameters
    #  except where they indicate event handlers.
    #
    #  Handlers for the events generated by the class or its descendants may
    #  be passed as part of \p params.  The handlers are each registered with a 
    #  call to AnEventfulObject.on().  Each event registered should be prefixed
    #  with "on" (i.e., "onInit" for the "init" event); this distinguishes 
    #  event handlers from the other parameters passed in \p params.  Event
    #  handlers are removed from \p params after they have been processed.
    def __init__(Self, **params):
        AnObject.__init__(Self)
        
        # Parse the parameters passed
        Self._params = Self.parseParams(params)
        
        # Generate the "init" event
        Self.generate("init")
        
    ## Initializer
    def init(Self):
        result = AnObject.init(Self)
        
        if result:
            # Establish private attributes
            # The parameters of the object
            Self._params = None
            # The events generated by the object
            Self._events = eoEvents
            
        return result
        
    ## Destructor
    def __del__(Self):
        # Generate the "delete" event
        Self.generate("delete")
            
        AnObject.__del__(Self)    
        
    ## Parse the specified parameters, looking for named events and setting
    #  event handlers as appropriate.
    def parseParams(Self, params):
        result = {}
        
        if isinstance(params, dict):
            for key, value in params.iteritems():
                # If the parameter begins with "on"
                if key.find("on") == 0:
                    # Strip the "on" from the event name
                    eventName = key[2:]
                    # Make the first character lower-case
                    eventName = eventName[0].lower() + eventName[1:]
                    # Register the event
                    Self.on(eventName, value)
                    
                else:
                    result[key] = value
                    
        return result
        
    ## Generate the named event
    def generate(Self, eventName, **params):
        Event = AnEvent(Self, eventName, **params)
        
        if eventName in Self._events:
            handle = Self._events[eventName]
            
            if callable(handle):
                # Invoke the most recently registered event handler
                handle(Event)
                
        return Event
        
    ## Update the events generated by the object
    def update(Self, *events, **kwEvents):
        for eventList in events:
            Self._events.update(eventList)
            
        Self._events.update(kwEvents)
        
    ## Generate an error event.
    #
    #  If an event handler is specified for this event, it will receive an
    #  instance of AnEvent with a parameter named \p Error that refers to the
    #  exception that was raised because of the error.  The handler should set
    #  Event.handled() to \p True in order to prevent the exception from being
    #  raised; otherwise, if the event is not handled, an exception will be 
    #  raised by this routine.
    def error(Self, Error):
        if Error is not None:
            # Generate the event
            Event = Self.generate("error", Error=Error)
            
            if not Event.handled():
                del Event
                raise Error
            
            del Event
            
    # IsPrintable implementation -----------------------------------------------
    ## Construct a string representation of the object.
    #    
    #  This representation uses the format provided in #eoStringRepresentation.
    def toString(Self):
        return eoStringRepresentation.format(name=Self.displayName(),
            params=String.implode(Self._params, "\n\t", ": "),
            events=String.implode(Self._events.keys(), "\n\t"))            
        
    # Properties ---------------------------------------------------------------
    ## Set the handler for the specified event.
    #
    #  The \p newHandler parameter should refer to a callable object
    #  that can receive the event object.
    #
    #  This routine returns the previous handler for the specified event.
    def on(Self, eventName, newHandler):
        previousHandler = None
        
        if (len(eventName) > 0) and (newHandler is not None):
            # See if the event is listed among those generated by the class
            if eventName in Self._events:
                previousHandler = Self._events[eventName]
                
                Self._events[eventName] = newHandler
            
        return previousHandler
        
    ## Set handlers for multiple events.
    #
    #  The parameters passed in \p events should follow the same format as those
    #  passed to the constructor for AnEventfulObject: the event to handle 
    #  should be specified by prefixing the event name with "on".
    #
    #  This method returns a dictionary object that contains the previous
    #  handler for each event.  Only those events in \ p events which are
    #  actually generated by the class will be registered; all other event names
    #  are discarded.
    def onMultiple(Self, **events):
        previousHandlers = {}
        
        if len(events) == 0:
            return previousHandlers
            
        for key, value in events.iteritems():
            if key.find("on") == 0:
                # Get the event name
                eventName = key[2:]
                # Ensure the first character of the event is lowercase
                eventName = eventName[0].lower() + eventName[1:]
                
                if eventName in Self._events:
                    # Register the handler for the event
                    previousHandlers[key] = Self._events[eventName]
                    Self._events[eventName] = value
                
        return previousHandlers
        
    ## Retrieve the handler for the specified event.
    #
    #  If \p eventName refers to an event that is not generated by the class,
    #  this routine returns \p None.
    def handlerFor(Self, eventName):
        if eventName in Self._events:
            return Self._events[eventName]
            
        return None
        
    ## Determine whether or not the object generates the named event.
    def generates(Self, eventName):
        return eventName in Self._events
        
    ## Retrieve the value of the named property
    def __getitem__(Self, key):
        if key in Self._params:
            return Self._params
            
        return None
        
    ## Set the value of the named property
    def __setitem__(Self, key, value):
        Self._params[key] = value        

## An object that generates events and which has access to a log for status and
#  debugging messages.
class ALoggingEventfulObject(AnEventfulObject):
    ## Constructor.
    #
    #  The constructor expects that \p params will contain the following items:
    #  - \p Log -- A reference to an instance of AMessageLog that will be used
    #              to log status messages and errors.
    def __init__(Self, **params):
        AnEventfulObject.__init__(Self, **params)
        
        if "Log" in Self._params:
            Self._Log = Self._params["Log"]
            del Self._params["Log"]
            
    ## Initializer
    def init(Self):
        result = AnEventfulObject.init(Self)
        
        if result:
            # Establish private attributes
            # The log used to log status messages and errors
            Self._Log = None
            
            # Update the events generated by the object
            Self._events.update(leoEvents)
            
        return result
            
    ## Generate an error event.
    #
    #  If an event handler is specified for this event, it will receive an
    #  instance of AnEvent with a parameter named \p Error that refers to the
    #  exception that was raised because of the error.  The handler should set
    #  Event.handled() to \p True in order to prevent the exception from being
    #  raised; otherwise, if the event is not handled, it will be logged to the
    #  log associated with the class, and then an exception will be raised.
    #
    #  This routine generates the \p error event.
    def error(Self, Error):
        if Error is not None:
            # Generate the event
            Event = Self.generate("error", Error=Error)
            
            if not Event.handled():
                del Event
                # Log the event
                raise Self.log(Error, False)
            
            del Event

    ## Log the specified item.
    #
    #  This routine generates the \p log event.
    def log(Self, Item, freeItem=True):
        Result = None
        
        Event = Self.generate("log", Item=Item, freeItem=freeItem)
        if Event.handled():
            Result = Event.result()
            del Event
            return Result
            
        del Event
        
        if Self._Log is not None:
            return Self._Log.log(Item, freeItem)
            
        if freeItem == False:
            return Item
            
        else:
            del Item
                        
        return None
        
    # Properties ---------------------------------------------------------------
    ## Retrieve the log used by the class
    def Log(Self):
        return Self._Log
