# =============================================================================
# codePost v2.0 SDK
#
# LOGGING SUB-MODULE
# =============================================================================

from __future__ import print_function # Python 2

# Python stdlib imports
import logging as _logging
import os as _os
import time as _time
import sys as _sys
import platform as _platform

# External imports
import better_exceptions as _better_exceptions
import eliot
import eliot.stdlib

# Eliot imports
from eliot import Message
from eliot import log_call, start_task
from eliot import start_action, current_action, Action

# =============================================================================

# Global submodule constants
LOG_FILENAME = "codepost.log"
LOG_DEFAULT_SCOPE = "{}".format(__name__)
LOG_FILE = open(LOG_FILENAME, "ab")

# Global submodule protected attributes
_logger = None
_only_eliot = False
_eliot_configured = False
_loggers_configured = {}

# =============================================================================

def fail_action(reason="", warning=True):
    # type: (str, bool) -> None
    """
    Helper method to report an `eliot` action as failed.
    """
    exc_klass = RuntimeError
    if warning:
        exc_klass = RuntimeWarning

    return current_action().finish(exception=exc_klass(reason))

# =============================================================================

class _QuietableStreamHandler(_logging.StreamHandler):
    """
    Wrapper around the `logging.StreamHandler` that can be toggled through the
    global submodule attribute `_only_eliot`.
    """

    def setLevel(self, lvl, *args, **kwargs):
        """
        Set the logging level of this logger.  level must be an int or a str.
        """
        return super(_QuietableStreamHandler, self).setLevel(
            lvl, *args, **kwargs)

    def emit(self, record, *args, **kwargs):
        """
        Emit a record, but only if the global submodule attribute `_only_eliot`
        is `False`.
        """
        if not _only_eliot:
            return super(_QuietableStreamHandler, self).emit(
                record, *args, **kwargs)

# =============================================================================

class _SimpleColorFormatter(_logging.Formatter):
    """
    This is a simple log formatting class to output short informative users to
    the console, for the benefit of the end-user. This is complementary to the
    more serious logging effort with the `eliot` library.
    """

    def __init__(self, *args, **kwargs):
        """
        Configure the formatter by initializing the colored terminal captions,
        for the log events which are output to standard output.
        """
        import blessings as _blessings
        _t = _blessings.Terminal()
        f = lambda s: s.format(_t=_t) # Python 2 compatibility of f"..."
        self._title = {
            "DEBUG":   f("{_t.normal}[{_t.bold}{_t.blue}DBUG{_t.normal}]"),
            "INFO":    f("{_t.normal}[{_t.bold}{_t.green}INFO{_t.normal}]"),
            "WARNING": f("{_t.normal}[{_t.bold}{_t.yellow}WARN{_t.normal}]"),
            "ERROR":   f("{_t.normal}[{_t.bold}{_t.red}ERR.{_t.normal}]"),
        }
        super(_SimpleColorFormatter, self).__init__(*args, **kwargs)

    def normalize_path(self, path):
        # type: str -> str
        """
        Return a path which is relative to the working directory, provided a
        path that may be absolute or relative (for error reporting).
        """
        abs_path = _os.path.abspath(path)
        pwd_path = _os.getcwd()
        return abs_path.replace(pwd_path, ".", 1)

    def format_message(self, record):
        # type: (_logging.LogRecord) -> str
        """
        Format a log record as provided by the standard `logging` library, to
        have color characters for terminal output.
        """
        header = self._title.get(record.levelname, self._title.get("INFO"))
        return("{} {} (\"{}\", line {}): {}".format(
            header,
            record.module,
            self.normalize_path(record.filename),
            record.lineno,
            record.message
        ))

    def formatMessage(self, record):
        # type: (_logging.LogRecord) -> str
        return self.format_message(record=record)

# =============================================================================

def _setup_eliot():
    # type: () -> bool
    """
    Set up the `eliot` package so that it outputs to a log file.
    """

    global _eliot_configured

    if not _eliot_configured:
        eliot.to_file(LOG_FILE)
        _eliot_configured = True

    return _eliot_configured

def _setup_logging(name=None, level="INFO"):
    # type: (str, str) -> _logging.Logger
    """
    Set up the logger for a specific submodule.
    """

    logger = _logging.getLogger(name)

    # Check if we have already configured this logger.

    if not name in _loggers_configured:

        # Add the color handler to the terminal output

        handler = _logging.StreamHandler()#_QuietableStreamHandler()

        if _platform.system() != 'Windows':
            formatter = _SimpleColorFormatter()
            handler.setFormatter(formatter)

        # Set logging level of the terminal output (default to provided level)

        handler.setLevel(_os.environ.get("LOGLEVEL", level))

        # Set logging level of the logger

        logger.setLevel("DEBUG")

        # Add the color terminal output handler to the logger

        logger.addHandler(handler)

        # Add the EliotHandler so that all user-intended prompts are also
        # logged in the master log; we set the level to DEBUG so all messages
        # are logged.

        eliotHandler = eliot.stdlib.EliotHandler()
        eliotHandler.setLevel("DEBUG")
        logger.addHandler(eliotHandler)

        # Remember we configured this logger

        _loggers_configured[name] = handler

        # We may also need to configure eliot (at most once)

        _setup_eliot()

    return logger

# =============================================================================

def get_logger(name=None):
    # type: (str) -> _logging.Logger
    """
    Return a logger with the specified name, creating it if necessary.

    If no name is specified, return the root logger.
    """
    global _logger

    if name == None or name == "":

        # Configure it the first time
        if _logger == None:
            _logger = _setup_logging(LOG_DEFAULT_SCOPE)

        return _logger
    else:
        return _setup_logging(name)

# =============================================================================

def make_verbose():
    # type: () -> bool
    """
    Make the logging verbose by output the full `eliot` output.
    """
    global _only_eliot

    if not _only_eliot:
        # Change the attribute (which will mute normal standard error msgs)
        _only_eliot = True

        # Add a standard output stream to eliot
        eliot.to_file(_sys.stdout)

    return _only_eliot

# =============================================================================