"""
Copyright (c) 2017 Dell Inc. or its subsidiaries. All Rights Reserved.
"""
import logging
import re
import argparse
import optparse
import sys


class _LoggingConfigFilter(logging.Filter):
    """
    Python logging Filter subclass that handles the following qualities:
    * An override handler-threshold. Say from DEBUG_5 to override console-capture's normal INFO.
    * which loggers connected to this handler should be tested against the override.
      (I.E., you can set 'infra.run', and 'test.run' to DEBUG_5, and this filter will only
       check infra.run and test.run loggers. Others will be checked against the 'unmatched_levelno'.
    * An 'unmatched_levelno' to use when the logger is NOT in the list. (see note below)
    * An optional file-pattern. Only checked within a matching logger.

    The filter is applied on a 1:1 basis with a single handler.

    Note that the unmatched_levelno is needed, because in order to get message records TO this
    filter, the loggers that feed them need to have their threshold dropped low enough, BUT we
    still want to apply their old handler threshold. This is how we do it.
    """
    def __init__(self, which_lgs, levelno, unmatched_levelno, name, file_pat=None):
        super(_LoggingConfigFilter, self).__init__(name)
        self.__which_loggers = which_lgs
        self.__levelno = levelno
        self.__unmatched_levelno = unmatched_levelno
        if file_pat is None:
            self.__file_matcher = re.compile('.*')  # match ANYTHING
        else:
            self.__file_matcher = re.compile(file_pat)

    def filter(self, record):
        if record.name in self.__which_loggers:
            if record.levelno >= self.__levelno:
                if self.__file_matcher.search(record.filename):
                    return True
            return False
        if record.levelno >= self.__unmatched_levelno:
            return True
        return False

    def __str__(self):
        rs = 'level {0} threshold for {1} loggers, umatched={2}'.format(
            self.__levelno, self.__which_loggers, self.__unmatched_levelno)
        return rs


class _ArgparseToOptparseWrapper(object):
    def __init__(self, parser):
        """
        Wrapper for optparser based objects that map argparse style operations
        to optparser ones. This is done to allow 'nose' (which still uses optparse!!!!) to
        function.
        """
        self.__parser = parser

    def add_argument_group(self, *args, **kwargs):
        new_group = optparse.OptionGroup(self.__parser, *args, **kwargs)
        new_wr_group = _ArgparseToOptparseWrapper(new_group)
        self.__parser.add_option_group(new_group)
        return new_wr_group

    def add_argument(self, *args, **kwargs):
        self.__parser.add_option(*args, **kwargs)

    def error(self, message):
        print >>sys.stderr, message
        sys.exit(2)


class LoggerArgParseHelper(object):
    def __init__(self, parser):
        """
        This is a class that can be instantiated with a parser (either optparse OR argparse) that
        adds options to control the infra-logging system.
        """
        if not isinstance(parser, argparse.ArgumentParser):
            parser = _ArgparseToOptparseWrapper(parser)

        self.__parser = parser
        list_group = parser.add_argument_group('listing log settings')
        list_group.add_argument(
            '--sm-list-logging', dest='list_logging', action='store_true', default=False,
            help='list logging settings')
        list_group.add_argument(
            '--sm-list-level', nargs=1, default=1, dest='list_logging_level',
            help='detail level if --list-logging is used')
        set_group = parser.add_argument_group(
            'setting log options',
            "note: both 'logger-name's and 'handler-name's can contain wild-card characters to match multiple items.")
        set_group.add_argument(
            '--sm-set-logger-level', nargs=2, default=[],
            dest='set_logger_level_list', action='append',
            metavar=('logger-name', 'level-name-or-value'),
            help="Set a logger's capture threshold. Loggers are evaluated before handlers.")
        set_group.add_argument(
            '--sm-set-handler-level', nargs=2, default=[],
            dest='set_handler_level_list', action='append',
            metavar=('handler-name[:logger-name]', 'level-name-or-value'),
            help="Set a handler's capture threshold.")

        set_group.add_argument(
            '--sm-set-combo-level', nargs=2, default=[],
            dest='set_combo_level_list', action='append',
            metavar=('handler-name[:logger-name]', 'level-name-or-value'),
            help="Set a handler's capture threshold AND all loggers feeding it.")

        set_group.add_argument(
            '--sm-set-file-level', nargs=3, dest='set_file_level',
            metavar=('file-pattern', '[handler-name[:logger-name] [level-name-or-value]]'),
            help="Same as --sm-set-combo-level, but restricts the change in output to the files that match the file-pattern")

    def __display_filter(self, indent, filter, detail):
        """
        Print a bit of info on the given filter.
        """
        indent_txt = ' ' * indent
        print >>sys.stderr, 'F{0}{1}: {2}'.format(indent_txt, filter.name, str(filter))

    def __display_handler(self, indent, handler, detail):
        """
        Print out a bit of info on the given handler and then display its filters
        """
        indent_txt = ' ' * indent
        if isinstance(handler, logging.StreamHandler):
            extra_info = ', stream={0}'.format(handler.stream)
        else:
            extra_info = ''
        print >>sys.stderr, 'H{0}{1}({2}): level={3}{4}'.format(
            indent_txt, handler.get_name(), handler.__class__.__name__,
            handler.level, extra_info)
        for filter in handler.filters:
            self.__display_filter(indent + 1, filter, detail)

    def __recurse_list(self, indent, logger, detail):
        """
        Print the passed in logger, its handlers, and then recurse on other
        loggers under its space.
        """
        indent_txt = ' ' * indent
        print >>sys.stderr, 'L{0}{1}: level={2}'.format(indent_txt, logger.name, logger.level)
        for handler in logger.handlers:
            self.__display_handler(indent + 1, handler, detail)
        for lg_name, lg_obj in logging.Logger.manager.loggerDict.items():
            if isinstance(lg_obj, logging.PlaceHolder):