import time

from conans.client.conanfile.configure import run_configure_method
from conans.client.graph.graph import DepsGraph, Node, RECIPE_EDITABLE, CONTEXT_HOST, CONTEXT_BUILD
from conans.errors import (ConanException, ConanExceptionInUserConanfileMethod,
                           conanfile_exception_formatter)
from conans.model.conan_file import get_env_context_manager
from conans.model.ref import ConanFileReference
from conans.model.requires import Requirements, Requirement
from conans.util.log import logger


class DepsGraphBuilder(object):
    """
    This is a summary, in pseudo-code of the execution and structure of the graph
    resolution algorithm

    load_graph(root_node)
        init root_node
        expand_node(root_node)
            # 1. Evaluate requirements(), overrides, and version ranges
            get_node_requirements(node)
                node.conanfile.requirements()                         # call the requirements()
                resolve_cached_alias(node.conanfile.requires)         # replace cached alias
                update_requires_from_downstream(down_requires)        # process overrides
                resolve_ranges(node)                                  # resolve version-ranges
                    resolve_cached_alias(node.conanfile.requires)     # replace cached alias again

            # 2. Process each requires of this node
            for req in node.conanfile.requires:
                expand_require(req)
                    if req.name not in graph:                         # New node
                        new_node = create_new_node(req)               # fetch and load conanfile.py
                            if alias => create_new_node(alias)        # recurse alias
                        expand_node(new_node)                         # recursion
                    else:                                             # Node exists, closing diamond
                        resolve_cached_alias(req)
                        check_conflicts(req)                          # diamonds can cause conflicts
                        if need_recurse:                              # check for conflicts upstream
                            expand_node(previous_node)                # recursion
    """

    def __init__(self, proxy, output, loader, resolver, recorder):
        self._proxy = proxy
        self._output = output
        self._loader = loader
        self._resolver = resolver
        self._recorder = recorder

    def load_graph(self, root_node, check_updates, update, remotes, profile_host, profile_build,
                   graph_lock=None):
        check_updates = check_updates or update
        initial = graph_lock.initial_counter if graph_lock else None
        dep_graph = DepsGraph(initial_node_id=initial)
        # compute the conanfile entry point for this dependency graph
        root_node.public_closure.add(root_node)
        root_node.public_deps.add(root_node)
        root_node.transitive_closure[root_node.name] = root_node
        if profile_build:
            root_node.conanfile.settings_build = profile_build.processed_settings.copy()
            root_node.conanfile.settings_target = None
        dep_graph.add_node(root_node)

        # enter recursive computation
        t1 = time.time()
        self._expand_node(root_node, dep_graph, Requirements(), None, None, check_updates,
                          update, remotes, profile_host, profile_build, graph_lock)
        logger.debug("GRAPH: Time to load deps %s" % (time.time() - t1))
        return dep_graph

    def extend_build_requires(self, graph, node, build_requires_refs, check_updates, update,
                              remotes, profile_host, profile_build, graph_lock):
        # The options that will be defined in the node will be the real options values that have
        # been already propagated downstream from the dependency graph. This will override any
        # other possible option in the build_requires dependency graph. This means that in theory
        # an option conflict while expanding the build_requires is impossible
        node.conanfile.build_requires_options.clear_unscoped_options()
        new_options = node.conanfile.build_requires_options._reqs_options
        new_reqs = Requirements()

        conanfile = node.conanfile
        scope = conanfile.display_name

        build_requires = []
        for ref, context in build_requires_refs:
            r = Requirement(ref)
            r.build_require = True
            r.build_require_context = context
            r.force_host_context = getattr(ref, "force_host_context", False)
            build_requires.append(r)

        if graph_lock:
            graph_lock.pre_lock_node(node)
            # TODO: Add info about context?
            graph_lock.lock_node(node, build_requires, build_requires=True)

        for require in build_requires:
            self._resolve_alias(node, require, graph, update, update, remotes)
        self._resolve_ranges(graph, build_requires, scope, update, remotes)

        for br in build_requires:
            context_switch = bool(br.build_require_context == CONTEXT_BUILD)
            populate_settings_target = context_switch  # Avoid 'settings_target' for BR-host
            self._expand_require(br, node, graph, check_updates, update,
                                 remotes, profile_host, profile_build, new_reqs, new_options,
                                 graph_lock, context_switch=context_switch,
                                 populate_settings_target=populate_settings_target)

        new_nodes = set(n for n in graph.nodes if n.package_id is None)
        # This is to make sure that build_requires have precedence over the normal requires
        node.public_closure.sort(key_fn=lambda x: x not in new_nodes)
        return new_nodes

    def _expand_node(self, node, graph, down_reqs, down_ref, down_options, check_updates, update,
                     remotes, profile_host, profile_build, graph_lock):
        """ expands the dependencies of the node, recursively

        param node: Node object to be expanded in this step
        down_reqs: the Requirements as coming from downstream, which can overwrite current
                    values
        param down_ref: ConanFileReference of who is depending on current node for this expansion
        """