"""
Used to manage the outputter system. This package is the modular system used
for managing outputters.
"""

import errno
import io
import logging
import os
import re
import sys
import traceback

import salt.loader
import salt.utils.files
import salt.utils.platform
import salt.utils.secret
import salt.utils.stringutils

# Are you really sure !!!
# dealing with unicode is not as simple as setting defaultencoding
# which can break other python modules imported by salt in bad ways...
# reloading sys is not either a good idea...
# reload(sys)
# sys.setdefaultencoding('utf-8')

log = logging.getLogger(__name__)


def try_printout(data, out, opts, **kwargs):
    """
    Safely get the string to print out, try the configured outputter, then
    fall back to nested and then to raw
    """
    data = salt.utils.secret.mask_output(data)
    try:
        printout = get_printout(out, opts)(data, **kwargs)
        if printout is not None:
            return printout.rstrip()
    except (KeyError, AttributeError, TypeError):
        log.debug(traceback.format_exc())
        try:
            printout = get_printout("nested", opts)(data, **kwargs)
            if printout is not None:
                return printout.rstrip()
        except (KeyError, AttributeError, TypeError):
            log.error("Nested output failed: ", exc_info=True)
            printout = get_printout("raw", opts)(data, **kwargs)
            if printout is not None:
                return printout.rstrip()


def get_progress(opts, out, progress):
    """
    Get the progress bar from the given outputter
    """
    return salt.loader.raw_mod(opts, out, "rawmodule", mod="output")[
        f"{out}.progress_iter"
    ](progress)


def update_progress(opts, progress, progress_iter, out):
    """
    Update the progress iterator for the given outputter
    """
    # Look up the outputter
    try:
        progress_outputter = salt.loader.outputters(opts)[out]
    except KeyError:  # Outputter is not loaded
        log.warning("Progress outputter not available.")
        return False
    progress_outputter(progress, progress_iter)


def progress_end(progress_iter):
    try:
        progress_iter.stop()
    except Exception:  # pylint: disable=broad-except
        pass
    return None


def display_output(data, out=None, opts=None, **kwargs):
    """
    Print the passed data using the desired output
    """
    if opts is None:
        opts = {}
    display_data = try_printout(data, out, opts, **kwargs)

    output_filename = opts.get("output_file", None)
    log.trace("data = %s", data)
    try:
        # output filename can be either '' or None
        if output_filename:
            if not hasattr(output_filename, "write"):
                # pylint: disable=resource-leakage
                ofh = salt.utils.files.fopen(output_filename, "a")
                # pylint: enable=resource-leakage
                fh_opened = True
            else:
                # Filehandle/file-like object
                ofh = output_filename
                fh_opened = False

            try:
                fdata = display_data
                if isinstance(fdata, str):
                    try:
                        fdata = fdata.encode("utf-8")
                    except (UnicodeDecodeError, UnicodeEncodeError):
                        # try to let the stream write
                        # even if we didn't encode it
                        pass
                if fdata:
                    ofh.write(salt.utils.stringutils.to_str(fdata))
                    ofh.write("\n")
            finally:
                if fh_opened:
                    ofh.close()
            return
        if display_data:
            salt.utils.stringutils.print_cli(display_data)
    except OSError as exc:
        # Only raise if it's NOT a broken pipe
        if exc.errno != errno.EPIPE:
            raise


def get_printout(out, opts=None, **kwargs):
    """
    Return a printer function
    """
    if opts is None:
        opts = {}

    if "output" in opts and opts["output"] != "highstate":
        # new --out option, but don't choke when using --out=highstate at CLI
        # See Issue #29796 for more information.
        out = opts["output"]

    # Handle setting the output when --static is passed.
    if not out and opts.get("static"):
        if opts.get("output"):
            out = opts["output"]
        elif opts.get("fun", "").split(".")[0] == "state":
            # --static doesn't have an output set at this point, but if we're
            # running a state function and "out" hasn't already been set, we
            # should set the out variable to "highstate". Otherwise state runs
            # are set to "nested" below. See Issue #44556 for more information.
            out = "highstate"

    if out == "text":
        out = "txt"
    elif out is None or out == "":
        out = "nested"
    if opts.get("progress", False):
        out = "progress"

    opts.update(kwargs)
    if "color" not in opts:

        def is_pipe():
            """
            Check if sys.stdout is a pipe or not
            """
            try:
                fileno = sys.stdout.fileno()
            except (AttributeError, io.UnsupportedOperation):
                fileno = -1  # sys.stdout is StringIO or fake
            return not os.isatty(fileno)

        if opts.get("force_color", False):
            opts["color"] = True
        elif (
            opts.get("no_color", False) or is_pipe() or salt.utils.platform.is_windows()
        ):
            opts["color"] = False
        else:
            opts["color"] = True
    else:
        if opts.get("force_color", False):
            opts["color"] = True
        elif opts.get("no_color", False) or salt.utils.platform.is_windows():
            opts["color"] = False
        else:
            pass

    outputters = salt.loader.outputters(opts)
    if out not in outputters:
        # Since the grains outputter was removed we don't need to fire this
        # error when old minions are asking for it
        if out != "grains":
            log.error(
                "Invalid outputter %s specified, fall back to nested",
                out,
            )
        return outputters["nested"]
    return outputters[out]


def out_format(data, out, opts=None, **kwargs):
    """
    Return the formatted outputter string for the passed data
    """
    return try_printout(data, out, opts, **kwargs)


def string_format(data, out, opts=None, **kwargs):
    """
    Return the formatted outputter string, removing the ANSI escape sequences.
    """
    raw_output = try_printout(data, out, opts, **kwargs)
    ansi_escape = re.compile(r"\x1b[^m]*m")
    return ansi_escape.sub("", raw_output)


def html_format(data, out, opts=None, **kwargs):
    """
    Return the formatted string as HTML.
    """
    ansi_escaped_string = string_format(data, out, opts, **kwargs)
    return ansi_escaped_string.replace(" ", "&nbsp;").replace("\n", "<br />")


def strip_esc_sequence(txt):
    """
    Replace ESC (ASCII 27/Oct 33) to prevent unsafe strings
    from writing their own terminal manipulation commands
    """
    if isinstance(txt, str):
        return txt.replace("\033", "?")
    else:
        return txt
