# Filename: logger.py
# pylint: disable=locally-disabled,C0103
"""
The logging facility.

"""
from hashlib import sha256
from inspect import getframeinfo, stack
import socket
import logging
import logging.handlers

from .tools import colored, supports_color

__author__ = "Tamas Gal"
__copyright__ = "Copyright 2016, Tamas Gal and the KM3NeT collaboration."
__credits__ = []
__license__ = "MIT"
__maintainer__ = "Tamas Gal"
__email__ = "tgal@km3net.de"
__status__ = "Development"

loggers = {}  # this holds all the registered loggers
# logging.basicConfig()

DEPRECATION = 45
logging.addLevelName(DEPRECATION, "DEPRECATION")
ONCE = 46
logging.addLevelName(ONCE, "ONCE")


def deprecation(self, message, *args, **kws):
    """Show a deprecation warning."""
    self._log(DEPRECATION, message, args, **kws)


def once(self, message, *args, **kws):
    """Show a message only once, determined by position in source or identifer.

    This will not work in IPython or Jupyter notebooks if no identifier is
    specified, since then the determined position in source contains the
    execution number of the input (cell), which changes every time.
    Set a unique ``identifier=X``, otherwise the message will be printed every
    time.

    """
    identifier = kws.pop("identifier", None)

    if identifier is None:
        caller = getframeinfo(stack()[1][0])
        identifier = "%s:%d" % (caller.filename, caller.lineno)
    if not hasattr(self, "once_dict"):
        self.once_dict = {}
    if identifier in self.once_dict:
        return
    self.once_dict[identifier] = True
    self._log(ONCE, message, args, **kws)


logging.Logger.deprecation = deprecation
logging.Logger.once = once

if supports_color():
    logging.addLevelName(
        logging.INFO, "\033[1;32m%s\033[1;0m" % logging.getLevelName(logging.INFO)
    )
    logging.addLevelName(
        logging.DEBUG, "\033[1;34m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)
    )
    logging.addLevelName(
        logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING)
    )
    logging.addLevelName(
        logging.ERROR, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.ERROR)
    )
    logging.addLevelName(
        logging.CRITICAL,
        "\033[1;101m%s\033[1;0m" % logging.getLevelName(logging.CRITICAL),
    )
    logging.addLevelName(DEPRECATION, "\033[1;35m%s\033[1;0m" % "DEPRECATION")
    logging.addLevelName(ONCE, "\033[1;36m%s\033[1;0m" % "ONCE")


class LogIO(object):
    """Read/write logging information."""

    def __init__(self, node, stream, url="pi2089.physik.uni-erlangen.de", port=28777):
        self.node = node
        self.stream = stream
        self.url = url
        self.port = port
        self.sock = None
        self.connect()

    def send(self, message, level="info"):
        message_string = "+log|{0}|{1}|{2}|{3}\r\n".format(
            self.stream, self.node, level, message
        )
        try:
            self.sock.send(message_string)
        except socket.error:
            print("Lost connection, reconnecting...")
            self.connect()
            self.sock.send(message_string)

    def connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.connect((self.url, self.port))


def get_logger(name, filename=None, stream_loglevel="INFO", file_loglevel="DEBUG"):
    """Helper function to get a logger"""
    if name in loggers:
        return loggers[name]
    logger = logging.getLogger(name)
    logger.propagate = False

    with_color = supports_color()

    pre1, suf1 = hash_coloured_escapes(name) if with_color else ("", "")
    pre2, suf2 = hash_coloured_escapes(name + "salt") if with_color else ("", "")
    formatter = logging.Formatter(
        "%(asctime)s %(levelname)s {}+{}+{} "
        "%(name)s: %(message)s".format(pre1, pre2, suf1),
        datefmt="%Y-%m-%d %H:%M:%S",
    )
    if filename is not None:
        ch_file = logging.handlers.RotatingFileHandler(
            filename, maxBytes=5 * 1024 * 1024, backupCount=10
        )
        ch_file.setLevel(file_loglevel)
        ch_file.setFormatter(formatter)
        logger.addHandler(ch_file)
    ch = logging.StreamHandler()
    ch.setLevel(stream_loglevel)
    ch.setFormatter(formatter)
    logger.addHandler(ch)

    loggers[name] = logger

    logger.once_dict = {}

    return logger


def available_loggers():
    """Return a list of avialable logger names"""
    return list(logging.Logger.manager.loggerDict.keys())


def set_level(log_or_name, level):
    """Set the log level for given logger and all handlers"""
    if isinstance(log_or_name, str):
        log = get_logger(log_or_name)
    else:
        log = log_or_name
    log.setLevel(level)
    for handler in log.handlers:
        handler.setLevel(level)


def get_printer(name, color=None, ansi_code=None, force_color=False):
    """Return a function which prints a message with a coloured name prefix"""

    if force_color or supports_color():
        if color is None and ansi_code is None:
            cpre_1, csuf_1 = hash_coloured_escapes(name)
            cpre_2, csuf_2 = hash_coloured_escapes(name + "salt")
            name = cpre_1 + "+" + cpre_2 + "+" + csuf_1 + " " + name
        else:
            name = colored(name, color=color, ansi_code=ansi_code)

    prefix = name + ": "

    def printer(text):
        print(prefix + str(text))

    return printer


def hash_coloured(text):
    """Return a ANSI coloured text based on its hash"""
    ansi_code = int(sha256(text.encode("utf-8")).hexdigest(), 16) % 230
    return colored(text, ansi_code=ansi_code)


def hash_coloured_escapes(text):
    """Return the ANSI hash colour prefix and suffix for a given text"""
    ansi_code = int(sha256(text.encode("utf-8")).hexdigest(), 16) % 230
    prefix, suffix = colored("SPLIT", ansi_code=ansi_code).split("SPLIT")
    return prefix, suffix
