from collections import OrderedDict

from conans.client.graph.graph import BINARY_SKIP
from conans.errors import ConanException
from conans.model.recipe_ref import RecipeReference
from conans.model.conanfile_interface import ConanFileInterface


class UserRequirementsDict(object):
    """ user facing dict to allow access of dependencies by name
    """
    def __init__(self, data, require_filter=None):
        self._data = data  # dict-like
        self._require_filter = require_filter  # dict {trait: value} for requirements

    def filter(self, require_filter):
        def filter_fn(require):
            for k, v in require_filter.items():
                if getattr(require, k) != v:
                    return False
            return True
        data = OrderedDict((k, v) for k, v in self._data.items() if filter_fn(k))
        return UserRequirementsDict(data, require_filter)

    def __bool__(self):
        return bool(self._data)

    def get(self, ref, build=None, **kwargs):
        if build is None:
            current_filters = self._require_filter or {}
            if "build" not in current_filters:
                # By default we search in the "host" context
                kwargs["build"] = False
        else:
            kwargs["build"] = build
        data = self.filter(kwargs)
        ret = []
        if "/" in ref:
            # FIXME: Validate reference
            ref = RecipeReference.loads(ref)
            for require, value in data.items():
                if require.ref == ref:
                    ret.append((require, value))
        else:
            name = ref
            for require, value in data.items():
                if require.ref.name == name:
                    ret.append((require, value))
        if len(ret) > 1:
            current_filters = data._require_filter or "{}"
            requires = "\n".join(["- {}".format(require) for require, _ in ret])
            raise ConanException("There are more than one requires matching the specified filters:"
                                 " {}\n{}".format(current_filters, requires))
        if not ret:
            raise KeyError("'{}' not found in the dependency set".format(ref))

        _, value = ret[0]
        return value

    def __getitem__(self, name):
        return self.get(name)

    def __delitem__(self, name):
        r = self.get(name)
        del self._data[r]

    def items(self):
        return self._data.items()

    def values(self):
        return self._data.values()


class ConanFileDependencies(UserRequirementsDict):

    @staticmethod
    def from_node(node):
        # TODO: Probably the BINARY_SKIP should be filtered later at the user level, not forced here
        d = OrderedDict((require, ConanFileInterface(transitive.node.conanfile))
                        for require, transitive in node.transitive_deps.items()
                        if transitive.node.binary != BINARY_SKIP)
        return ConanFileDependencies(d)

    def filter(self, require_filter):
        # FIXME: Copy of hte above, to return ConanFileDependencies class object
        def filter_fn(require):
            for k, v in require_filter.items():
                if getattr(require, k) != v:
                    return False
            return True

        data = OrderedDict((k, v) for k, v in self._data.items() if filter_fn(k))
        return ConanFileDependencies(data, require_filter)

    @property
    def topological_sort(self):
        # Return first independent nodes, final ones are the more direct deps
        result = OrderedDict()
        opened = self._data.copy()

        while opened:
            opened_values = set(opened.values())
            new_opened = OrderedDict()
            for req, conanfile in opened.items():
                deps_in_opened = any(d in opened_values for d in conanfile.dependencies.values())
                if deps_in_opened:
                    new_opened[req] = conanfile  # keep it for next iteration
                else:
                    result[req] = conanfile  # No dependencies in open set!

            opened = new_opened
        return ConanFileDependencies(result)

    @property
    def direct_host(self):
        return self.filter({"build": False, "direct": True, "test": False})

    @property
    def direct_build(self):
        return self.filter({"build": True, "direct": True})

    @property
    def host(self):
        return self.filter({"build": False, "test": False})

    @property
    def test(self):
        # Not needed a direct_test because they are visible=False so only the direct consumer
        # will have them in the graph
        return self.filter({"build": False, "test": True})

    @property
    def build(self):
        return self.filter({"build": True})
