"""

Python logging module - Version 0.3

Loglevels: 
    
    LOGLEVEL_NONE, LOGLEVEL_NORMAL, LOGLEVEL_VERBOSE, LOGLEVEL_DEBUG

Format-Parameters:
    
    %C ..... class-name
    %D ..... program duration
    %d ..... duration for the last step (last output)
    %F ..... function name
    %L ..... logtype (Error, Warning, ...)
    %M ..... message
    %N ..... Line-number
    %T ..... current time

Pre-defined Formats:

    FMT_SHORT, FMT_MEDIUM, FMT_LONG, FMT_DEBUG

"""

# Logging levels

LOGLEVEL_NONE = 1 << 0
LOGLEVEL_NORMAL = 1 << 1
LOGLEVEL_VERBOSE = 1 << 2
LOGLEVEL_DEBUG = 1 << 3

# Pre-defined format strings

FMT_SHORT = "%M"
FMT_MEDIUM = "[ %C.%F ] %D: %M"
FMT_LONG = "%T %L %C [%F] - %M"
FMT_DEBUG = "%T [%D (%d)] %L %C [%F (%N)] - %M"

# Configuration files

CONFIGURATION_FILES = {}
CONFIGURATION_FILES[1] = "log4py.conf"                    # local directory
CONFIGURATION_FILES[2] = "$HOME/.log4py.conf"             # hidden file in the home directory
CONFIGURATION_FILES[3] = "/etc/log4py.conf"               # system wide file

# The following constants are of internal interest only

# Message constants (used for ansi colors and for logtype %L)

MSG_DEBUG = 1 << 0
MSG_WARN = 1 << 1
MSG_ERROR = 1 << 2
MSG_INFO = 1 << 3

# Boolean constants

TRUE = "TRUE"
FALSE = "FALSE"

# Color constants

BLACK = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
PURPLE = 35
AQUA = 36
WHITE = 37

LOG_MSG = {MSG_DEBUG: "DEBUG", MSG_WARN: "WARNING", MSG_ERROR: "ERROR", MSG_INFO: "INFO"}
LOG_COLORS = {MSG_DEBUG: [WHITE, BLACK, FALSE], MSG_WARN: [WHITE, BLACK, FALSE], MSG_ERROR: [WHITE, BLACK, TRUE], MSG_INFO: [WHITE, BLACK, FALSE]}
LOG_LEVELS = { "DEBUG": LOGLEVEL_DEBUG, "VERBOSE": LOGLEVEL_VERBOSE, "NORMAL": LOGLEVEL_NORMAL, "NONE": LOGLEVEL_NONE }

from time import time, strftime, localtime
from types import StringType, ClassType, InstanceType
from string import zfill, atoi, lower, upper
from re import sub
from ConfigParser import ConfigParser

import sys
import traceback
import os

# This is the main class for the logging module

class Category:

    # class initalization & customization - don't use sys.stdout as default target - copy.deepcopy won't work otherwise

    def __init__(self, classid = None, useconfigfiles = TRUE, customconfigfiles = []):

        self.__Category_setdefaults()
        if (useconfigfiles == TRUE):
            self.__Category_appendconfigfiles(customconfigfiles)
            self.__Category_loadconfigfile()

        self.__Category_timeinit = time()
        self.__Category_timelaststep = self.__Category_timeinit
        if (classid == None):
            self.__Category_classname = "Main"
        else:
            if (type(classid) == StringType):
                self.__Category_classname = classid
            elif (type(classid) == ClassType):
                self.__Category_classname = classid.__name__
            elif (type(classid) == InstanceType):
                self.__Category_classname = classid.__class__.__name__

    # Methods to set properties

    def set_target(self, target):
        self.__Category_target = target

    def set_loglevel(self, loglevel):
        self.__Category_loglevel = loglevel

    def set_formatstring(self, formatstring):
        self.__Category_formatstring = formatstring

    def set_use_ansi_codes(self, useansicodes):
        self.__Category_useansicodes = useansicodes

    def set_time_format(self, timeformat):
        self.__Category_timeformat = timeformat

    # Method to get properties

    def get_loglevel(self):
        return self.__Category_loglevel

    def get_formatstring(self):
        return self.__Category_formatstring

    def get_use_ansi_codes(self):
        return self.__Category_useansicodes

    def get_time_format(self):
        return self.__Category_timeformat

    # Methods to actually print messages

    def debug(self, message):
        if (self.__Category_loglevel >= LOGLEVEL_DEBUG):
            self.__Category_showmessage(message, MSG_DEBUG)

    def warn(self, message):
        if (self.__Category_loglevel >= LOGLEVEL_VERBOSE):
            self.__Category_showmessage(message, MSG_WARN)

    def error(self, message):
        if (self.__Category_loglevel >= LOGLEVEL_NORMAL):
            self.__Category_showmessage(message, MSG_ERROR)

    def info(self, message):
        if (self.__Category_loglevel >= LOGLEVEL_NORMAL):
            self.__Category_showmessage(message, MSG_INFO)

    # Private method of the Category class - you never have to use those directly

    def __Category_tracestack(self):
        stack = traceback.extract_stack()
        self.__Category_linenumber = stack[-4][1]
        self.__Category_functionname = stack[-4][2]
        if (self.__Category_functionname == "?"):
            self.__Category_functionname = "Main"

    def __Category_setdefaults(self):
        self.__Category_target = None                         # None = sys.stdout
        self.__Category_formatstring = FMT_LONG
        self.__Category_loglevel = LOGLEVEL_NORMAL
        self.__Category_useansicodes = FALSE
        self.__Category_functionname = ""
        self.__Category_linenumber = -1
        self.__Category_timeformat = "%d.%m.%Y %H:%M:%S"

    def __Category_loadconfigfile(self):
        priorities = CONFIGURATION_FILES.keys()
        priorities.sort()
        configfilename = ""
        for i in range(len(priorities)):
            filename = CONFIGURATION_FILES[priorities[i]]
            filename = sub("\$HOME", os.environ["HOME"], filename)
            if (os.path.exists(filename)):
                configfilename = filename
                break
        if (configfilename != ""):
            parser = ConfigParser()
            parser.read(configfilename)
            section = "log4py"
            for i in range(len(parser.options(section))):
                option = lower(parser.options(section)[i])
                value = parser.get(section, option)
                if (option == "format"):
                    self.set_formatstring(value)
                elif (option == "loglevel"):
                    self.set_loglevel(LOG_LEVELS[upper(value)])
                elif (option == "timeformat"):
                    self.set_time_format(value)
                elif (option == "ansi"):
                    self.__Category_useansicodes = upper(value)
                elif (option == "target"):
                    if (lower(value) == "stdout"):
                        self.set_target(None)
                    elif (lower(value) == "stderr"):
                        self.set_target(sys.stderr)
                    else:
                        self.set_target(value)
            return TRUE
        else:
            return FALSE

    def __Category_appendconfigfiles(self, filenames):
        filenames.reverse()
        for i in range(len(filenames)):
            keys = CONFIGURATION_FILES.keys()
            CONFIGURATION_FILES[min(keys) - 1] = filenames[i]

    def __Category_showmessage(self, message, messagesource):
        currenttime = time()
        self.__Category_tracestack()
        timedifference = "%.3f" % (currenttime - self.__Category_timeinit)
        timedifflaststep = "%.3f" % (currenttime - self.__Category_timelaststep)
        self.__Category_timelaststep = currenttime
        milliseconds = int(round((currenttime - long(currenttime)) * 1000))
        timeformat = sub("%S", "%S." + (zfill(milliseconds, 3)), self.__Category_timeformat)
        currenttime = strftime(timeformat, localtime(currenttime))

        line = self.__Category_formatstring
        line = sub("%C", self.__Category_classname, line)
        line = sub("%D", timedifference, line)
        line = sub("%d", timedifflaststep, line)
        line = sub("%F", self.__Category_functionname, line)
        if (self.__Category_useansicodes == TRUE):
            line = sub("%L", self.__Category_ansi(LOG_MSG[messagesource], messagesource), line)
            line = sub("%M", self.__Category_ansi(message, messagesource), line)
        else:
            line = sub("%L", LOG_MSG[messagesource], line)
            line = sub("%M", message, line)
        line = sub("%N", str(self.__Category_linenumber), line)
        line = sub("%T", currenttime, line)

        if (self.__Category_target == sys.stdout) or (self.__Category_target == sys.stderr):
            self.__Category_target.write("%s\n" % line)
        elif (self.__Category_target == None):
            sys.stdout.write("%s\n" % line)
        else:
            file = open(self.__Category_target, "a")
            file.write("%s\n" % line)
            file.close()

    # Convert text to text including ansi codes

    def __Category_ansi(self, text, messagesource):
        bold = LOG_COLORS[messagesource][2]
        fg = str(LOG_COLORS[messagesource][0])
        bg = LOG_COLORS[messagesource][1]
        if (bold == TRUE):
            fg = "%s;1" % fg
        bg = bg + 10
        text = "\033[%d;%sm%s\033[0m" % (bg, fg, text)
        return text
