"""Nice logging, with colors on Linux."""

from typing import Any, Union
import logging
import sys

class ColoredFormatter(logging.Formatter):
    """Logging Formatter to add colors"""

    fg_bright_blue    = "\x1b[94m"
    fg_yellow        = "\x1b[33m"
    fg_red           = "\x1b[31m"
    fg_bright_red_bold = "\x1b[91;1m"
    reset            = "\x1b[0m"

    def __init__(self, fmt, datefmt):
        super().__init__()
        self.messagefmt = fmt
        self.datefmt = datefmt

        self.formats = {
            logging.DEBUG:    "{}{}{}".format(self.fg_bright_blue, self.messagefmt, self.reset),
            logging.INFO:       "{}".format(self.messagefmt),
            logging.WARNING:  "{}{}{}".format(self.fg_yellow, self.messagefmt, self.reset),
            logging.ERROR:    "{}{}{}".format(self.fg_red, self.messagefmt, self.reset),
            logging.CRITICAL: "{}{}{}".format(self.fg_bright_red_bold, self.messagefmt, self.reset)
        }

        self.formatters = {
            logging.DEBUG:    logging.Formatter(self.formats[logging.DEBUG],    datefmt = self.datefmt),
            logging.INFO:     logging.Formatter(self.formats[logging.INFO],     datefmt = self.datefmt),
            logging.WARNING:  logging.Formatter(self.formats[logging.WARNING],  datefmt = self.datefmt),
            logging.ERROR:    logging.Formatter(self.formats[logging.ERROR],    datefmt = self.datefmt),
            logging.CRITICAL: logging.Formatter(self.formats[logging.CRITICAL], datefmt = self.datefmt)
        }

    def format(self, record):
        formatter = self.formatters[record.levelno]
        return formatter.format(record)

def setup_root_logger(
        no_color: bool = False,
        verbosity_level: int = 0,
        quietness_level: int = 0,
        messagefmt: str = "[%(asctime)s][%(levelname)s] %(message)s (%(filename)s:%(lineno)d)",
        messagefmt_verbose: str = "[%(asctime)s][%(levelname)s] %(message)s (%(filename)s:%(lineno)d)",
        datefmt: str = "%Y-%m-%d %H:%M:%S"
    ):
    messagefmt_to_use = messagefmt_verbose if verbosity_level else messagefmt
    logging_level = 10 * (2 + quietness_level - verbosity_level)
    if not no_color and sys.platform == "linux":
        formatter_class = ColoredFormatter
    else:
        formatter_class = logging.Formatter

    root_logger = logging.getLogger()
    root_logger.setLevel(logging_level)
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter_class(fmt = messagefmt_to_use, datefmt = datefmt))
    root_logger.addHandler(console_handler)

def logging_fatal(message, log_stack_info: bool = True, exit_code: int = 1):
    logging.critical(message)
    logging.debug("Stack Trace", stack_info = log_stack_info)
    logging.critical("Exiting")
    raise SystemExit(exit_code)

def log_tree(title, tree, finals = None, log_leaves_types = True, logging_level = logging.INFO):
    """Log tree nicely if it is a dictionary.
    log_leaves_types can be False to log no leaves, True to log all leaves, or a tuple of types for which to log."""
    if finals is None:
        finals = []
    if not isinstance(tree, dict):
        logging.log(msg = "{}{}{}".format(
            "".join([" " if final else "│" for final in finals[:-1]] + ["└" if final else "├" for final in finals[-1:]]),
            title,
            ": {}".format(tree) if log_leaves_types is not False and (log_leaves_types is True or isinstance(tree, log_leaves_types)) else ""
        ), level = logging_level)
    else:
        logging.log(msg = "{}{}".format(
            "".join([" " if final else "│" for final in finals[:-1]] + ["└" if final else "├" for final in finals[-1:]]),
            title
        ), level = logging_level)
        tree_items = list(tree.items())
        for key, value in tree_items[:-1]:
            log_tree(key, value, finals = finals + [False], log_leaves_types = log_leaves_types, logging_level = logging_level)
        for key, value in tree_items[-1:]:
            log_tree(key, value, finals = finals + [True], log_leaves_types = log_leaves_types, logging_level = logging_level)

# like logging.CRITICAl, logging.DEBUG etc
FATAL = 60

def perror(s: Union[str, Any], e: Exception, logging_level: int = logging.ERROR):
    strerror = e.strerror if (isinstance(e, OSError) and e.strerror is not None) else e.__class__.__name__
    msg = f"{s}{': ' if s else ''}{strerror}"
    if logging_level == FATAL:
        logging_fatal(msg)
    else:
        logging.log(logging_level, msg)
