"""Module for verbose logging set up.
"""
import logging
import inspect
import sys
import pprint as pp

# Make the log messages lowercase - useful resources:
# https://stackoverflow.com/questions/22313603/how-can-i-make-the-level-name-be-lower-case
# https://stackoverflow.com/questions/2183233/how-to-add-a-custom-loglevel-to-pythons-logging-facility/35804945#35804945
logging.addLevelName(logging.DEBUG, 'debug')
logging.addLevelName(logging.INFO, 'info')
logging.addLevelName(logging.WARNING, 'warning')
logging.addLevelName(logging.ERROR, 'error')
logging.addLevelName(logging.CRITICAL, 'critical')

LOG_LEVELS = [
    logging.NOTSET,
    logging.DEBUG,
    logging.INFO,
    logging.WARNING,
    logging.ERROR,
    logging.CRITICAL
]
"""The log levels that are supported by verbosity (`list` of `int`)
"""

NULL_LOGGER_NAME = "NULL"


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def init_logger(name, verbose=False, datefmt="%H:%M:%S",
                fmt='[{levelname}] {asctime} > {message}',
                stream=None, style="{"):
    """Initialise a logger that respects verbosity can be used during
    execution.

    Parameters
    ----------
    name : `str`
        The name for the logger.
    verbose : `bool` or `int`, optional, default: `False`
        The logging level. ``False`` or ``0`` will turn verbosity off.
        A level of ``True`` or ``1``, will output all messages and higher
        levels will restrict logging output to more serious message classes.
    datefmt : `str`, optional, default: `%H:%M:%S`
        The format for the date/timestamp. See
        `strftime()
        <https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes>`_
    fmt : `str`, optional, default: `{levelname.lower}] {asctime} > {message}`
        The named formatted string, suitable got passing to
        ``logging.Formatter`` which is appropriate for ``style``,
    stream : `Stream` or `NoneType`, optional, default: `NoneType`
        The location of the stream for ``logging.StreamHandler``.
        ``NoneType``, defaults in ``sys.stderr``.
    style : `str`, optional, default: `{`
        The style of ``fmt``, can be ``%``, ``{`` or ``$``.

    Returns
    -------
    logger : `logging.Logger`
        The logger to issue log messages.
    """
    verbose = int(verbose)

    # create logger
    logger = logging.getLogger(name)
    level = min(int(verbose), len(LOG_LEVELS) - 1)

    logger.setLevel(LOG_LEVELS[level])

    ch = None
    if verbose > 0:
        ch = logging.StreamHandler()
    else:
        ch = logging.NullHandler()

    ch.setLevel(LOG_LEVELS[level])

    # create formatter
    formatter = logging.Formatter(
        fmt=fmt,
        datefmt=datefmt,
        style="{"
    )

    # add formatter to ch
    ch.setFormatter(formatter)

    # add ch to logger
    logger.addHandler(ch)
    return logger


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def is_verbose(logger):
    """Rest to see if the logger is enabled for verbose output.

    Parameters
    ----------
    logger

    Returns
    -------
    is_berbose : `bool`
        ``True`` is it is ``False`` if it is not.

    Notes
    -----
    This works on the assumption that ``logging.NOTSET`` is not verbose and
    anything above that is verbose. Please note that the handlers are not
    checked (i.e. for the presence of a NULL handler)
    """
    return not any(
        [isinstance(i, logging.NullHandler) for i in logger.handlers]
    )


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def init_null_logger():
    """Create/setup a logger that does not do anything.

    Returns
    -------
    logger : `logging.Logger`
        The logger with the name ``"NULL"`` and a ``logging.NullHandler``.
    """
    logger = logging.getLogger("NULL")
    ch = logging.NullHandler()
    logger.addHandler(ch)
    return logger


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def get_null_logger():
    """Retrieve a logger that does not do anything. The assumption is that it
    has been created with ``init_null_logger.

    Returns
    -------
    logger : `logging.Logger`
        The logger with the name ``"NULL"`` and a ``logging.NullHandler``.
    """
    return logging.getLogger(NULL_LOGGER_NAME)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def retrive_logger(name, verbose=False):
    """Retrieve a logger that has been previously created.

    Returns
    -------
    logger : `logging.Logger`
        The logger with the name ``"NULL"`` and a ``logging.NullHandler``.
    """
    if verbose is False:
        return logging.getLogger(NULL_LOGGER_NAME)
    return logging.getLogger(name)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def log_prog_name(logger, pkg_name, version, stream=None):
    """Output the program name.

    Parameters
    ----------
    name : `str`
        The program name.
    pkg_name : `str`
        The package name.
    version : `str`
        The package version.
    verbose : `bool` or `int`, optional, default: False=False, stream=None
        Do you want to output.
    stream : `NoneType` or `Any`, optional, default: `NoneType`
        A location to print to, if ``NoneType``, this will default to
        ``sys.stderr``.

    Notes
    -----
    This does not use logging so is really designed for aesthetics. If verbose
    is ``0`` or ``False`` then nothing is output, if ``True`` or > ``0``, then
    the program name, version etc is output.
    """
    stream = stream or sys.stderr

    if is_verbose(logger) is True:
        print("=== {0} ({1} v{2}) ===".format(logger.name, pkg_name, version),
              file=stream)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def log_end(logger):
    """Log an end message ``*** END ***``.

    Parameters
    ----------
    logger : `logging.Logger`
        The logger to write to.
    """
    logger.info("*** END ***")


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def log_interrupt(logger):
    """Log an interrupt message ``*** INTERRUPT ***``.

    Parameters
    ----------
    logger : `logging.Logger`
        The logger to write to.
    """
    logger.warning("*** INTERRUPT ***")


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def log_args(logger, args):
    """Output all the command line arguments to the log.

    Parameters
    ----------
    logger : `logging.Logger`
        The logger to write to.
    args : `argparse.Namespace`
        The argparse namespace object containing the arguments.

    Notes
    -----
    Any argument starting with ``_``, is assumed to be private. List
    arguments only have their length output.
    """
    # Loop through the objects attributes
    for i in inspect.getmembers(args):
        # Ignores anything starting with underscore
        # (that is, private and protected attributes)
        if not i[0].startswith('_'):
            # Ignores methods
            if not inspect.ismethod(i[1]):
                attribute = str(i[1])
                modifier = "value"
                # For lists, dicts and tuples we report the length
                if isinstance(i[1], (list, dict, tuple)):
                    attribute = str(len(i[1]))
                    modifier = "length"
                # Output the message, here we use clean password of lazy
                # SQLAlchemy to make sure any passwords are stripped
                # from connection URLS
                logger.info("{0} {1}: {2}".format(i[0], modifier, attribute))
