#  _________________________________________________________________________
#
#  PyUtilib: A Python utility library.
#  Copyright (c) 2008 Sandia Corporation.
#  This software is distributed under the BSD License.
#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
#  the U.S. Government retains certain rights in this software.
#  _________________________________________________________________________

import logging
import re
import sys
import textwrap

_indention = re.compile('\s*')

class LogHandler(logging.Handler):

    def __init__( self, base='', stream=None,
                  level=logging.NOTSET, verbosity=None ):
        logging.Handler.__init__(self, level=level)

        if verbosity is None:
            verbosity = lambda: True
        if stream is None:
            stream = sys.stdout

        self.verbosity = verbosity
        self.stream = stream
        self.basepath = base
        # Public attributes (because embedded constants in functions are evil)
        self.wrap = 78
        self.initial_indent = ''
        self.subsequent_indent = ' '*4

    def emit(self, record):
        level = record.levelname
        msg = record.getMessage()
        # Most of the messages are either unformatted long lines or
        # triple-quote blocks of text.  In the latter case, if the text
        # starts on the same line as the triple-quote, then it is almost
        # certainly NOT indented with the bulk of the text, which will
        # cause dedent to get confused and not strip any leading
        # whitespace.  This attempts to work around that case:
        #
        #if not ( msg.startswith('\n') or _indention.match(msg).group() ):
        #    # copy the indention for the second line to the first:
        #    lines = msg.splitlines()
        #    if len(lines) > 1:
        #        msg = _indention.match(lines[1]).group() + msg
        #
        # The problem with the above logic is that users may want a
        # simple introductory line followed by an intented line (our
        # tests did this!), and cannot specify it without adding an
        # extra blank line to the output.  In contrast, it is possible
        # for the user to fix the scenario above that motivated this
        # code by just indenting their first line correctly.

        # TBD: dedent does not convert \t to ' '*8. Should we do that?
        msg = textwrap.dedent(msg)

        # As textwrap only works on single paragraphs, we need to break
        # up the incoming message into paragraphs before we pass it to
        # textwrap.
        paragraphs = []
        indent = _indention.match(msg).group()
        par_lines = []
        for line in msg.splitlines():
            leading = _indention.match(line).group()
            content = line.strip()
            if not content:
                paragraphs.append((indent, par_lines))
                par_lines = []
                # Blank lines reset the indentation level
                indent = None
            elif indent == leading:
                # Catch things like bulleted lists
                if len(content) > 1 and par_lines and content[1] == ' ' \
                   and content[0] in '-* ':
                    paragraphs.append((indent, par_lines))
                    par_lines = []
                par_lines.append( content )
            else:
                paragraphs.append((indent, par_lines))
                par_lines = [ content ]
                indent = leading
        # Collect the final paragraph
        if par_lines:
            paragraphs.append((indent, par_lines))

        # Skip any leading/trailing blank lines
        while paragraphs and not paragraphs[-1][1]:
            paragraphs.pop()
        while paragraphs and not paragraphs[0][1]:
            paragraphs.pop(0)

        if self.verbosity():
            #
            # If verbosity is on, the first logged line is the file,
            # line, and function name that called the logger.  The first
            # line of the message is actually the second line of the
            # output (and so is indented/wrapped the same as the rest of
            # the message)
            #
            filename = record.pathname  # file path
            lineno = record.lineno
            try:
                function = record.funcName
            except AttributeError:
                function = '(unknown)'
            if self.basepath and filename.startswith(self.basepath):
                filename = '[base]' + filename[len(self.basepath):]

            self.stream.write(
                '%s: "%s", %d, %s\n' %
                ( level, filename, lineno, function.strip(), ))
        else:
            #
            # If verbosity is off, prepend the log level name to the
            # beginning of the message and format the line without the
            # 'subsequent' indentation of the remainder of the message
            #
            if paragraphs:
                firstPar = ' '.join(paragraphs.pop(0)[1]).strip()
                if level:
                    firstPar = ('%s: %s' % (level, firstPar))
            else:
                firstPar = level
            self.stream.write( '%s\n' % (
                textwrap.fill( firstPar,
                               width=self.wrap,
                               initial_indent=self.initial_indent,
                               subsequent_indent=self.subsequent_indent ), ))
        for indent, par in paragraphs:
            if not indent:
                indent = ''
            # Bulleted lists get indented with a hanging indent
            if par and len(par[0]) > 1 and par[0][0] in '-*':
                hang = ' '*4
            else:
                hang = ''
            self.stream.write( '%s\n' % (
                textwrap.fill(
                    ' '.join(par),
                    width=self.wrap,
                    initial_indent=self.subsequent_indent+indent,
                    subsequent_indent=self.subsequent_indent+indent+hang ), ))


#
# Set up default logging for PyUtilib
#
# __file__ fails if script is called in different ways on Windows
# __file__ fails if someone does os.chdir() before
# sys.argv[0] also fails because it does not always contain the path
from os.path import dirname as _dir, abspath as _abs
import inspect
_pyutilib_base = _dir(_dir(_dir(_abs(inspect.getfile(inspect.currentframe())))))

_logger = logging.getLogger('pyutilib')
_logger.setLevel(logging.WARNING)
_logger.addHandler( LogHandler(
    _pyutilib_base, verbosity=lambda: _logger.isEnabledFor(logging.DEBUG)))
