#!/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 and implements a class for logging messages and errors.

from Library.Base.Classwork import AnObject
from Library.Base.Exceptions import AnException
from Library.String.Utilities import IsPrintable, Time
from Library.IO.Streams import AStream
from Library.IO.FileStreams import AFileOutputStream
from Library.Memory.BinaryTrees import ABinaryTree
from Library.IO.Logging.Counters import ACounter

## The name of the default counter used when logging items
mDefaultCounterName = "message"

## The format used to create a string representation of AMessageWithTimeStamp
mwtsStringRepresentation = "[{gmtime} ({localtime})] {name}: {message}\n"

## The format used to create a string representation of AMessageLog
mlStringRepresentation = "Total number of: \n"

## The base class for items that can be logged (such as messages, errors,
#  hints, warnings, etc.).
class AMessage(AnException):
    ## Constructor
    def __init__(Self, errorMessage, *params):
        AnException.__init__(Self, errorMessage, *params)
        
    ## Initializer
    def init(Self):
        result = AnException.init(Self)
        
        if result == True:
            # Establish private attributes
            ## The name of the counter to increment when the item is logged
            Self._counterName = mDefaultCounterName
            
        return result
        
    # Properties ---------------------------------------------------------------
    ## Retrieve the name of the counter used by the message item.
    def counterName(Self):
        return Self._counterName
        
## A message that, when logged, will include a timestamp indicating when it was
#  logged.
class AMessageWithTimeStamp(AMessage):
    ## Constructor
    def __init__(Self, errorMessage, *params):
        AMessage.__init__(Self, errorMessage, *params)
    
    ## Initializer
    def init(Self):
        result = AMessage.init(Self)
        
        if result == True:
            # Establish private attributes
            ## The time at which the message occurred
            Self._localtime = Time.now()
            Self._gmtime = Time.gmt()
            
        return result
            
    # IsPrintable implementation -----------------------------------------------
    ## Construct a string representation of the message.  
    #    
    #  This routine overrides the behavior inherited from AnException to 
    #  include two timestamps (GMT and local time) with the message.
    def toString(Self):
        return mwtsStringRepresentation.format(
          gmtime=Time.gmtString(Self._gmtime, 'rfc2822'),
          localtime=Time.localString(Self._localtime, 'rfc2822'),
          name=Self.displayName(),
          message=Self._message)
          
    # Properties ---------------------------------------------------------------
    ## Return the local time at which the message was logged.
    def localtime(Self):
        return Self._localtime
    
    ## Return the time, in GMT, at which the message was logged.    
    def gmtime(Self):
        return Self._gmtime
  
## A message log that outputs messages to a given stream or file.      
class AMessageLog(AnObject, IsPrintable):
    ## Construct a new message log that will output messages to the specified
    #  stream or file name.
    def __init__(Self, ThisStream):
        AnObject.__init__(Self)
        
        if isinstance(ThisStream, AStream):
            Self._Output = ThisStream
        
        elif isinstance(ThisStream, basestring):
            Self._Output = AFileOutputStream(ThisStream)
            Self.ownsOutput = True
        
    ## Initializer
    def init(Self):
        result = AnObject.init(Self)
        
        if result == True:
            # Establish private attributes
            ## The list of counters used by the log
            Self._Counters = ABinaryTree()
            ## Add the default counter
            Self._Counters.insert(ACounter(mDefaultCounterName))
            ## The stream used by the log
            Self._Output = None
            # Establish public attributes
            ## Whether or not the log owns its output stream
            Self.ownsOutput = False
            
        return result
        
    ## Destructor
    def __del__(Self):
        if (Self._Output is not None) and (Self.ownsOutput):
            del Self._Output
            
        del Self._Counters
        
    ## Add the named counter to the list of counters managed by the log.
    def addCounterNamed(Self, thisName):
        Result = None
        
        if len(thisName) > 0:
            NewCounter = ACounter(thisName)
            if Self._Counters.insert(NewCounter):
                Result = NewCounter
                
            else:
                del NewCounter
                
    ## Log the specified item to the log.
    #    
    #  If \p freeItem is \p false, the routine will return \p ThisItem.  This
    #  behavior allows a call to this routine to be embedded into a \p raise
    #  statement, so that an exception can be raised immediately after logging
    #  the specified item.  Otherwise, this routine returns \p None and 
    #  deletes the reference to \p ThisItem, which may or may not result in
    #  \p ThisItem being freed, depending on how many references it holds.
    def log(Self, ThisItem, freeItem=True):
        Result = None
        
        if ThisItem is not None:
            # Increment the counter tracking this item
            ThisCounter = Self._Counters.fetch(ThisItem.counterName())
            if ThisCounter is not None:
                ThisCounter.increment()
            
            # Output the item
            ThisItem.printTo(Self._Output)
            
            if freeItem:
                del ThisItem
                
            else:
                Result = ThisItem
                
        return Result
            
    # IsPrintable implementation -----------------------------------------------
    ## Return a string representation of the message log.  This 
    #  representation is simply a header line that is meant to be used with
    #  the string representation of each counter managed by the log.         
    def toString(Self):
        return mlStringRepresentation
        
    ## Print a string representation of the message log and its counters
    #  to the specified stream.
    def printTo(Self, Dest):
        result = 0
        
        if Dest is not None:
            result = Dest.write(Self.toString())
            # Write each counter
            for Counter in Self._Counters.itervalues():
                result += Counter.printTo(Dest)
                
        return result
        
    # Properties ---------------------------------------------------------------
    ## Retrieve the named counter.
    def Counter(Self, counterName):
        if counterName in Self._Counters:
            return Self._Counters[counterName]
            
        return None
    
    ## Retrieve the list of counters managed by the message log.    
    def Counters(Self):
        return Self._Counters
    
    ## Retrieve a reference to the output stream used by the message log.    
    def Output(Self):
        return Self._Output

## A null message log.
#    
#  This class does not do much.  Its log() method simply returns the message
#  object with which it is called, so that the method can still be used as
#  part of an exception-raising statement.  The class is designed for those
#  instances where a class may depend upon having a log but, for whatever
#  reason, no logging is actually to be done.  This class can be used in those
#  instances where a reference to a message log is required in order to prevent
#  code from breaking -- the author, however, considers it a hack and something
#  to remove at some point.
class ANullMessageLog(AMessageLog):
    ## Log the specified item. 
    #    
    #  This method does nothing but return \p ThisItem, so that a call to it
    #  can be used as part of an exception-raising statement.
    def log(Self, ThisItem, freeItem=True):
        if freeItem:
            del ThisItem
            return None
            
        return ThisItem
