import logging
from logging import LogRecord, Formatter, ERROR
from traceback import TracebackException
from colorlog import ColoredFormatter
from os.path import join, abspath

HISTORY_SIZE = 1000
PATH_BASE = abspath(join(__file__, "..", ".."))

logging.addLevelName(5, "TRACE")
logging.TRACE = 5


class HistoryHandler(logging.Handler):
    def __init__(self):
        super(HistoryHandler, self).__init__()
        self.history = [None] * HISTORY_SIZE
        self.history_index = 0

    def reset(self):
        self.history = [None] * HISTORY_SIZE
        self.history_index = 0

    def emit(self, record: LogRecord):
        self.history[self.history_index % HISTORY_SIZE] = record
        self.history_index += 1

    def getHistory(self, start=0, html=False):
        end = self.history_index
        if end - start >= HISTORY_SIZE:
            start = end - HISTORY_SIZE
        for x in range(start, end):
            item = self.history[x % HISTORY_SIZE]
            if html:
                if item.levelno == logging.WARN:
                    style = "console-warning"
                elif item.levelno == logging.ERROR:
                    style = "console-error"
                elif item.levelno == logging.DEBUG:
                    style = "console-debug"
                elif item.levelno == logging.CRITICAL:
                    style = "console-critical"
                elif item.levelno == logging.FATAL:
                    style = "console-fatal"
                elif item.levelno == logging.WARNING:
                    style = "console-warning"
                elif item.levelno == logging.TRACE:
                    style = "console-trace"
                else:
                    style = "console-default"
                line = "<span class='" + style + \
                    "'>" + self.format(item) + "</span>"
                yield (x + 1, line)
            else:
                yield (x + 1, self.format(item))

    def getLast(self) -> LogRecord:
        return self.history[(self.history_index - 1) % HISTORY_SIZE]


CONSOLE = logging.StreamHandler()
CONSOLE.setLevel(logging.INFO)
formatter_color = ColoredFormatter(
    '%(log_color)s%(asctime)s %(levelname)s %(message)s%(reset)s',
    datefmt='%m-%d %H:%M:%S',
    reset=True,
    log_colors={
        "DEBUG": "cyan",
        "INFO": "green",
        "WARNING": "yellow",
        "ERROR": "red",
        "CRITICAL": "red",
        "TRACE": "white",
    },
)
CONSOLE.setFormatter(formatter_color)

HISTORY = HistoryHandler()
HISTORY.setLevel(logging.DEBUG)
HISTORY.setFormatter(Formatter('%(asctime)s %(levelname)s [%(name)s] %(message)s', '%m-%d %H:%M:%S'))


class StandardLogger(logging.Logger):
    def __init__(self, name):
        super().__init__(name)
        self.setLevel(logging.TRACE)
        self.addHandler(CONSOLE)
        self.addHandler(HISTORY)

    def trace(self, msg, *args, **kwargs):
        self.log(logging.TRACE, msg, *args, **kwargs)

    def printException(self, ex: Exception, level=ERROR):
        self.log(level, self.formatException(ex))

    def formatException(self, e: Exception) -> str:
        trace = None
        if (hasattr(e, "__traceback__")):
            trace = e.__traceback__
        tbe = TracebackException(type(e), e, trace, limit=None)
        lines = list(self._format(tbe))
        return '\n%s' % ''.join(lines)

    def _format(self, tbe):
        if (tbe.__context__ is not None and not tbe.__suppress_context__):
            yield from self._format(tbe.__context__)
            yield "Whose handling caused:\n"
        is_addon, stack = self._formatStack(tbe)
        yield from stack
        yield from tbe.format_exception_only()

    def _formatStack(self, tbe):
        _RECURSIVE_CUTOFF = 3
        result = []
        last_file = None
        last_line = None
        last_name = None
        count = 0
        is_addon = False
        buffer = []
        for frame in tbe.stack:
            line_internal = True
            if (last_file is None or last_file != frame.filename or last_line is None or last_line != frame.lineno or last_name is None or last_name != frame.name):
                if count > _RECURSIVE_CUTOFF:
                    count -= _RECURSIVE_CUTOFF
                    result.append(
                        f'  [Previous line repeated {count} more '
                        f'time{"s" if count > 1 else ""}]\n'
                    )
                last_file = frame.filename
                last_line = frame.lineno
                last_name = frame.name
                count = 0
            count += 1
            if count > _RECURSIVE_CUTOFF:
                continue
            fileName = frame.filename
            pos = fileName.rfind(PATH_BASE)
            if pos >= 0:
                is_addon = True
                line_internal = False
                fileName = "addon" + \
                    fileName[pos + len(PATH_BASE):]

            pos = fileName.rfind("site-packages")
            if pos > 0:
                fileName = fileName[pos - 1:]

            pos = fileName.rfind("python3.7")
            if pos > 0:
                fileName = fileName[pos - 1:]
                pass
            line = '  {}:{} ({})\n'.format(fileName, frame.lineno, frame.name)
            if line_internal:
                buffer.append(line)
            else:
                result.extend(self._compressFrames(buffer))
                buffer = []
                result.append(line)
        if count > _RECURSIVE_CUTOFF:
            count -= _RECURSIVE_CUTOFF
            result.append(
                f'  [Previous line repeated {count} more '
                f'time{"s" if count > 1 else ""}]\n'
            )
        result.extend(self._compressFrames(buffer))
        return is_addon, result

    def overrideLevel(self, console, history):
        CONSOLE.setLevel(console)
        HISTORY.setLevel(history)

    def _compressFrames(self, buffer):
        if len(buffer) > 1:
            yield buffer[0]
            if len(buffer) == 3:
                yield buffer[1]
            elif len(buffer) > 2:
                yield "  [{} hidden frames]\n".format(len(buffer) - 2)
            yield buffer[len(buffer) - 1]
        elif len(buffer) > 0:
            yield buffer[len(buffer) - 1]
            pass


def getLogger(name):
    return StandardLogger(name)


def getHistory(index, html):
    return HISTORY.getHistory(index, html)


def getLast() -> LogRecord:
    return HISTORY.getLast()


def reset() -> None:
    return HISTORY.reset()


class TraceLogger(StandardLogger):
    def __init__(self, name):
        super().__init__(name)
        self.setLevel(logging.TRACE)

    def log(self, lvl, msg, *args, **kwargs):
        super().log(logging.TRACE, msg, *args, **kwargs)

    def info(self, *args, **kwargs):
        super().log(logging.TRACE, *args, **kwargs)

    def error(self, *args, **kwargs):
        super().log(logging.TRACE, *args, **kwargs)

    def warn(self, *args, **kwargs):
        super().log(logging.TRACE, *args, **kwargs)
