"""

This module decorates methods of Logger class.
It's quite harmless, unless you use some weird non-tty stdout/stderr while DEBUG-ing.

This hack should be auto-loaded by it's app.
If you don't use the app, just import this file to apply the hack.

"""

import sys
import logging
import itertools
import random


# Control sequences (just google VT52 to see the compatibility burden straight from 1975)
color_set = "\x1b[{}m{}"
color_reset = "\x1b[0m"

# useless stuff displayed before applying patches... yes, I felt very bored
intro_words = ["magic", "MaGiC", "voodoo", "sorcery", "wizardry", "witchery", "foobar", "rainbow"]
intro_adjectives = ["colourful", "evil", "fairy", "random"]
intro_colors = [34, 36, 32, 33, 35]

decorate_info = {
    # method names and their desired highlight colors
    "debug": "1;90",
    "info": "1;32",
    "warning": "1;33",
    "error": "1;91",
    "critical": "1;95",
    "exception": "1;95",
}

if settings.DEBUG:  # probably PyCharm or some other IDE with own stream handling
    # useless foo
    intro_seq = zip(itertools.cycle(intro_colors), random.choice(intro_words))
    print(
        "{adj} {word}{reset}:".format(
            adj=random.choice(intro_adjectives).capitalize(),
            word=str().join(color_set.format(*symbol) for symbol in intro_seq),
            reset=color_reset,
        ),
        end=" ")
    # Patching handlers: Python logging.StreamHandler targets stderr by default,
    # therefore Django example config does, therefore our logging config also does,
    # so PyCharm believes every line of log is error. Well... Fixing it now.
    # Existing loggers first.
    patched = list()
    for logger in logging.Logger.manager.loggerDict.values():
        for handler in getattr(logger, "handlers", list()):
            if isinstance(handler, logging.StreamHandler):
                if handler.stream == sys.stderr:
                    handler.stream = sys.stdout
                    patched.append(handler)
    # Future loggers (they should be initially created with the proper stream).
    original_constructor = logging.StreamHandler.__init__  # must grab a reference outside of the lambda
    logging.StreamHandler.__init__ = lambda zelf, stream=None: original_constructor(zelf, stream or sys.stdout)
    # the isatty() stuff below solved some problems in the past, I'm not sure anymore which exactly
    try:
        stdout_tty = sys.stdout.isatty()
    except Exception:
        stdout_tty = False
    if not stdout_tty:
        patched.append(sys.stdout.isatty)
        sys.stdout.isatty = lambda: True  # stdout is now a TTY no matter what
    # reporting
    print("applied {n} logging patches.".format(n=len(patched)))
    logging.getLogger().debug("Logging patches: {}.".format(", ".join(str(e) for e in patched) or "none"))


def _apply_color_to_msg(msg, color, extras):
    return "".join([color_set.format(color, extras), str(msg), color_reset])


def decorate_for_tty(method, color="1;39", extras=""):
    """ it also knows how to decorate bound methods, but it is not used anymore """
    method_is_bound = bool(getattr(method, "__self__", None))  # existing loggers cause bound methods
    msg_index = 0 if method_is_bound else 1  # message argument is located there

    def wrapper(*args, **kwargs):
        args = list(args)  # it's an immutable tuple when received
        args[msg_index] = _apply_color_to_msg(args[msg_index], color, extras)
        return method(*args, **kwargs)

    return wrapper

for entity in [logging.Logger]:
    for method_name, color_str in decorate_info.items():
        if isinstance(entity, logging.Logger) or entity is logging.Logger:
            new_method = decorate_for_tty(getattr(entity, method_name), color_str)
            setattr(entity, method_name, new_method)
        else:
            pass  # that should be a placeholder object (non-public API), ignoring it