"""
Recursively display nested data
===============================

This is the default outputter for most execution functions.

Example output::

    myminion:
        ----------
        foo:
            ----------
            bar:
                baz
            dictionary:
                ----------
                abc:
                    123
                def:
                    456
            list:
                - Hello
                - World
"""

from collections.abc import Mapping
from numbers import Number

import salt.output
import salt.utils.color
import salt.utils.odict
import salt.utils.stringutils


class NestDisplay:
    """
    Manage the nested display contents
    """

    def __init__(self, retcode=0):
        self.__dict__.update(
            salt.utils.color.get_colors(
                __opts__.get("color"), __opts__.get("color_theme")
            )
        )
        self.strip_colors = __opts__.get("strip_colors", True)
        self.retcode = retcode

    def ustring(self, indent, color, msg, prefix="", suffix="", endc=None):
        if endc is None:
            endc = self.ENDC

        indent *= " "
        fmt = "{0}{1}{2}{3}{4}{5}"

        try:
            return fmt.format(indent, color, prefix, msg, endc, suffix)
        except UnicodeDecodeError:
            try:
                return fmt.format(
                    indent,
                    color,
                    prefix,
                    salt.utils.stringutils.to_unicode(msg),
                    endc,
                    suffix,
                )
            except UnicodeDecodeError:
                # msg contains binary data that can't be decoded
                return str(fmt).format(indent, color, prefix, msg, endc, suffix)

    def display(self, ret, indent, prefix, out):
        """
        Recursively iterate down through data structures to determine output
        """
        if isinstance(ret, bytes):
            try:
                ret = salt.utils.stringutils.to_unicode(ret)
            except UnicodeDecodeError:
                # ret contains binary data that can't be decoded
                pass

        if ret is None or ret is True or ret is False:
            out.append(self.ustring(indent, self.LIGHT_YELLOW, ret, prefix=prefix))
        # Number includes all python numbers types
        #  (float, int, long, complex, ...)
        # use repr() to get the full precision also for older python versions
        # as until about python32 it was limited to 12 digits only by default
        elif isinstance(ret, Number):
            out.append(
                self.ustring(indent, self.LIGHT_YELLOW, repr(ret), prefix=prefix)
            )
        elif isinstance(ret, str):
            first_line = True
            for line in ret.splitlines():
                line_prefix = " " * len(prefix) if not first_line else prefix
                if isinstance(line, bytes):
                    out.append(
                        self.ustring(
                            indent, self.YELLOW, "Not string data", prefix=line_prefix
                        )
                    )
                    break
                if self.strip_colors:
                    line = salt.output.strip_esc_sequence(line)
                out.append(self.ustring(indent, self.GREEN, line, prefix=line_prefix))
                first_line = False
        elif isinstance(ret, (list, tuple)):
            color = self.GREEN
            if self.retcode != 0:
                color = self.RED
            for ind in ret:
                if isinstance(ind, (list, tuple, Mapping)):
                    out.append(self.ustring(indent, color, "|_"))
                    prefix = "" if isinstance(ind, Mapping) else "- "
                    self.display(ind, indent + 2, prefix, out)
                else:
                    self.display(ind, indent, "- ", out)
        elif isinstance(ret, Mapping):
            if indent:
                color = self.CYAN
                if self.retcode != 0:
                    color = self.RED
                out.append(self.ustring(indent, color, "----------"))

            # respect key ordering of ordered dicts
            if isinstance(ret, salt.utils.odict.OrderedDict):
                keys = ret.keys()
            else:
                try:
                    keys = sorted(ret)
                except TypeError:
                    # Some of the keys must be non-string types
                    ret = {str(k): v for k, v in ret.items()}
                    keys = sorted(ret)

            color = self.CYAN
            if self.retcode != 0:
                color = self.RED
            for key in keys:
                val = ret[key]
                out.append(self.ustring(indent, color, key, suffix=":", prefix=prefix))
                self.display(val, indent + 4, "", out)
        return out


def output(ret, **kwargs):
    """
    Display ret data
    """
    # Prefer kwargs before opts
    retcode = kwargs.get("_retcode", 0)
    base_indent = kwargs.get("nested_indent", 0) or __opts__.get("nested_indent", 0)
    nest = NestDisplay(retcode=retcode)
    lines = nest.display(ret, base_indent, "", [])
    try:
        return "\n".join(lines)
    except UnicodeDecodeError:
        # output contains binary data that can't be decoded
        return "\n".join([salt.utils.stringutils.to_str(x) for x in lines])
