#
# A library to manage ARCFIRE experiments
#
#    Copyright (C) 2017-2018 Nextworks S.r.l.
#    Copyright (C) 2017-2018 imec
#
#    Sander Vrijders   <sander.vrijders@ugent.be>
#    Dimitri Staessens <dimitri.staessens@ugent.be>
#    Vincenzo Maffione <v.maffione@nextworks.it>
#    Marco Capitani    <m.capitani@nextworks.it>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., http://www.fsf.org/about/contact/.
#

import atexit
import logging
import logging.handlers
from logging.handlers import QueueHandler, QueueListener
import queue
import sys

import time


DEBUG = logging.DEBUG
INFO = logging.INFO
WARNING = logging.WARNING
ERROR = logging.ERROR
CRITICAL = logging.CRITICAL


loggers_set = set()

_setup_done = False

mq = queue.Queue()

logging_listener = None


class RumbaFormatter(logging.Formatter):
    """
    The `logging.Formatter` subclass used by Rumba
    """

    level_name_table = {
        'CRITICAL': 'CRT',
        'ERROR': 'ERR',
        'WARNING': 'WRN',
        'INFO': 'INF',
        'DEBUG': 'DBG',
        # handlers beyond the first will get the
        # modified levelname
        'CRT': 'CRT',
        'ERR': 'ERR',
        'WRN': 'WRN',
        'INF': 'INF',
        'DBG': 'DBG'
    }

    def __init__(self):
        super(RumbaFormatter, self).__init__(
            fmt='%(asctime)s | %(levelname)3.3s | '
                '%(name)11.11s | %(message)s',
            datefmt='%H:%M:%S')

    def format(self, record):
        record.name = record.name.split('.')[-1]
        record.levelname = self.level_name_table[record.levelname]
        return super(RumbaFormatter, self).format(record)


def setup():
    """Configures the logging framework with default values."""
    global mq, _setup_done
    if _setup_done:
        return
    # Attach QueueHandler to root logger so messages flow through the queue
    queue_handler = QueueHandler(mq)
    queue_handler.setLevel(logging.DEBUG)
    root_logger = logging.getLogger('')
    root_logger.addHandler(queue_handler)
    root_logger.setLevel(logging.DEBUG)
    # Configure rumba loggers to show INFO and above
    logging.getLogger('rumba').setLevel(logging.INFO)
    _setup_done = True


# Used for the first call, in order to configure logging
def _get_logger_with_setup(name):
    setup()
    # Swap _get_logger implementation to the setup-less version.
    global _get_logger
    _get_logger = _get_logger_without_setup
    return logging.getLogger(name)


# Then this one is used.
def _get_logger_without_setup(name):
    return logging.getLogger(name)


_get_logger = _get_logger_with_setup


def get_logger(name):
    """
    Returns the logger named `name`.

    `name` should be the module name, for consistency.

    If setup has not been called yet, it will call it first.

    :param name: the name of the desired logger
    :type name: `str`
    :return: The logger
    """
    return _get_logger(name)


def set_logging_level(level, name=None):
    """
    Set the current logging level to `level` for the logger named `name`.
    If `name` is not specified, sets the logging level for all rumba loggers.

    :param level: the desired logging level, in string or int form.
    :type level: `str` or `int`
    :param name: The name of the logger to configure
    :type name: `str`

    .. note:: Accepted levels are:

              - DEBUG == 10,
              - INFO == 20,
              - WARNING == 30,
              - ERROR == 40,
              - CRITICAL == 50,
              - NOTSET == 0 (resets the logger: its level is set to
                the default or its parents' level)
    """
    if name is None:
        if level == 'NOTSET' or level == 0:
            set_logging_level(logging.INFO)
            return
        name = 'rumba'
    if (level == 'NOTSET' or level == 0) and name == '':
        set_logging_level(logging.ERROR, '')
        return
    logger = get_logger(name)
    loggers_set.add(logger)
    logger.setLevel(level)


def reset_logging_level():
    """
    Resets the current logging level of all loggers to the default.
    For the Rumba library the default is INFO.
    """
    # Un-sets every logger previously set
    for logger in loggers_set:
        logger.setLevel(logging.NOTSET)
    set_logging_level(logging.INFO)
    set_logging_level(logging.ERROR, '')


def flush_log():
    """
    Flush the log.
    """
    time.sleep(0.1)


def flush_and_kill_logging():
    """
    Flushes all queued log messages and stops the logging facility.
    Since the logging is done by a daemon thread, log entries might be lost
    if execution is interrupted abruptly. Call this method to make sure
    this does not happen.
    """
    global logging_listener
    if logging_listener is not None:
        logging_listener.stop()


class LogOptions(object):
    """Class holding the logging configuration"""

    def __init__(self):
        global logging_listener
        global mq

        logging_listener = QueueListener(mq)
        self.log_to_console()
        logging_listener.start()
        # Configure logging levels
        setup()

    @staticmethod
    def _get_handlers():
        global logging_listener
        return tuple(logging_listener.handlers)

    @staticmethod
    def _set_handlers(*handlers):
        global logging_listener
        logging_listener.handlers = handlers

    def _add_handler(self, handler):
        handler.setFormatter(RumbaFormatter())
        handler.setLevel(DEBUG)
        handlers = self._get_handlers() + (handler,)
        self._set_handlers(*handlers)
        return self

    def log_to_file(self, path='rumba.log'):
        """
        Set the logging framework to log to file on top of the other
        logging facilities.

        :param path: logging file filename
        :type path: `str`
        :return: this `.LogOptions` instance
        """
        new_handler = logging.handlers.RotatingFileHandler(path)
        return self._add_handler(new_handler)

    def reset_logging(self):
        """
        Disable all logging facilities

        :return: this `.LogOptions` instance
        """
        self._set_handlers(*tuple())
        return self

    def log_to_console(self):
        """
        Set the logging framework to log to stdout on top of the
        other configured logging facilities

        :return: this `.LogOptions` instance
        """
        new_handler = logging.StreamHandler(sys.stdout)
        return self._add_handler(new_handler)


options = LogOptions()  # module instance used for configuration
atexit.register(flush_and_kill_logging)
