'''
Name: srd_logging.py

Overview: Provides classes for enabling message logging.

Description:
    
    
Notes:

Author: Doug Crane
        May, 2012

Modifications:

'''

__author__ = 'Doug Crane'
__version__ = '1.0'

import sys
import os
import logging
import datetime
import pdb

import arcpy

import srd_featureclass_info

__all__ = ['SRD_Log', 'SRD_Logging']

# ====================================================================
class SRD_Log(object):
    '''
    Creates a State Singleton (Borg) of a logging object that can be used throughout
    a given set of processes. The first time it is called it is set after
    which all other instances will use same logging state.

    The intended use of the SRD_Log is to create an initial instance
    in the mainline of your process. Other classes may also create an
    instance but these will reference the original instance you defined.
    In this way if a log file was designated then all messages from the
    other instances will show up in this log file.

    This is a class that wraps the SRD_Logging class simplifying its
    options to provide those most ofter used.
    
    Example
        import srd.srd_logging as srd_logging

        # On first call in the mainline of your process set the log file.
        logChn1 = srd_logging.SRD_Log(r'd:\tmp\_tstLog.txt')

        # Set the header for the log file. In this example we pass a
        # few header lines to be written as part of header.
        logChn1.stampHeader(('My Process Name', 'Second line'))

        # IN YOUR OTHER CLASSES THAT YOU USE
        # In the __init__ of your class create an instance of an SRD_Log
        # This will reference the original from your mainline.

        self._logChn_ = srd_logging.SRD_Log()

        # From here just use as normally and message will be echoed in log file.
        self._logChn_.logMsg('Any message I want')
        
        
    '''
    
    __state = {}
    __logChn = None
    __logFilePath = None
    
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(self, logFilePath=None):
        self.__dict__ = self.__state

        # Only want one instance of log file that will be used by any
        # other instance of class that is created.

        # Set warning if user trys to set another log file after initial call.
        if logFilePath:
            if not self.__logFilePath is None:
                print('Log File has already been set, cannot create new one')
        else:
            self.__logFilePath = logFilePath
            
        # Create the single instance of logging object that will be used by all.
        if self.__logChn is None:
            self.__logChn = SRD_Logging(logFilePath)
            
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logMsg(self, msg):
        '''
        Writes to log file and output stream.

        Parameters:
            msg - message to log.

        '''

        self.__logChn.logMsg(msg)
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logWarning(self, msg):
        '''
        Writes warning to log file and output stream.

        Parameters:
            msg - message to log.

        '''
        
        self.__logChn.logWarning(msg)
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logError(self, msg):
        '''
        Writes warning to log file and output stream.

        Parameters:
            msg - message to log.

        '''

        self.__logChn.logError(msg)
        
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def closeLog(self):
        self.__logChn.closeLogFile()
        self.__LogChn = None
        
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def stampHeader(self, hdrList=[]):
        '''
        Stamps header information at top of the log file. An optional list
        of header lines can be passed to method. The current date and the user
        who was running the process is also written.

        Parameters:
            hdrList - list of header lines you wish to add to heading of log file.
            
        '''
        
        self.__logChn.stampHeader(hdrList)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def setLogFile(self, logFilePath):
        '''
        Allows setting log file after intial instance of logging
        object is created.

        Parameters:
            logFilePath - path to log file to create.
        '''
        self.__logFilePath = logFilePath
        
        self.__logChn.setLogFile(logFilePath)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logResultWarnings(self, resultObj):
        '''
        Uses the result object returned from the execution of a
        tool and prints out any warning messages associated with it.

        Parameters:
            resultObj - the result object returned from execution of tool
        '''

        # Get list of all warning messages.
        warningMessages = resultObj.getMessages(1)
        if warningMessages:
            self.logMsg('\n************ WARNINGS ****************')
            # If there are warning messages then get list and print them.
            msgList = warningMessages.split('\n')
            for msg in msgList:
                self.logWarning('   %s' % msg)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logResultErrors(self, resultObj):
        '''
        Uses the result object returned from the execution of a
        tool and prints out any error messages associated with it.

        Parameters:
            resultObj - the result object returned from execution of tool
        '''

        # Get list of all warning messages.
        warningMessages = resultObj.getMessages(2)
        if warningMessages:
            self.logMsg('\n************ ERRORS ****************')
            # If there are warning messages then get list and print them.
            msgList = warningMessages.split('\n')
            for msg in msgList:
                self.logWarning('   %s' % msg)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logResultErrorsAndWarnings(self, resultObj):
        '''
        Uses the result object returned from the execution of a
        tool and prints out any error or warning messages associated
        with it.

        Parameters:
            resultObj - the result object returned from execution of tool
        '''

        self.logResultErrors(resultObj)

        self.logResultWarnings(resultObj)
        
# ====================================================================
class SRD_Logging:
    '''
    This class provides basic logging capabilities for your applications.
    It can be used simply to write messages to standard output and the
    geoprocessing window or you can use it to also log messages to a log
    file.
    '''
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(self, logFilePath=None):
        '''
        Initialize

        Variables:
            logChn - logger object used to perform logging operations.
            
        Parameters:
            logFilePath - path to file where you wish messages
                          written to.

        Examples:

            
        '''

        # The following will be set if a log file has been indicated.
        # With no log file the messages will be simply handled with
        # a print statement.
        self._logChn_ = None
        self._streamChn_ = None
        self._fileChn_ = None
        
        # User may wish to write all messages to log file.
        if logFilePath:
            self.logFilePath = logFilePath
            self.setLogFile(logFilePath)
            
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __del__(self):
        if self.logFilePath:
            self.closeLogFile()
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logMsg(self, msg):

        '''
        Logs a general message.

        Parameters:
            msg - message to log.
        '''

        # Add to geoprocessing display window.
        arcpy.AddMessage(msg)

        # Print to log file if one has been created, otherwise just print message.
        if self._logChn_:
            self._logChn_.debug(msg)
        else:
            print(msg)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logWarning(self, msg):

        '''
        Logs a Warning message. These messages will be displayed
        in green within the geoprocessing window.

        Parameters:
            msg - warning to log.
        '''

        # Add to geoprocessing display window.
        arcpy.AddWarning(msg)

        # Print to log file if one has been created, otherwise just print message.
        if self._logChn_:
            self._logChn_.warning(msg)
        else:
            print(msg)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logError(self, msg):

        '''
        Logs an Error message. These messages will be displayed
        in red within the geoprocessing window.

        Parameters:
            msg - error to log.
        '''

        # Add to geoprocessing display window.
        arcpy.AddError(msg)

        # Print to log file if one has been created, otherwise just print message.
        if self._logChn_:
            self._logChn_.critical(msg)
        else:
            print(msg)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def stampHeader(self, hdrList=[]):
        '''
        Stamps header information at top of the log file. An optional list
        of header lines can be passed to method. The current date and the user
        who was running the process is also written.

        Parameters:
            hdrList - list of header lines you wish to add to heading of log file.
            
        '''
        
        for hdr in hdrList:
            self.logMsg(hdr)
            
        self.logMsg('Date: %s' % str(datetime.datetime.now()))
        self.logMsg('User Name: %s' % os.environ.get('USERNAME'))
        self.logMsg('\n------------------------------------------------------------\n')
        
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def setLogFile(self, logFilePath):
        '''
        Opens the log file indicated by logFilePath for writing.
        By default the logging level will be set to DEBUG.

        The log file will be opened in write mode. This means
        that if the log file already exists it will be overwritten.

        Parameters:
            logFilePath - path to file that will be used to
                          log any messages.

        '''
        # Establish handler both for standard output and
        # output to our log file.

        self._logChn_ = logging.getLogger()
        
        self._streamChn_ = logging.StreamHandler(sys.stdout)
        self._fileChn_ = logging.FileHandler(logFilePath, 'w')

        self._logChn_.addHandler(self._streamChn_)
        self._logChn_.addHandler(self._fileChn_)
        self.setLoggingLevel()

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def closeLogFile(self):
        '''
        Closes any log file that may have been open to capture
        logging messages.
        '''

        if self._streamChn_:
            self._streamChn_.flush()
            self._logChn_.removeHandler(self._streamChn_)
            self._streamChn_ = None

        if self._fileChn_:
            self._fileChn_.close()
            self._logChn_.removeHandler(self._fileChn_)
            self._fileChn_ = None
            
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def setScriptLogFile(self, scriptPath, featureClass=None):
        '''
        Creates a log file based on the name of your script.
        Optionally you can use the path to a feature class to
        determine the location of this log file.

        The easiest way to pass name of script is by using
        sys.argv[0]. The name of the log file will be derived
        from the script name and given a .log extension.
        If no featureClass is passed then the location of the log
        file will be the directory defined through the TEMP environement
        variable.

        Parameters:
            scriptPath - full path to script being executed. Use
                         sys.argv[0] for simple method of obtaining
                         path.
            Optional:
            featureClass - path to feature class that will be used to
                           determine location of log file. The parent directory
                           of the feature class will be used. With most
                           tools there is a feature class that you will
                           be processing so having log file in same directory
                           will allow users to easily find it in case
                           problems encounterd.
        Examples:

            import srd.srd_logging as srd_logging
            
            # Create log file based on name of script with its location
            # in directory where convex.shp feature class is located.
            logFile = srd_logging.SRD_Logging()
            logFile.setScriptLogFile(sys.argv[0], r'D:\PythonScripts\_utils\data\convex.shp')

            # Create log file based on name of script with its location
            # in directory pointed to by TEMP environment variable.
            logFile = srd_logging.SRD_Logging()
            logFile.setScriptLogFile(sys.argv[0])
            
        '''

        # Name of logfile will be same as script.
        logFilename,ext = os.path.splitext(os.path.basename(scriptPath))

        if featureClass:
            # Obtain parent direcotry of feature class.
            utilFC = SRD_FeatureClassInfo(featureClass)
            utilFC.getParentDir()
            basePath = utilFC.parentDir
        else:
            basePath = os.environ.get('TEMP')

        logFilePath = os.path.join(basePath, logFilename + '.log')
            
        self.setLogFile(logFilePath)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def setLoggingLevel(self, level='DEBUG'):
        '''
        Sets the level of logging to perform when displaying messages.
        By default the logging level is set to DEBUG which will allow
        messages of any level to be passed through to output.

        Parameters:
            Optional:
            level - level of logging to set, defaults to DEBUG:
                DEBUG
                INFO
                WARNING
                ERROR
                CRITICAL

            The level is used as a filter for accepting various
            messages. You can write messages to the log channel
            with an appropriate level. For example, if you set
            the logging level to INFO then send a message with
            a level of DEBUG associated with it then that message
            will be ignored
        '''
        
        if level.upper() == 'INFO':
            self._logChn_.setLevel(logging.INFO)
        elif level.upper() == 'DEBUG':
            self._logChn_.setLevel(logging.DEBUG)
        elif level.upper() == 'WARNING':
            self._logChn_.setLevel(logging.WARNING)
        elif level.upper() == 'ERROR':
            self._logChn_.setLevel(logging.ERROR)
        elif level.upper() == 'CRITICAL':
            self._logChn_.setLevel(logging.CRITICAL)
        else:
            self._logChn_.setLevel(logging.DEBUG)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def logMsg2(self, msg, level='DEBUG', gpSeverity=0):
        '''
        FOR HISTORICAL PURPOSES
        
        Logs a message to the established logging level
        and also to the geoprocessing display window. If
        a log file has been opened then the messages will
        also be written to this file.

        Parameters:
            msg - message to log.
            level - level to write the message to. When the
                    log channel was first defined a level was
                    set. Whether a message is written to the log
                    channel depends on the current level. If you
                    set the original level to INFO then pass an
                    DEBUG level message it will not be displayed.
                    The order of levels is as follows:
                        DEBUG
                        INFO
                        WARNING
                        ERROR
                        CRITICAL
                    The default level is DEBUG
            gpSeverity - determines the level of messages written to
                         the geoprocessing window. The set of available
                         levels are:
                             0 - INFO
                             1 - WARNING
                             2 - ERROR
                         The default level is 0.
        '''

        # Add to geoprocessing display window.
        if gpSeverity == 2:
            arcpy.AddError(msg)
        else:
            if gpSeverity == 1:
                arcpy.AddWarning(msg)
            else:
                arcpy.AddMessage(msg)

        # Print to log file if one has been created.
        if self._logChn_:
            # Log message according to requested level.
            if level.upper() == 'INFO':
                self._logChn_.info(msg)
            elif level.upper() == 'DEBUG':
                self._logChn_.debug(msg)
            elif level.upper() == 'WARNING':
                self._logChn_.warning(msg)
            elif level.upper() == 'ERROR':
                self._logChn_.error(msg)
            elif level.upper() == 'CRITICAL':
                self._logChn_.critical(msg)
            else:
                self._logChn_.debug(msg)
        else:
            # If no logging has been enabled then just print the message.
            print(msg)
            
# --------------------------------------------------------------------
if __name__ == '__main__':

    logFile = SRD_Logging()
    print(sys.argv[0])
    logFile.setScriptLogFile(sys.argv[0])

    logFile.logMsg('This is a test of log')
