import fnmatch
from collections import OrderedDict

from conans.client.output import Color
from conans.model.options import OptionsValues
from conans.model.ref import ConanFileReference
from conans.util.conan_v2_mode import conan_v2_error


class Printer(object):
    """ Print some specific information """

    INDENT_COLOR = {0: Color.BRIGHT_CYAN,
                    1: Color.BRIGHT_RED,
                    2: Color.BRIGHT_GREEN,
                    3: Color.BRIGHT_YELLOW,
                    4: Color.BRIGHT_MAGENTA}

    INDENT_SPACES = 4

    def __init__(self, out):
        self._out = out

    def print_inspect(self, inspect, raw=False):
        for k, v in inspect.items():
            if k == "default_options":
                conan_v2_error("Declare 'default_options' as a dictionary", not isinstance(v, dict))

                if isinstance(v, str):
                    v = OptionsValues.loads(v)
                elif isinstance(v, tuple):
                    v = OptionsValues(v)
                elif isinstance(v, list):
                    v = OptionsValues(tuple(v))
                elif isinstance(v, dict):
                    v = OptionsValues(v)

            if raw:
                self._out.write(str(v))
            else:
                if isinstance(v, (dict, OptionsValues)):
                    self._out.writeln("%s:" % k)
                    for ok, ov in sorted(v.items()):
                        self._out.writeln("    %s: %s" % (ok, ov))
                else:
                    self._out.writeln("%s: %s" % (k, str(v)))

    def print_info(self, data, _info, package_filter=None, show_paths=False, show_revisions=False):
        """ Print in console the dependency information for a conan file
        """
        if _info is None:  # No filter
            def show(_):
                return True
        else:
            _info_lower = [s.lower() for s in _info]

            def show(field):
                return field in _info_lower

        # Handy function to perform a common print task
        def _print(it_field, show_field=None, name=None, color=Color.BRIGHT_GREEN):
            show_field = show_field or it_field
            name = name or it_field
            if show(show_field) and it_field in it:
                self._out.writeln("    %s: %s" % (name, it[it_field]), color)

        # Iteration and printing
        for it in data:
            if package_filter and not fnmatch.fnmatch(it["reference"], package_filter):
                continue

            is_ref = it["is_ref"]

            self._out.writeln(it["display_name"], Color.BRIGHT_CYAN)
            _print("id", name="ID")
            _print("build_id", name="BuildID")
            _print("context", name="Context")
            if show_paths:
                _print("export_folder")
                _print("source_folder")
                _print("build_folder")
                _print("package_folder")

            if show("remote") and is_ref:
                if "remote" in it:
                    self._out.writeln("    Remote: %s=%s" % (it["remote"]["name"],
                                                             it["remote"]["url"]),
                                      Color.BRIGHT_GREEN)
                else:
                    self._out.writeln("    Remote: None", Color.BRIGHT_GREEN)

            _print("url", name="URL")
            _print("homepage", name="Homepage")

            if show("license") and "license" in it:
                licenses_str = ", ".join(it["license"])
                lead_str = "Licenses" if len(it["license"]) > 1 else "License"
                self._out.writeln("    %s: %s" % (lead_str, licenses_str), Color.BRIGHT_GREEN)

            _print("author", name="Author")
            _print("description", name="Description")

            if show("topics") and "topics" in it:
                self._out.writeln("    Topics: %s" % ", ".join(it["topics"]), Color.BRIGHT_GREEN)

            if show("provides") and "provides" in it:
                self._out.writeln("    Provides: %s" % ", ".join(it["provides"]), Color.BRIGHT_GREEN)

            _print("deprecated", name="Deprecated")

            _print("recipe", name="Recipe", color=None)
            if show_revisions:
                _print("revision", name="Revision", color=None)
                _print("package_revision", name="Package revision", color=None)
            _print("binary", name="Binary", color=None)

            if show("binary_remote") and is_ref:
                if "binary_remote" in it:
                    self._out.writeln("    Binary remote: %s" % it["binary_remote"])
                else:
                    self._out.writeln("    Binary remote: None")

            _print("creation_date", show_field="date", name="Creation date")

            _print("scm", show_field="scm", name="scm")

            if show("python_requires") and "python_requires" in it:
                self._out.writeln("    Python-requires:", Color.BRIGHT_GREEN)
                for d in it["python_requires"]:
                    self._out.writeln("        %s" % d, Color.BRIGHT_YELLOW)

            if show("required") and "required_by" in it:
                self._out.writeln("    Required by:", Color.BRIGHT_GREEN)
                for d in it["required_by"]:
                    self._out.writeln("        %s" % d, Color.BRIGHT_YELLOW)

            if show("requires"):
                if "requires" in it:
                    self._out.writeln("    Requires:", Color.BRIGHT_GREEN)
                    for d in it["requires"]:
                        self._out.writeln("        %s" % d, Color.BRIGHT_YELLOW)

                if "build_requires" in it:
                    self._out.writeln("    Build Requires:", Color.BRIGHT_GREEN)
                    for d in it["build_requires"]:
                        self._out.writeln("        %s" % d, Color.BRIGHT_YELLOW)

    def print_search_recipes(self, search_info, pattern, raw, all_remotes_search):
        """ Print all the exported conans information
        param pattern: wildcards, e.g., "opencv/*"
        """
        if not search_info and not raw:
            warn_msg = "There are no packages"
            pattern_msg = " matching the '%s' pattern" % pattern
            self._out.info(warn_msg + pattern_msg if pattern else warn_msg)
            return

        if not raw:
            self._out.info("Existing package recipes:\n")
            for remote_info in search_info:
                if all_remotes_search:
                    self._out.highlight("Remote '%s':" % str(remote_info["remote"]))
                for conan_item in remote_info["items"]:
                    reference = conan_item["recipe"]["id"]
                    ref = ConanFileReference.loads(reference)
                    self._print_colored_line(ref.full_str(), indent=0)
        else:
            for remote_info in search_info:
                if all_remotes_search:
                    self._out.writeln("Remote '%s':" % str(remote_info["remote"]))
                for conan_item in remote_info["items"]:
                    reference = conan_item["recipe"]["id"]
                    ref = ConanFileReference.loads(reference)
                    self._out.writeln(ref.full_str())

    def print_search_packages(self, search_info, ref, packages_query, raw, outdated=False):
        assert(isinstance(ref, ConanFileReference))
        if not raw:
            self._out.info("Existing packages for recipe %s:\n" % str(ref))
        for remote_info in search_info:
            if remote_info["remote"] and not raw:
                self._out.info("Existing recipe in remote '%s':\n" % remote_info["remote"])

            if not remote_info["items"][0]["packages"]:
                if packages_query:
                    warn_msg = "There are no %spackages for reference '%s' matching the query '%s'" \
                               % ("outdated " if outdated else "", str(ref), packages_query)
                elif remote_info["items"][0]["recipe"]:
                    warn_msg = "There are no %spackages for reference '%s', but package recipe " \
                               "found." % ("outdated " if outdated else "", str(ref))
                if not raw:
                    self._out.info(warn_msg)
                continue

            ref = remote_info["items"][0]["recipe"]["id"]
            packages = remote_info["items"][0]["packages"]

            # Each package
            for package in packages:
                package_id = package["id"]
                self._print_colored_line("Package_ID", package_id, 1)
                for section in ("options", "settings", "requires"):
                    attr = package[section]
                    if attr:
                        self._print_colored_line("[%s]" % section, indent=2)
                        if isinstance(attr, dict):  # options, settings
                            attr = OrderedDict(sorted(attr.items()))
                            for key, value in attr.items():
                                self._print_colored_line(key, value=value, indent=3)
                        elif isinstance(attr, list):  # full requires
                            for key in sorted(attr):
                                self._print_colored_line(key, indent=3)
                # Always compare outdated with local recipe, simplification,
                # if a remote check is needed install recipe first
                if "outdated" in package:
                    self._print_colored_line("Outdated from recipe: %s" % package["outdated"],
                                             indent=2)
                self._out.writeln("")

    def print_profile(self, name, profile):
        self._out.info("Configuration for profile %s:\n" % name)
        self._print_profile_section("settings", profile.settings.items(), separator="=")
        self._print_profile_section("options", profile.options.as_list(), separator="=")
        self._print_profile_section("conf", profile.conf.as_list(), separator="=")
        self._print_profile_section("build_requires", [(key, ", ".join(str(val) for val in values))
                                                       for key, values in
                                                       profile.build_requires.items()])

        envs = []
        for package, env_vars in profile.env_values.data.items():
            for name, value in env_vars.items():
                key = "%s:%s" % (package, name) if package else name
                envs.append((key, value))
        self._print_profile_section("env", envs, separator='=')

    def _print_profile_section(self, name, items, indent=0, separator=": "):
        self._print_colored_line("[%s]" % name, indent=indent, color=Color.BRIGHT_RED)
        for key, value in items:
            self._print_colored_line(key, value=str(value), indent=0, separator=separator)

    def _print_colored_line(self, text, value=None, indent=0, separator=": ", color=None):
        """ Print a colored line depending on its indentation level
            Attributes:
                text: string line
                split_symbol: if you want an output with different in-line colors
                indent_plus: integer to add a plus indentation
        """
        text = text.strip()
        if not text:
            return

        text_color = Printer.INDENT_COLOR.get(indent, Color.BRIGHT_WHITE) if not color else color
        indent_text = ' ' * Printer.INDENT_SPACES * indent
        if value is not None:
            value_color = Color.BRIGHT_WHITE
            self._out.write('%s%s%s' % (indent_text, text, separator), text_color)
            self._out.writeln(value, value_color)
        else:
            self._out.writeln('%s%s' % (indent_text, text), text_color)
