# Copyright 2011 The Emscripten Authors.  All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License.  Both these licenses can be
# found in the LICENSE file.

"""Simple color-enabled diagnositics reporting functions.
"""

import ctypes
import logging
import os
import sys
from typing import Any

WINDOWS = sys.platform.startswith("win")

logger = logging.getLogger("diagnostics")
color_enabled = sys.stderr.isatty()
tool_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]

# diagnostic levels
WARN = 1
ERROR = 2
FATAL = 3

# available colors
RED = 1
GREEN = 2
YELLOW = 3
BLUE = 4
MAGENTA = 5
CYAN = 6
WHITE = 7

# color for use for each diagnostic level
level_colors = {
    WARN: MAGENTA,
    ERROR: RED,
}

level_prefixes = {
    WARN: "warning: ",
    ERROR: "error: ",
}

# Constants from the Windows API
STD_OUTPUT_HANDLE = -11


def output_color_windows(color):
    # TODO(sbc): This code is duplicated in colored_logger.py.  Refactor.
    # wincon.h
    FOREGROUND_BLACK = 0x0000  # noqa
    FOREGROUND_BLUE = 0x0001  # noqa
    FOREGROUND_GREEN = 0x0002  # noqa
    FOREGROUND_CYAN = 0x0003  # noqa
    FOREGROUND_RED = 0x0004  # noqa
    FOREGROUND_MAGENTA = 0x0005  # noqa
    FOREGROUND_YELLOW = 0x0006  # noqa
    FOREGROUND_GREY = 0x0007  # noqa

    color_map = {
        RED: FOREGROUND_RED,
        GREEN: FOREGROUND_GREEN,
        YELLOW: FOREGROUND_YELLOW,
        BLUE: FOREGROUND_BLUE,
        MAGENTA: FOREGROUND_MAGENTA,
        CYAN: FOREGROUND_CYAN,
        WHITE: FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED,
    }

    sys.stderr.flush()
    hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
    ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, color_map[color])


def get_color_windows():
    SHORT = ctypes.c_short
    WORD = ctypes.c_ushort

    class COORD(ctypes.Structure):
        _fields_ = [("X", SHORT), ("Y", SHORT)]

    class SMALL_RECT(ctypes.Structure):
        _fields_ = [
            ("Left", SHORT),
            ("Top", SHORT),
            ("Right", SHORT),
            ("Bottom", SHORT),
        ]

    class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
        _fields_ = [
            ("dwSize", COORD),
            ("dwCursorPosition", COORD),
            ("wAttributes", WORD),
            ("srWindow", SMALL_RECT),
            ("dwMaximumWindowSize", COORD),
        ]

    hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
    csbi = CONSOLE_SCREEN_BUFFER_INFO()
    ctypes.windll.kernel32.GetConsoleScreenBufferInfo(hdl, ctypes.byref(csbi))
    return csbi.wAttributes


def reset_color_windows():
    sys.stderr.flush()
    hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
    ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, default_color)


def output_color(color):
    if WINDOWS:
        return output_color_windows(color)
    return "\033[3%sm" % color


def reset_color():
    if WINDOWS:
        return reset_color_windows()
    return "\033[0m"


def diag(level, msg, *args):
    # Format output message as:
    # <tool>: <level>: msg
    # With the `<level>:` part being colored accordingly.
    sys.stderr.write(tool_name + ": ")

    if color_enabled:
        output = output_color(level_colors[level])
        if output:
            sys.stderr.write(output)

    sys.stderr.write(level_prefixes[level])

    if color_enabled:
        output = reset_color()
        if output:
            sys.stderr.write(output)

    if args:
        msg = msg % args
    sys.stderr.write(str(msg))
    sys.stderr.write("\n")


def error(msg, *args):
    diag(ERROR, msg, *args)
    sys.exit(1)


def warn(msg, *args):
    diag(WARN, msg, *args)


class WarningManager:
    warnings: Any = {}

    def add_warning(
        self, name, enabled=True, part_of_all=True, shared=False, error=False
    ):
        self.warnings[name] = {
            "enabled": enabled,
            "part_of_all": part_of_all,
            # True for flags that are shared with the underlying clang driver
            "shared": shared,
            "error": error,
        }

    def capture_warnings(self, cmd_args):
        for i in range(len(cmd_args)):
            if cmd_args[i] == "-w":
                for warning in self.warnings.values():
                    warning["enabled"] = False
                continue

            if not cmd_args[i].startswith("-W"):
                continue

            if cmd_args[i] == "-Wall":
                for warning in self.warnings.values():
                    if warning["part_of_all"]:
                        warning["enabled"] = True
                continue

            if cmd_args[i] == "-Werror":
                for warning in self.warnings.values():
                    warning["error"] = True
                continue

            if cmd_args[i].startswith("-Werror=") or cmd_args[i].startswith(
                "-Wno-error="
            ):
                warning_name = cmd_args[i].split("=", 1)[1]
                if warning_name in self.warnings:
                    enabled = not cmd_args[i].startswith("-Wno-")
                    self.warnings[warning_name]["error"] = enabled
                    if enabled:
                        self.warnings[warning_name]["enabled"] = True
                    cmd_args[i] = ""
                    continue

            warning_name = cmd_args[i].replace("-Wno-", "").replace("-W", "")
            enabled = not cmd_args[i].startswith("-Wno-")

            # special case pre-existing warn-absolute-paths
            if warning_name == "warn-absolute-paths":
                self.warnings["absolute-paths"]["enabled"] = enabled
                cmd_args[i] = ""
                continue

            if warning_name in self.warnings:
                self.warnings[warning_name]["enabled"] = enabled
                if not self.warnings[warning_name]["shared"]:
                    cmd_args[i] = ""
                continue

        return cmd_args

    def warning(self, warning_type, message, *args):
        warning_info = self.warnings[warning_type]
        msg = (message % args) + " [-W" + warning_type.lower().replace("_", "-") + "]"
        if warning_info["enabled"]:
            if warning_info["error"]:
                error(msg + " [-Werror]")
            else:
                warn(msg)
        else:
            logger.debug("disabled warning: " + msg)


def add_warning(name, enabled=True, part_of_all=True, shared=False, error=False):
    manager.add_warning(name, enabled, part_of_all, shared, error)


def enable_warning(name, as_error=False):
    manager.warnings[name]["enabled"] = True
    if as_error:
        manager.warnings[name]["error"] = True


def disable_warning(name):
    manager.warnings[name]["enabled"] = False


def warning(warning_type, message, *args):
    manager.warning(warning_type, message, *args)


def capture_warnings(argv):
    return manager.capture_warnings(argv)


if WINDOWS:
    default_color = get_color_windows()

manager = WarningManager()
