# -*- coding: utf-8; -*-
"""Colorize terminal output.

Use Colorama if available; works on any OS.

If not available, and OS is a *nix, use ANSI escape codes.
"""

__all__ = ["setcolor", "colorize", "ColorScheme",
           "Fore", "Back", "Style"]

try:
    from colorama import Back, Fore, Style  # type: ignore[import]
    from colorama import init as colorama_init  # type: ignore[import]
    colorama_init()
except ImportError:  # pragma: no cover
    # The `ansi` module is a slightly modified, POSIX-only,
    # vendored version from Colorama. Useful e.g. in Docker
    # images that don't have the library available.
    from .ansi import Fore, Back, Style  # noqa: F811

# TODO: Get rid of this hack if Colorama adds these styles later.
# Inject some styles missing from Colorama 0.4.4
_additional_styles = (("ITALIC", "\33[3m"),
                      ("URL", "\33[4m"),  # underline plus possibly a special color (depends on terminal app)
                      ("BLINK", "\33[5m"),
                      ("BLINK2", "\33[6m"))  # same effect as BLINK?
for _name, _value in _additional_styles:
    if not hasattr(Style, _name):
        setattr(Style, _name, _value)
del _name, _value

from .bunch import Bunch


def setcolor(*colors, reset=True):
    """Set color for terminal display.

    Returns a string that, when printed into a terminal, sets the style
    and color.

    If `reset=True`, reset style and color before setting the requested
    style and color.

    If `reset=False`, augment current style and color. E.g. if `Style.BRIGHT`
    is already active, and you set `Fore.BLUE`, the style will remain BRIGHT,
    and color will be BLUE.

    For available `colors`, see `Fore`, `Back` and `Style`.

    Each entry can also be a tuple (arbitrarily nested), which is useful
    for defining compound styles.

    **CAUTION**: The specified style and color remain in effect until another
    explicit call to `setcolor`. To reset, use `setcolor()`.

    If you want to colorize a piece of text so that the color and style
    auto-reset after your text, use `colorize` instead.
    """
    def _setcolor(color):
        if isinstance(color, (list, tuple)):
            return "".join(_setcolor(elt) for elt in color)
        return color
    out = [_setcolor(Style.RESET_ALL)] if reset else []
    out.append(_setcolor(colors))
    return "".join(out)


def colorize(text, *colors):
    """Colorize string `text` for terminal display.

    Always reset style and color at the start of `text`, as well as after it.

    Returns `text`, augmented with color and style commands for terminals.

    For available `colors`, see `Fore`, `Back` and `Style`.

    Usage::

        print(colorize("I'm new here", Fore.GREEN))
        print(colorize("I'm bold and bluetiful", Style.BRIGHT, Fore.BLUE))

    Each entry can also be a tuple (arbitrarily nested), which is useful
    for defining compound styles::

        BRIGHT_BLUE = (Style.BRIGHT, Fore.BLUE)
        ...
        print(colorize("I'm bold and bluetiful, too", BRIGHT_BLUE))

    **CAUTION**: Does not nest. If you want to set a color and style
    until further notice, use `setcolor` instead.
    """
    return "{}{}{}".format(setcolor(colors),
                           text,
                           setcolor())


class ColorScheme(Bunch):
    """The color scheme for terminal output in `mcpyrate`'s debug utilities.

    This is just a bunch of constants. To change the colors, simply assign new
    values to them. Changes take effect immediately for any new output.

    To replace the whole color scheme at once, fill in a suitable `Bunch`, and
    then use the `replace` method. If you need to get the names of all settings
    programmatically, call the `keys` method.

    Don't replace the color scheme object itself; all the use sites
    from-import it.

    See `Fore`, `Back`, `Style` for valid values. To make a compound style,
    place the values into a tuple.

    The defaults are designed to fit the "Solarized" (Zenburn-like) theme
    of `gnome-terminal`, with "Show bold text in bright colors" set to OFF.
    But they work also with "Tango", and indeed with most themes.
    """
    def __init__(self):
        super().__init__()

        # ------------------------------------------------------------
        # unparse

        self.LINENUMBER = Style.DIM

        self.LANGUAGEKEYWORD = (Style.BRIGHT, Fore.YELLOW)  # for, if, import, ...
        self.BUILTINEXCEPTION = Fore.CYAN  # TypeError, ValueError, Warning, ...
        self.BUILTINOTHER = Style.BRIGHT  # str, property, print, ...

        self.DEFNAME = (Style.BRIGHT, Fore.CYAN)  # name of a function or class being defined
        self.DECORATOR = Fore.LIGHTBLUE_EX

        # These can be highlighted differently although Python 3.8+ uses `Constant` for all.
        self.STRING = Fore.GREEN
        self.NUMBER = Fore.GREEN
        self.NAMECONSTANT = Fore.GREEN  # True, False, None

        # Macro names are syntax-highlighted when a macro expander instance is
        # running and is provided to `unparse`, so it can query for bindings.
        # `step_expansion` does that automatically.
        #
        # So they won't yet be highlighted during dialect AST transforms,
        # because at that point, there is no *macro* expander.
        self.MACRONAME = Fore.BLUE

        self.INVISIBLENODE = Style.DIM  # AST node with no surface syntax repr (`Module`, `Expr`)

        # AST markers for data-driven communication within the macro expander
        self.ASTMARKER = Style.DIM  # the "$AstMarker" title
        self.ASTMARKERCLASS = Fore.YELLOW  # the actual marker type name

        # ------------------------------------------------------------
        # format_bindings, step_expansion, StepExpansion

        self.HEADING1 = (Style.BRIGHT, Fore.LIGHTBLUE_EX)  # main heading
        self.HEADING2 = Fore.LIGHTBLUE_EX  # subheading (filenames, tree ids, ...)
        self.SOURCEFILENAME = Style.BRIGHT

        # format_bindings
        self.GREYEDOUT = Style.DIM  # if no bindings

        # StepExpansion
        self.ATTENTION = (Style.BRIGHT, Fore.GREEN)  # "DialectExpander debug mode", "PHASE 0"
        self.TRANSFORMERKIND = (Style.BRIGHT, Fore.GREEN)  # "source", "AST"
        self.DIALECTTRANSFORMERNAME = (Style.BRIGHT, Fore.YELLOW)

        # ------------------------------------------------------------
        # dump

        self.NODETYPE = (Style.BRIGHT, Fore.LIGHTBLUE_EX)
        self.FIELDNAME = Fore.YELLOW
        self.BAREVALUE = Fore.GREEN

        # ------------------------------------------------------------
        # runtests
        self.TESTHEADING = self.HEADING1
        self.TESTPASS = (Style.BRIGHT, Fore.GREEN)
        self.TESTFAIL = (Style.BRIGHT, Fore.RED)
        self.TESTERROR = (Style.BRIGHT, Fore.YELLOW)
ColorScheme = ColorScheme()  # type: ignore[assignment, misc]
