#!/usr/bin/env python
# coding=utf-8
#
# Copyright © Splunk, Inc. All Rights Reserved.

from __future__ import absolute_import, division, print_function, unicode_literals

from traceback import format_tb
import logging
import sys

from . internal import string
from . public import SlimEnum

__all__ = ['SlimLogger', 'SlimLoggerLevel', 'SlimExternalFormatter']


SlimLoggerLevel = SlimEnum(['DEBUG', 'STEP', 'NOTE', 'WARN', 'ERROR', 'FATAL'])

logging.NOTE = logging.INFO
logging.addLevelName(logging.NOTE, 'NOTE')

logging.STEP = logging.INFO - 1
logging.addLevelName(logging.STEP, 'STEP')


class SlimFormatter(logging.Formatter):

    # noinspection PyShadowingBuiltins
    def __init__(self, formatstr):
        logging.Formatter.__init__(self, formatstr)

    def format(self, record):
        record.levelname = SlimFormatter._level_names.get(record.levelno, ' ')
        record.msg = '%s' * len(record.args)
        return logging.Formatter.format(self, record)

    _level_names = {
        logging.DEBUG: ' [DEBUG] ',
        logging.NOTE:  ' [NOTE] ',
        logging.WARN:  ' [WARNING] ',
        logging.ERROR: ' [ERROR] ',
        logging.FATAL: ' [FATAL] '
    }


class SlimExternalFormatter(logging.Formatter):

    # noinspection PyShadowingBuiltins
    def __init__(self, formatstr):
        logging.Formatter.__init__(self, formatstr)

    def format(self, record):
        record.msg = '%s' * len(record.args)
        return logging.Formatter.format(self, record)


# TODO: SPL-123971: Refactor slim.utils.logger so that it has fewer public methods
# pylint: disable=too-many-public-methods
class SlimLogger(object):

    # Logging and logging count methods

    @classmethod
    def debug(cls, *args):
        cls._emit(SlimLoggerLevel.DEBUG, *args)

    @classmethod
    def debug_count(cls):
        return cls._message_count[SlimLoggerLevel.DEBUG]

    @classmethod
    def error(cls, *args):
        cls._emit(SlimLoggerLevel.ERROR, *args)

    @classmethod
    def error_count(cls):
        return cls._message_count[SlimLoggerLevel.ERROR]

    @classmethod
    def fatal(cls, *args, **kwargs):

        exception_info = kwargs.get('exception_info')

        if exception_info is None:
            cls._emit(SlimLoggerLevel.FATAL, *args)
        else:
            error_type, error_value, traceback = exception_info

            message = string(error_type.__name__ if error_value is None else error_value)

            if cls._debug:
                message += '\nTraceback: ' + error_type.__name__ + '\n' + ''.join(format_tb(traceback))

            cls._emit(SlimLoggerLevel.FATAL, *(args + (': ', message)) if len(args) > 0 else message)

        sys.exit(1)

    @classmethod
    def fatal_count(cls):
        return cls._message_count[SlimLoggerLevel.FATAL]

    @classmethod
    def information(cls, *args):
        cls._emit(SlimLoggerLevel.NOTE, *args)

    @classmethod
    def information_count(cls):
        return cls._message_count[SlimLoggerLevel.NOTE]

    @classmethod
    def step(cls, *args):
        cls._emit(SlimLoggerLevel.STEP, *args)

    @classmethod
    def step_count(cls):
        return cls._message_count[SlimLoggerLevel.STEP]

    @classmethod
    def warning(cls, *args):
        cls._emit(SlimLoggerLevel.WARN, *args)

    @classmethod
    def warning_count(cls):
        return cls._message_count[SlimLoggerLevel.WARN]

    @classmethod
    def message(cls, level, *args, **kwargs):
        if level != SlimLoggerLevel.FATAL:
            cls._emit(level, *args)
            return
        cls.fatal(*args, **kwargs)

    # endregion

    # region Logging configuration methods

    @classmethod
    def add_handler(cls, handler):
        cls._logger.addHandler(handler)

    @classmethod
    def handlers(cls):
        return cls._logger.handlers

    @classmethod
    def is_debug_enabled(cls):
        return cls._debug is True

    @classmethod
    def is_quiet_enabled(cls):
        return cls._logger.getEffectiveLevel() is logging.ERROR

    @classmethod
    def level(cls, key):
        try:
            return getattr(SlimLoggerLevel, key.upper())
        except AttributeError:
            return SlimLoggerLevel.NOTE

    @classmethod
    def remove_handler(cls, handler):
        cls._logger.removeHandler(handler)

    @classmethod
    def reset_command_name(cls, command_name):
        cls._adapter = logging.LoggerAdapter(cls._logger, {'command_name': command_name})

    @classmethod
    def reset_counts(cls):
        for level in cls._message_count:
            cls._message_count[level] = 0

    @classmethod
    def set_debug(cls, value):
        cls._debug = bool(value)

    @classmethod
    def set_level(cls, value):
        if isinstance(value, string):
            value = getattr(SlimLoggerLevel, value.upper())
        level = cls._message_type[value]
        cls._logger.setLevel(level)
        cls._default_level = level

    @classmethod
    def set_quiet(cls, value):
        cls._logger.setLevel(logging.ERROR if value else cls._default_level)

    # endregion

    # Other methods

    @classmethod
    def exit_on_error(cls):
        if cls._message_count[SlimLoggerLevel.ERROR]:
            sys.exit(1)

    @classmethod
    def use_external_handler(cls, handler):
        cls.remove_handler(cls._handler)
        cls.add_handler(handler)

    @classmethod
    def set_logger_name(cls, name):
        cls._logger.name = name

    # endregion

    # region Privates

    _debug = False  # turns on debug output which is different than turning on debug messages using, e.g., set_level
    _default_level = logging.STEP  # call SlimLogger.set_level to change (works in tandem with SlimLogger.set_quiet)

    _message_count = {
        SlimLoggerLevel.DEBUG: 0,
        SlimLoggerLevel.STEP:  0,
        SlimLoggerLevel.NOTE:  0,
        SlimLoggerLevel.WARN:  0,
        SlimLoggerLevel.ERROR: 0,
        SlimLoggerLevel.FATAL: 0
    }

    _message_type = {
        SlimLoggerLevel.DEBUG: logging.DEBUG,
        SlimLoggerLevel.STEP:  logging.STEP,
        SlimLoggerLevel.NOTE:  logging.NOTE,
        SlimLoggerLevel.WARN:  logging.WARN,
        SlimLoggerLevel.ERROR: logging.ERROR,
        SlimLoggerLevel.FATAL: logging.FATAL
    }

    # noinspection PyShadowingNames
    @classmethod
    def _emit(cls, level, *args):
        cls._adapter.log(cls._message_type[level], None, *args)
        cls._message_count[level] += 1

    @staticmethod
    def _initialize_logging():

        from os.path import basename, splitext

        command_name = splitext(basename(sys.argv[0]))[0]

        logger = logging.getLogger(command_name)
        logger.setLevel(logging.STEP)
        logger.propagate = False  # Do not try to use parent logging handlers

        handler = logging.StreamHandler()
        handler.setFormatter(SlimFormatter('%(command_name)s:%(levelname)s%(message)s'))
        logger.addHandler(handler)

        adapter = logging.LoggerAdapter(logger, {'command_name': command_name})

        return logger, handler, adapter

    _logger, _handler, _adapter = _initialize_logging.__func__()

    # endregion
    pass  # pylint: disable=unnecessary-pass
