# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Supports webkitpy logging."""

# FIXME: Move this file to webkitpy/python24 since logging needs to
#        be configured prior to running version-checking code.

import logging
import os
import sys

import webkitpy

from logging import FileHandler


_log = logging.getLogger(__name__)

# We set these directory paths lazily in get_logger() below.
_scripts_dir = ""
"""The normalized, absolute path to the ...Scripts directory."""

_webkitpy_dir = ""
"""The normalized, absolute path to the ...Scripts/webkitpy directory."""


def _normalize_path(path):
    """Return the given path normalized.

    Converts a path to an absolute path, removes any trailing slashes,
    removes any extension, and lower-cases it.

    """
    path = os.path.abspath(path)
    path = os.path.normpath(path)
    path = os.path.splitext(path)[0]  # Remove the extension, if any.
    path = path.lower()

    return path


# Observe that the implementation of this function does not require
# the use of any hard-coded strings like "webkitpy", etc.
#
# The main benefit this function has over using--
#
# _log = logging.getLogger(__name__)
#
# is that get_logger() returns the same value even if __name__ is
# "__main__" -- i.e. even if the module is the script being executed
# from the command-line.
def get_logger(path):
    """Return a logging.logger for the given path.

    Returns:
      A logger whose name is the name of the module corresponding to
      the given path.  If the module is in webkitpy, the name is
      the fully-qualified dotted module name beginning with webkitpy....
      Otherwise, the name is the base name of the module (i.e. without
      any dotted module name prefix).

    Args:
      path: The path of the module.  Normally, this parameter should be
            the __file__ variable of the module.

    Sample usage:

      from webkitpy.common.system import logutils

      _log = logutils.get_logger(__file__)

    """
    # Since we assign to _scripts_dir and _webkitpy_dir in this function,
    # we need to declare them global.
    global _scripts_dir
    global _webkitpy_dir

    path = _normalize_path(path)

    # Lazily evaluate _webkitpy_dir and _scripts_dir.
    if not _scripts_dir:
        # The normalized, absolute path to ...Scripts/webkitpy/__init__.
        webkitpy_path = _normalize_path(webkitpy.__file__)

        _webkitpy_dir = os.path.split(webkitpy_path)[0]
        _scripts_dir = os.path.split(_webkitpy_dir)[0]

    if path.startswith(_webkitpy_dir):
        # Remove the initial Scripts directory portion, so the path
        # starts with /webkitpy, for example "/webkitpy/init/logutils".
        path = path[len(_scripts_dir):]

        parts = []
        while True:
            (path, tail) = os.path.split(path)
            if not tail:
                break
            parts.insert(0, tail)

        logger_name = ".".join(parts)  # For example, webkitpy.common.system.logutils.
    else:
        # The path is outside of webkitpy.  Default to the basename
        # without the extension.
        basename = os.path.basename(path)
        logger_name = os.path.splitext(basename)[0]

    return logging.getLogger(logger_name)


def _default_handlers(stream, logging_level):
    """Return a list of the default logging handlers to use.

    Args:
      stream: See the configure_logging() docstring.

    """
    # Create the filter.
    def should_log(record):
        """Return whether a logging.LogRecord should be logged."""
        # FIXME: Enable the logging of autoinstall messages once
        #        autoinstall is adjusted.  Currently, autoinstall logs
        #        INFO messages when importing already-downloaded packages,
        #        which is too verbose.
        if record.name.startswith("webkitpy.thirdparty.autoinstall"):
            return False
        return True

    logging_filter = logging.Filter()
    logging_filter.filter = should_log

    # Create the handler.
    handler = logging.StreamHandler(stream)
    if logging_level == logging.DEBUG:
        formatter = logging.Formatter("%(name)s: [%(levelname)s] %(message)s")
    else:
        formatter = logging.Formatter("%(message)s")

    handler.setFormatter(formatter)
    handler.addFilter(logging_filter)

    return [handler]


def configure_logging(logging_level=None, logger=None, stream=None,
                      handlers=None):
    """Configure logging for standard purposes.

    Returns:
      A list of references to the logging handlers added to the root
      logger.  This allows the caller to later remove the handlers
      using logger.removeHandler.  This is useful primarily during unit
      testing where the caller may want to configure logging temporarily
      and then undo the configuring.

    Args:
      logging_level: The minimum logging level to log.  Defaults to
                     logging.INFO.
      logger: A logging.logger instance to configure.  This parameter
              should be used only in unit tests.  Defaults to the
              root logger.
      stream: A file-like object to which to log used in creating the default
              handlers.  The stream must define an "encoding" data attribute,
              or else logging raises an error.  Defaults to sys.stderr.
      handlers: A list of logging.Handler instances to add to the logger
                being configured.  If this parameter is provided, then the
                stream parameter is not used.

    """
    # If the stream does not define an "encoding" data attribute, the
    # logging module can throw an error like the following:
    #
    # Traceback (most recent call last):
    #   File "/System/Library/Frameworks/Python.framework/Versions/2.6/...
    #         lib/python2.6/logging/__init__.py", line 761, in emit
    #     self.stream.write(fs % msg.encode(self.stream.encoding))
    # LookupError: unknown encoding: unknown
    if logging_level is None:
        logging_level = logging.INFO
    if logger is None:
        logger = logging.getLogger()
    if stream is None:
        stream = sys.stderr
    if handlers is None:
        handlers = _default_handlers(stream, logging_level)

    logger.setLevel(logging_level)

    for handler in handlers:
        logger.addHandler(handler)

    _log.debug("Debug logging enabled.")

    return handlers


def configure_logger_to_log_to_file(logger, log_path, filesystem):
    log_directory = filesystem.dirname(log_path)
    if log_directory and not filesystem.exists(log_directory):
        filesystem.maybe_make_directory(log_directory)

    handler = FileSystemHandler(log_path, filesystem)
    formatter = logging.Formatter('%(asctime)s - %(message)s')
    handler.setFormatter(formatter)

    logger.addHandler(handler)
    return handler


class FileSystemHandler(FileHandler):
    def __init__(self, filename, filesystem):
        self.filename = filename
        self.filesystem = filesystem
        FileHandler.__init__(self, filename)

    def _open(self):
        return self.filesystem.open_text_file_for_writing(self.filename, should_append=True)
