#!/usr/bin/env python
# -*- 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 which can be used to manage sessions, such as
#  those undertaken with a remote user over HTTP.

from Library.Base.Events import ALoggingEventfulObject
from Library.String.Utilities import String, Time
from Library.Memory.Dictionaries import ADictionary

## The events generated by a session
ssEvents = {
    # Before the session is created
    "create": None,
    # Before the session is opened
    "open": None,
    # Before session data is loaded from a data source
    "load": None,
    # Before session data is flushed to a data source
    "flush": None,
    # Before the session is renewed
    "renew": None,
    # Before the session is closed
    "close": None
}

## The error message used when an attempt is made to retrieve a non-existent
#  item from a binary tree
ssErrorRetrievingItem = ("Failed to retrieve the item \"{itemname}\""
                        " from {classname}")

## This is the base class for a session instance.  It defines the methods
#  that are used by its descendants to manage session states and variables,
#  which can be sourced from a file or a database or something else entirely.
class ASession(ALoggingEventfulObject):
    ## Construct a new session instance.
    def __init__(Self, **params):
        ALoggingEventfulObject.__init__(Self, **params)
     
    ## Initializer
    def init(Self):
        result = ALoggingEventfulObject.init(Self)
        
        if result == True:
            # Establish private attributes
            # The log to use when outputting session information
            Self._Log = None
            # The session ID
            Self._id = None
            # Whether or not the session has been modified since it was opened
            Self._changed = False
            # The session start time
            Self._opened = Time.now()
            # The session end time
            Self._closed = 0.0
            # The maximum amount of time alotted to the session
            Self._maxLength = 0.0
            # The session expiration time
            Self._expiration = 0.0
            ## Session variables
            Self._Variables = ADictionary()
            # Establish public attributes
            # The function used to generate session ids
            Self.identifier = lambda: str(Time.now())
            
            # Establish the events generated by the session
            Self._events.update(ssEvents)
            
        return result

    ## Flush session information to the storage medium.
    #    
    #  This routine is meant to be overridden by descendant classes.  It is
    #  called automatically by the destructor method.  The routine should
    #  write pertinent session information to the storage medium and return
    #  a status code to indicate whether such a write was successful.  If
    #  the routine indicates failure, a warning message will be logged by
    #  the destructor. 
    #
    #  A return value of zero (0) indicates success.  Any other value is 
    #  indicative of an error.
    def flush(Self):
        result = 0
        
        Event = Self.generate("flush")
        
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result

    ## Mark the end of the session.
    #    
    #  This routine can be overridden by descendant classes.  In its base
    #  implementation, it merely records the time that the session was closed
    #  and then calls ASession.flush()
    def close(Self):
        Event = Self.generate("close")
        
        if Event.handled() == False:
            Self._closed = time.time()
            
        del Event
        
        return Self.flush()
    
    ## Destructor    
    def __del__(Self):        
        ALoggingEventfulObject.__del__(Self)
        
        # Delete session variables
        del Self._Variables        
        
    ## Create the specified session with the specified maximum length.
    #    
    #  This routine should be implemented by descendant classes.  It should
    #  create the specified session and return a status code to indicate 
    #  whether or not the operation was successful.  A status code of zero (0)
    #  indicates success.
    def create(Self, maxLength=0):
        result = 0
        
        Event = Self.generate("create")
        
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result
        
    ## Load the specified session.
    #    
    #  This routine should be implemented by descendant classes.  It should
    #  load the specified session and set the session variables.
    #    
    #  A status code should be returned to indicate whether the load was
    #  successful.  A value of zero (0) indicates success. 
    def load(Self, sid=None):
        result = 0
        
        Event = Self.generate("load")
        
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result
        
    ## Open the specified session or, if one is not specified, begin a
    #  new session. 
    #    
    #  This routine should be overridden by descendant classes, where it can
    #  load session information from a stream or data source, establish 
    #  session variables, and so forth.  It should return the session ID
    #  if the session was successfully opened, or \p None if not.        
    def open(Self, maxLength=0, sid=None):    
        result = None
        
        Event = Self.generate("open", maxLength=maxLength, sid=sid)
        if Event.handled():
            result = Event.result()
            del Event
            return result
                
        if sid is not None:
            Self._maxLength = maxLength
            return Self.load(sid)
            
        else:
            return Self.create(maxLength)
        
    ## Renew the session.
    #    
    #  This routine extends the expiration time of the session by resetting its
    #  opened and closed attributes.  The opened attribute is set to the
    #  present time and the closed attribute is reset to zero (0).
    def renew(Self):
        Event = Self.generate("renew")
        
        if Event.handled() == False:
            Self._opened = Time.now()
            Self._closed = 0.0
            Self._expiration = Self._opened + Self._maxLength
            Self._changed = True
            
        del Event
                
    # Properties ---------------------------------------------------------------
    ## Return the length of the session, in seconds.
    #    
    #  If the session has ended, this routine returns the amount of time that
    #  the session was active.  If the session has not ended, this routine
    #  calculates the amount of time that has elapsed since the session began.
    def length(Self):
        if Self._closed == 0:
            return Time.now() - Self._opened
            
        return Self._closed - Self._opened
        
    ## Return the length of the session, in seconds.
    #    
    #  This method is defined for convenience, so that the Python \p len() 
    #  function can be used on a session instance.  This routine simply returns
    #  the result of a call to ASession.length().
    def __len__(Self):
        return Self.length()
    
    ## Retrieve a reference to the message log that is being used by the session
    #  to log errors and other diagnostic information.  
    def Log(Self):
        return Self._Log
    
    ## Retrieve the session ID.   
    def id(Self):
        return Self._id
    
    ## Retrieve whether or not session information has changed.  
    def changed(Self):
        return Self._changed
    
    ## Retrieve the time, in seconds since the Unix epoch, at which the
    #  session began.
    def opened(Self):
        return Self._opened
     
    ## Retrieve the time, in seconds since the Unix epoch, at which the
    #  session ended.
    def closed(Self):
        return Self._closed
        
    ## Retrieve the time, in seconds since the Unix epoch, at which the session
    #  will expire.
    def expiration(Self):
        return Self._expiration
    
    ## Return a string representation of the date and time at which the
    #  session will expire, formatted using the specified format.    
    def expirationTimeStamp(Self, fmt="rfc2822"):
        return Time.gmtString(Self._expiration, fmt)
    
    ## Retrieve the maximum number of seconds alotted to the session. 
    def maxLength(Self):
        return Self._maxLength
        
    ## Determine whether or not the session has expired
    def hasExpired(Self):
        return Time.now() >= Self._expiration
        
    ## Retrieve the session variables.
    def Variables(Self):
         return Self._Variables
        
    ## Retrieve the specified item from the session keys.
    #    
    #  This method is defined for the sake of convenience, so that access to
    #  session variables can be programmed as though the session instance was
    #  a native Python dictionary object.
    def __getitem__(Self, key):
        if key in Self._Variables:
            return Self._Variables[key]
            
        raise KeyError(ssErrorRetrievingItem.format(itemname=key,
          classname=Self.displayName()))
          
    ## Set the specified item in the session keys.
    #    
    #  This method is defined for the sake of convenience, so that access to
    #  session variables can be programmed as though the session instance was
    #  a native Python dictionary object.
    def __setitem__(Self, key, value):
        Self._Variables[key] = value
        Self._changed = True
        
    ## Delete the specified item from the session keys.
    #    
    #  This method is defined for the sake of convenience, so that access to
    #  session variables can be programmed as though the session instance was
    #  a native Python dictionary object.
    def __delitem__(Self, key):
        if key in Self._Variables:
            del Self._Variables[key]
            
    ## Create an iterator for the keys in the session variables.
    #    
    #  This routine is provided for convenience, so that access to the items in
    #  the session can be programmed as though the session was a native Python
    #  dictionary object.
    def __iter__(Self):
        return Self._Variables.__iter__()

    ## Create an iterator for the keys in the session variables.
    #    
    #  This routine is provided for convenience, so that access to the items in
    #  the session can be programmed as though the session was a native Python
    #  dictionary object.        
    def iterkeys(Self):
        return Self._Variables.iterkeys()

    ## Create an iterator to iterate backwards over the keys in the session
    #  variables.
    #    
    #  This routine is provided for convenience, so that access to the items in
    #  the session can be programmed as though the session was a native Python
    #  dictionary object.
    def __reversed__(Self):
        return Self._Variables.__reversed__()

    ## Create an iterator for the values in the session variables.
    #    
    #  This routine is provided for convenience, so that access to the items in
    #  the session can be programmed as though the session was a native Python 
    #  dictionary object.
    def iteritems(Self):
        return Self._Variables.iteritems()
        
    ## Create an iterator the keys and values in the session variables.
    #    
    #  This routine is provided for convenience, so that access to the items in
    #  the session can be programmed as though the session was a native Python
    #  dictionary object.
    def itervalues(Self):
        return Self._Variables.itervalues()
        
    ## Determine whether or not the specified key exists in the session
    #  variables.
    #    
    #  This routine is provided for convenience, so that access to the items in
    #  the session can be programmed as though the session was a native Python 
    #  dictionary object.
    def __contains__(Self, key):
        return key in Self._Variables
    
