from __future__ import print_function

import os
import sys
import six
import psutil
import rdflib
# Probably needed to force rdflib to load its plugins ?
# Apparently, this has to be loaded explicitly.
# Surprisingly it was not needed until this commit.
import rdflib.plugins.memory
import rdflib.plugins.sparql

import lib_util
import lib_common
import lib_ontology_tools
import lib_kbase
import lib_properties

################################################################################

# FIXME: This url is often hardcoded in the tests.
survol_url = "http://www.primhillcomputers.com/survol#"
class_CIM_Process = rdflib.term.URIRef(survol_url + "CIM_Process")
class_CIM_Directory = rdflib.term.URIRef(survol_url + "CIM_Directory")
class_CIM_DataFile = rdflib.term.URIRef(survol_url + "CIM_DataFile")

predicate_Handle = rdflib.term.URIRef(survol_url + "Handle")
predicate_Name = rdflib.term.URIRef(survol_url + "Name")

# This is not part of the ontology but allows to return several processes,
# based on their parent process id.
predicate_ParentProcessId = rdflib.term.URIRef(survol_url + "ParentProcessId")

associator_CIM_DirectoryContainsFile = rdflib.term.URIRef(survol_url + "CIM_DirectoryContainsFile")
associator_CIM_ProcessExecutable = rdflib.term.URIRef(survol_url + "CIM_ProcessExecutable")


def add_ontology(graph):
    graph.add((class_CIM_Process, rdflib.namespace.RDF.type, rdflib.namespace.RDFS.Class))
    graph.add((class_CIM_Process, rdflib.namespace.RDFS.label, rdflib.Literal("CIM_Process")))
    graph.add((class_CIM_Directory, rdflib.namespace.RDF.type, rdflib.namespace.RDFS.Class))
    graph.add((class_CIM_Directory, rdflib.namespace.RDFS.label, rdflib.Literal("CIM_Directory")))
    graph.add((class_CIM_DataFile, rdflib.namespace.RDF.type, rdflib.namespace.RDFS.Class))
    graph.add((class_CIM_DataFile, rdflib.namespace.RDFS.label, rdflib.Literal("CIM_DataFile")))

    graph.add((predicate_Handle, rdflib.namespace.RDF.type, rdflib.namespace.RDF.Property))
    graph.add((predicate_Handle, rdflib.namespace.RDFS.domain, class_CIM_Process))
    graph.add((predicate_Handle, rdflib.namespace.RDFS.range, rdflib.namespace.XSD.integer))
    graph.add((predicate_Handle, rdflib.namespace.RDFS.label, rdflib.Literal("Handle")))
    graph.add((predicate_Handle, rdflib.namespace.RDFS.range, rdflib.namespace.XSD.integer))

    graph.add((predicate_ParentProcessId, rdflib.namespace.RDF.type, rdflib.namespace.RDF.Property))
    graph.add((predicate_ParentProcessId, rdflib.namespace.RDFS.domain, class_CIM_Process))
    graph.add((predicate_ParentProcessId, rdflib.namespace.RDFS.range, rdflib.namespace.XSD.string))
    graph.add((predicate_ParentProcessId, rdflib.namespace.RDFS.label, rdflib.Literal("ParentProcessId")))

    graph.add((predicate_Name, rdflib.namespace.RDF.type, rdflib.namespace.RDF.Property))
    graph.add((predicate_Name, rdflib.namespace.RDFS.domain, class_CIM_Directory))
    graph.add((predicate_Name, rdflib.namespace.RDFS.domain, class_CIM_DataFile))
    graph.add((predicate_Name, rdflib.namespace.RDFS.range, rdflib.namespace.XSD.string))
    graph.add((predicate_Name, rdflib.namespace.RDFS.label, rdflib.Literal("Name")))

    graph.add((associator_CIM_DirectoryContainsFile, rdflib.namespace.RDF.type, rdflib.namespace.RDF.Property))
    graph.add((associator_CIM_DirectoryContainsFile, rdflib.namespace.RDFS.domain, class_CIM_Directory))
    graph.add((associator_CIM_DirectoryContainsFile, rdflib.namespace.RDFS.range, class_CIM_DataFile))
    graph.add((associator_CIM_DirectoryContainsFile, rdflib.namespace.RDFS.range, class_CIM_Directory))
    graph.add((associator_CIM_DirectoryContainsFile, rdflib.namespace.RDFS.range, rdflib.namespace.XSD.string))
    graph.add((associator_CIM_DirectoryContainsFile, rdflib.namespace.RDFS.label, rdflib.Literal("CIM_DirectoryContainsFile")))

    graph.add((associator_CIM_ProcessExecutable, rdflib.namespace.RDF.type, rdflib.namespace.RDF.Property))
    graph.add((associator_CIM_ProcessExecutable, rdflib.namespace.RDFS.domain, class_CIM_Process))
    graph.add((associator_CIM_ProcessExecutable, rdflib.namespace.RDFS.range, class_CIM_DataFile))
    graph.add((associator_CIM_ProcessExecutable, rdflib.namespace.RDFS.label, rdflib.Literal("CIM_ProcessExecutable")))


is_platform_windows = sys.platform.startswith("win32")
is_platform_linux = sys.platform.startswith("linux")


def equal_paths(path_a, path_b):
    # print("process_executable=", process_executable, "executable_path=", executable_path)
    # With pytest as a command line: "c:\python27\python.exe" != "C:\Python27\python.exe"
    if is_platform_linux:
        return path_a == path_b
    elif is_platform_windows:
        return path_a.upper() == path_b.upper()
    else:
        raise Exception("Invalid platform")


def is_usable_file(file_path):
    """For testing and debugging purpose."""
    if os.path.isfile(file_path):
        return True
    if is_platform_linux:
        if os.path.islink(file_path):
            return True
    return False


def current_function():
    """For debugging purpose."""
    return sys._getframe(1).f_code.co_name

################################################################################


class Sparql_CIM_Object(object):
    def __init__(self, class_name, key_variable, ontology_keys):
        self.m_variable = key_variable
        self.m_class_name = class_name
        self.m_associators = {}
        self.m_associated = {}
        self.m_properties = {}
        # For example ["Name", "Domain"]
        self._m_ontology_keys = ontology_keys
        # print("__init__ ontology_keys=", ontology_keys)

    def __str__(self):
        def kw_to_str(property, value):
            property_str = str(property)[len(survol_url):]
            value_str = str(value)
            return "%s=%s" % (property_str, value_str)

        # print("ka=", self.m_known_attributes.items())
        # This takes only the properties which are part of the ontology keys.
        kw_pairs_subset = [
            kw_to_str(property_key, self.m_properties.get(property_key, "UNKNOWN"))
            for property_key in self._m_ontology_keys]

        kw = ".".join(kw_pairs_subset)
        return "Sparql_CIM_Object:" + self.m_class_name + ":" + self.m_variable + ":" + kw

    def FetchAllVariables(self, graph, variables_context):
        """
        The role of this virtual function is to return a dictionary of pairs made of a variable,
        or a tuple of variables, and a list of its possible values, or a tuple of list of values.
        The caller uses these key-value paris to create variables context and recursively instantiate objects,
        by trying all combinations.
        If variables are grouped in tuples, it means that they are correlated: For example a file path and its node id,
        or a process id and its command line.
        """
        sys.stderr.write("FetchAllVariables not implemented\n")
        raise NotImplementedError(current_function())

    def CalculateVariablesNumber(self):
        self.m_number_variables = 0
        self.m_number_literals = 0
        # FIXME: No need to list the associators which contains only instances. Logic should be different.
        for one_dict in [self.m_associators, self.m_associated, self.m_properties]:
            for key, value in one_dict.items():
                if isinstance(value, rdflib.term.Variable):
                    self.m_number_variables += 1
                elif isinstance(value, rdflib.term.Literal):
                    self.m_number_literals += 1

    def GetNodeValue(self, predicate_node, variables_context):
        try:
            predicate_variable = self.m_properties[predicate_node]
        except KeyError:
            sys.stderr.write("GetNodeValue %s not in %s. variables_context.keys=%s\n"
                             % (predicate_node, str(self.m_properties), str(variables_context.keys())))
            raise
        if isinstance(predicate_variable, rdflib.term.Literal):
            node_value = predicate_variable
        elif isinstance(predicate_variable, rdflib.term.Variable):
            if predicate_variable not in variables_context:
                sys.stderr.write("GetNodeValue QUIT: %s not in%s\n" % (predicate_variable, str(variables_context.keys())))
                return None
            node_value = variables_context[predicate_variable]
            sys.stderr.write("predicate_variable=%s node_value=%s\n" % (predicate_variable, node_value))
            assert isinstance(node_value, rdflib.term.Literal)
        return node_value


class Sparql_CIM_DataFile(Sparql_CIM_Object):
    def __init__(self, class_name, node):
        """ Same key "Name" for its base class CIM_Directory. """
        super(Sparql_CIM_DataFile, self).__init__(class_name, node, ["Name"])

    def CreateFileNodeFromProperties(self, variables_context):
        """
        This returns the node of the filename which uniquely identifies the object. It uses the literal properties,
        or the variable properties if these variables have a value in the context.
        This returns None if it cannot be done.
        """

        #sys.stderr.write("Sparql_CIM_DataFile.CreateFileNodeFromProperties\n")
        if predicate_Name in self.m_properties:
            # The path name is enough to fully define a data file or a directory.
            return self.GetNodeValue(predicate_Name, variables_context)
        else:
            sys.stderr.write("Sparql_CIM_DataFile QUIT: No Name\n")
            return None

    def FetchFromDirectory(self, variables_context, file_path, graph, returned_variables, node_uri_ref):
        check_returned_variables(returned_variables)
        #sys.stderr.write("Sparql_CIM_DataFile.FetchFromDirectory file_path=%s\n" % file_path)
        if associator_CIM_DirectoryContainsFile in self.m_associated:
            associator_instance = self.m_associated[associator_CIM_DirectoryContainsFile]
            assert isinstance(associator_instance, Sparql_CIM_Directory)
            assert isinstance(associator_instance.m_variable, rdflib.term.Variable)

            if associator_instance.m_variable in variables_context:
                sys.stderr.write("ALREADY DEFINED ?? %s\n" % associator_instance.m_variable)
                return

            dir_file_path = lib_util.standardized_file_path(os.path.dirname(file_path))
            dir_file_path_node = rdflib.term.Literal(dir_file_path)

            dir_node_str = "Machine:CIM_Directory?Name=" + dir_file_path
            associator_instance_url = rdflib.term.URIRef(dir_node_str)
            graph.add((associator_instance_url, rdflib.namespace.RDF.type, class_CIM_Directory))
            graph.add((associator_instance_url, associator_CIM_DirectoryContainsFile, node_uri_ref))

            if predicate_Name in associator_instance.m_properties:
                dir_path_variable = associator_instance.m_properties[predicate_Name]
                assert isinstance(dir_path_variable, rdflib.term.Variable)
            else:
                # This property must be created, to make the directory usable,
                # for example to get its other properties.
                # Generally speaking, this must be done for all properties of the ontology.
                variable_name = str(associator_instance.m_variable) + "_dummy_name"
                dir_path_variable = rdflib.term.Variable(variable_name)
                associator_instance.m_properties[predicate_Name] = dir_path_variable

            if isinstance(dir_path_variable, rdflib.term.Variable):
                returned_variables[(associator_instance.m_variable, dir_path_variable)] = [(associator_instance_url, dir_file_path_node)]
                check_returned_variables(returned_variables)
            else:
                returned_variables[associator_instance.m_variable] = [associator_instance_url]
            check_returned_variables(returned_variables)
            graph.add((associator_instance_url, predicate_Name, dir_file_path_node))

    def FetchAllVariables(self, graph, variables_context):
        node_file_path = self.CreateFileNodeFromProperties(variables_context)
        if not node_file_path:
            return {}
        file_path = str(node_file_path)
        returned_variables = {}

        url_as_str = "Machine:CIM_DataFile?Name=" + file_path
        node_uri_ref = rdflib.term.URIRef(url_as_str)
        graph.add((node_uri_ref, rdflib.namespace.RDF.type, class_CIM_DataFile))

        returned_variables[(self.m_variable,)] = [(node_uri_ref,)]
        check_returned_variables(returned_variables)

        # No need to add node_file_path in the results because,
        # if it is a Variable, it is already in the context.
        assert isinstance(self.m_variable, rdflib.term.Variable)
        assert node_file_path
        graph.add((node_uri_ref, predicate_Name, node_file_path))

        assert associator_CIM_DirectoryContainsFile not in self.m_associators

        self.FetchFromDirectory(variables_context, file_path, graph, returned_variables, node_uri_ref)

        # TODO: If there are no properties and no directory, this should return ALL FILES OF THE FILE SYSTEM.

        check_returned_variables(returned_variables)
        return returned_variables


class Sparql_CIM_Directory(Sparql_CIM_DataFile):
    def __init__(self, class_name, node):
        super(Sparql_CIM_Directory, self).__init__(class_name, node)

    def FetchAllVariables(self, graph, variables_context):
        node_file_path = self.CreateFileNodeFromProperties(variables_context)
        if not node_file_path:
            #sys.stderr.write("Sparql_CIM_Directory.FetchAllVariables LEAVING DOING NOTHING !!!!!\n")
            return {}
        file_path = str(node_file_path)
        returned_variables = {}

        url_as_str = "Machine:CIM_Directory?Name=" + file_path
        node_uri_ref = rdflib.term.URIRef(url_as_str)

        assert isinstance(self.m_variable, rdflib.term.Variable)
        returned_variables[(self.m_variable,)] = [(node_uri_ref,)]
        check_returned_variables(returned_variables)
        graph.add((node_uri_ref, rdflib.namespace.RDF.type, class_CIM_Directory))

        # No need to add node_file_path in the results=:
        # This Variable is already in the context.
        assert node_file_path
        graph.add((node_uri_ref, predicate_Name, node_file_path))

        self.FetchFromDirectory(variables_context, file_path, graph, returned_variables, node_uri_ref)
        check_returned_variables(returned_variables)

        if associator_CIM_DirectoryContainsFile in self.m_associators:
            associated_instance = self.m_associators[associator_CIM_DirectoryContainsFile]
            assert isinstance(associated_instance, (Sparql_CIM_DataFile, Sparql_CIM_Directory))
            assert isinstance(associated_instance.m_variable, rdflib.term.Variable)

            return_values_list = []

            if predicate_Name in associated_instance.m_properties:
                dir_path_variable = associated_instance.m_properties[predicate_Name]
                #sys.stderr.write("dir_path_variable=%s\n" % dir_path_variable)
                #sys.stderr.write("Sparql_CIM_Directory.FetchAllVariables dir_path_variable=%s\n" % dir_path_variable)
            else:
                # This creates a temporary variable to store the name because
                # it might be necessary to identify this associated instance.
                # This is needed for all properties of the ontology.
                variable_name = str(associated_instance.m_variable) + "_dummy_subname"
                dir_path_variable = rdflib.term.Variable(variable_name)
                associated_instance.m_properties[predicate_Name] = dir_path_variable
                #sys.stderr.write("Sparql_CIM_Directory.FetchAllVariables Created dummy variable:%s\n" % variable_name)

            def add_sub_node(sub_node_str, cim_class, sub_path_name):
                # print("Sparql_CIM_Directory.FetchAllVariables add_sub_node ", sub_node_str, "sub_path_name=", sub_path_name)
                # WARNING("Sparql_CIM_Directory.FetchAllVariables add_sub_node %s / path=%s" % (sub_node_str, sub_path_name))
                assert cim_class in (class_CIM_Directory, class_CIM_DataFile)
                sub_node_uri_ref = rdflib.term.URIRef(sub_node_str)
                graph.add((sub_node_uri_ref, rdflib.namespace.RDF.type, cim_class))
                #sub_uri_ref_list.append(sub_node_uri_ref)
                sub_path_name_url = rdflib.term.Literal(sub_path_name)
                graph.add((sub_node_uri_ref, predicate_Name, sub_path_name_url))
                graph.add((node_uri_ref, associator_CIM_DirectoryContainsFile, sub_node_uri_ref))

                if isinstance(dir_path_variable, rdflib.term.Variable):
                    return_values_list.append((sub_node_uri_ref, sub_path_name_url))
                else:
                    return_values_list.append((sub_node_uri_ref,))
                    assert isinstance(dir_path_variable, rdflib.term.Literal)
                    #print("Associated object Name is literal:", dir_path_variable)

            #sys.stderr.write("Sparql_CIM_Directory.FetchAllVariables file_path=%s\n" % file_path)
            for root_dir, dir_lists, files_list in os.walk(file_path):
                if associated_instance.m_class_name == "CIM_Directory":
                    for one_file_name in dir_lists:
                        sub_path_name = lib_util.standardized_file_path(os.path.join(root_dir, one_file_name))
                        # This must be a directory, possibly unreadable due to access rights.
                        assert os.path.isdir(sub_path_name)
                        sub_node_str = "Machine:CIM_Directory?Name=" + sub_path_name
                        add_sub_node(sub_node_str, class_CIM_Directory, sub_path_name)
                elif associated_instance.m_class_name == "CIM_DataFile":
                    for one_file_name in files_list:
                        sub_path_name = lib_util.standardized_file_path(os.path.join(root_dir, one_file_name))
                        #sys.stderr.write("sub_path_name=%s\n" % sub_path_name)
                        # This must be a file, possibly unreadable due to access rights, or a symbolic link.
                        assert is_usable_file(sub_path_name)

                        sub_node_str = "Machine:CIM_DataFile?Name=" + sub_path_name
                        add_sub_node(sub_node_str, class_CIM_DataFile, sub_path_name)
                else:
                    raise Exception("Cannot happen")
                # Loop on first level only.
                break

            #sys.stderr.write("Sparql_CIM_Directory.FetchAll return_values_list=%s\n" % return_values_list)
            if isinstance(dir_path_variable, rdflib.term.Variable):
                #sys.stderr.write("Sparql_CIM_Directory.FetchAllVariables Returning variables pair:%s\n" % associated_instance.m_variable)
                returned_variables[(associated_instance.m_variable, dir_path_variable)] = return_values_list
                check_returned_variables(returned_variables)
            else:
                #sys.stderr.write("Sparql_CIM_Directory.FetchAllVariables Returning variable:%s\n" % associated_instance.m_variable)
                returned_variables[(associated_instance.m_variable,)] = return_values_list
                check_returned_variables(returned_variables)

            #sys.stderr.write("Sparql_CIM_Directory.FetchAllVariables returned_variables=%s\n" % returned_variables)

        # TODO: If there are no properties and no directory and no sub-files or sub-directories,
        # TODO: this should return ALL DIRECTORIES OF THE FILE SYSTEM.

        return returned_variables


class Sparql_CIM_Process(Sparql_CIM_Object):
    def __init__(self, class_name, node):
        super(Sparql_CIM_Process, self).__init__(class_name, node, ["Handle"])

    # Several properties can be used to return one or several objects.
    # TODO: We could use several properties at one: It is difficult to generalise.
    class PropertyDefinition:
        class property_definition_Handle:
            s_property_node = predicate_Handle

            def IfLiteralOrDefinedVariable(self, node_value):
                process_id = int(str(node_value))
                parent_process_pid =  psutil.Process(process_id).ppid()
                #parent_process_pid_node = rdflib.term.Literal(parent_process_pid)

                return (predicate_Handle, predicate_ParentProcessId), [(node_value, rdflib.term.Literal(parent_process_pid))]

        class property_definition_ParentProcessId:
            s_property_node = predicate_ParentProcessId

            def IfLiteralOrDefinedVariable(self, node_value):
                ppid = int(str(node_value))
                list_values = [
                    (rdflib.term.Literal(child_process.pid), node_value)
                    for child_process in psutil.Process(ppid).children(recursive=False)]
                return (predicate_Handle, predicate_ParentProcessId), list_values

        g_properties = [
            property_definition_Handle(),
            property_definition_ParentProcessId()
        ]

    def GetAllObjects(self):
        """If no property is usable, this returns all objects.
        This can work only for some classes, if there are not too many objects."""
        sys.stderr.write("Sparql_CIM_Process.GetAllObjects: Getting all processes\n")
        result_list = []

        list_values = [
            (rdflib.term.Literal(proc.pid), rdflib.term.Literal(proc.ppid()))
            for proc in psutil.process_iter()]
        return (predicate_Handle, predicate_ParentProcessId), list_values

    def GetListOfOntologyProperties(self, variables_context):
        """
        This returns key-value pairs defining objects.
        It returns all properties defined in the instance,
        not only the properties of the ontology."""
        sys.stderr.write("GetListOfOntologyProperties\n")
        for one_property in self.PropertyDefinition.g_properties:
            sys.stderr.write("    GetListOfOntologyProperties one_property=%s\n" % one_property.s_property_node)
            if one_property.s_property_node in self.m_properties:
                node_value = self.GetNodeValue(one_property.s_property_node, variables_context)
                if node_value:
                    assert isinstance(node_value, rdflib.term.Literal)
                    url_nodes_list = one_property.IfLiteralOrDefinedVariable(node_value)
                    return url_nodes_list
        sys.stderr.write("GetListOfOntologyProperties leaving: Cannot find anything.\n")
        return None, None

    def CreateURIRef(self, graph, class_name, class_node, dict_predicates_to_values):
        url_as_str = "Machine:" + class_name
        delimiter = "?"
        for node_predicate, node_value in dict_predicates_to_values.items():
            predicate_name = str(node_predicate)[len(survol_url):]
            str_value = str(node_value)
            url_as_str += delimiter + "%s=%s" % (predicate_name, str_value)
            delimiter = "."
        sys.stderr.write("CreateURIRef url_as_str=%s\n" % url_as_str)
        node_uri_ref = rdflib.term.URIRef(url_as_str)
        graph.add((node_uri_ref, rdflib.namespace.RDF.type, class_node))

        for node_predicate, node_value in dict_predicates_to_values.items():
            assert isinstance(node_value, rdflib.term.Literal)
            graph.add((node_uri_ref, node_predicate, node_value))
        return node_uri_ref

    def DefineExecutableFromProcess(self, variables_context, process_id, graph, returned_variables, node_uri_ref):
        """Given the process id, it creates the file representing the executable being run."""
        sys.stderr.write("Sparql_CIM_Process.DefineExecutableFromProcess process_id=%d\n" % process_id)
        if associator_CIM_ProcessExecutable in self.m_associators:
            associated_instance = self.m_associators[associator_CIM_ProcessExecutable]
            assert isinstance(associated_instance, Sparql_CIM_DataFile)
            assert isinstance(associated_instance.m_variable, rdflib.term.Variable)
            assert isinstance(process_id, int)

            # This calculates the variable which defines the executable node.
            assert associated_instance.m_variable not in variables_context

            # TODO: This could also explore DLLs, not only the main executable.
            executable_path = lib_util.standardized_file_path(psutil.Process(process_id).exe())
            #executable_path = psutil.Process(process_id).exe()
            executable_path_node = rdflib.term.Literal(executable_path)

            associated_instance_url = self.CreateURIRef(graph, "CIM_DataFile", class_CIM_DataFile,
                         {predicate_Name: executable_path_node})

            graph.add((node_uri_ref, associator_CIM_ProcessExecutable, associated_instance_url))

            if predicate_Name in associated_instance.m_properties:
                dir_path_variable = associated_instance.m_properties[predicate_Name]
                assert isinstance(dir_path_variable, rdflib.term.Variable)
            else:
                # This property must be created, to make the directory usable,
                # for example to get its other properties.
                # Generally speaking, this must be done for all properties of the ontology.
                variable_name = str(associated_instance.m_variable) + "_dummy_name"
                dir_path_variable = rdflib.term.Variable(variable_name)
                associated_instance.m_properties[predicate_Name] = dir_path_variable

            if isinstance(dir_path_variable, rdflib.term.Variable):
                assert (associated_instance, dir_path_variable) not in returned_variables
                returned_variables[(associated_instance.m_variable, dir_path_variable)] = [
                    (associated_instance_url, executable_path_node)]
                check_returned_variables(returned_variables)
            else:
                assert associated_instance.m_variable not in returned_variables
                returned_variables[associated_instance.m_variable] = [associated_instance_url]
                check_returned_variables(returned_variables)
            graph.add((associated_instance_url, predicate_Name, executable_path_node))

    def GetProcessesFromExecutable(self, graph, variables_context):
        """Given a file name, it returns all processes executing it."""

        #sys.stderr.write("Sparql_CIM_Process.GetProcessesFromExecutable executable=%s\n" % sys.executable)
        associated_instance = self.m_associators[associator_CIM_ProcessExecutable]
        assert isinstance(associated_instance, Sparql_CIM_DataFile)
        assert isinstance(associated_instance.m_variable, rdflib.term.Variable)
        assert associated_instance.m_variable in variables_context
        associated_executable_node = variables_context[associated_instance.m_variable]
        assert isinstance(associated_executable_node, rdflib.term.URIRef)

        #sys.stderr.write("Sparql_CIM_Process.GetProcessesFromExecutable variables_context=%s\n" % variables_context)
        #sys.stderr.write("associated_instance.m_variable=%s\n" % associated_instance.m_variable)
        executable_node = associated_instance.GetNodeValue(predicate_Name, variables_context)
        assert isinstance(executable_node, rdflib.term.Literal)
        executable_path = str(executable_node)

        # Because of backslashes transformed into slashes, which is necessary because of Sparql.
        #executable_path = os.path.normpath(executable_path)
        #if is_platform_linux:
        #    executable_path = os.path.realpath(executable_path)
        #    #sys.stderr.write("executable_path=%s\n" % executable_path)
        executable_path = lib_util.standardized_file_path(executable_path)

        process_urls_list = []
        for one_process in psutil.process_iter():
            try:
                process_executable = lib_util.standardized_file_path(one_process.exe())
            except psutil.AccessDenied as exc:
                sys.stderr.write("GetProcessesFromExecutable Caught:%s\n" % str(exc))
                continue
            # print("process_executable=", process_executable, "executable_path=", executable_path)
            # With pytest as a command line: "c:\python27\python.exe" != "C:\Python27\python.exe"

            # On Linux, it might be a symbolic link: /usr/bin/python3 and /usr/bin/python3.6
            if equal_paths(executable_path, process_executable):
                process_url = self.CreateURIRef(
                    graph, "CIM_Process", class_CIM_Process,
                    {predicate_Handle: rdflib.term.Literal(one_process.pid)})
                #sys.stderr.write("Adding process %s\n" % str(process_url))
                graph.add((process_url, associator_CIM_ProcessExecutable, associated_executable_node))
                process_urls_list.append(process_url)
        #sys.stderr.write("GetProcessesFromExecutable process_urls_list=%\n" % str(process_urls_list))
        return process_urls_list

    def FetchAllVariables(self, graph, variables_context):
        #sys.stderr.write("Sparql_CIM_Process.FetchAllVariables variables_context=%s\n" % str(variables_context))
        properties_tuple, url_nodes_list = self.GetListOfOntologyProperties(variables_context)

        returned_variables = {}

        if isinstance(url_nodes_list, list) and len(url_nodes_list) == 0:
            #sys.stderr.write("FetchAllVariables No such process with self.m_properties:%s\n" % str(self.m_properties))
            # No such process.
            return returned_variables

        # If no process was found with the properties, try with the associator.
        if associator_CIM_ProcessExecutable in self.m_associators:
            associated_instance = self.m_associators[associator_CIM_ProcessExecutable]
            if associated_instance.m_variable in variables_context:
                if url_nodes_list is not None:
                    raise Exception("BUG: Contradiction with non-empty processes list")
                node_uri_refs_list = self.GetProcessesFromExecutable(graph, variables_context)
                returned_variables[(self.m_variable,)] = [(node_uri_refs,) for node_uri_refs in node_uri_refs_list]
                check_returned_variables(returned_variables)
                return returned_variables

        if not url_nodes_list:
            # Get all objects by default.
            properties_tuple, url_nodes_list = self.GetAllObjects()

        # The object creation has evaluated properties of these objects.
        # Some of these properties might be mapped to variables which are not in used yet.
        # Their values must be returned, so they will be part of the next variables contexts.
        assert isinstance(url_nodes_list, list)
        properties_indices = []
        for index_property, one_property in enumerate(properties_tuple):
            # Maybe this property is defined in the generated objects but not in the instance.
            if one_property in self.m_properties:
                prop_value = self.m_properties[one_property]
                if isinstance(prop_value, rdflib.term.Variable) and prop_value not in variables_context:
                    properties_indices.append(index_property)
        new_properties_tuple = tuple(
            self.m_properties[properties_tuple[index_property]]
            for index_property in properties_indices)
        new_values_list = [
            tuple(value_tuple[index_property]
                  for index_property in properties_indices)
            for value_tuple in url_nodes_list]
        if properties_indices:
            returned_variables[new_properties_tuple] = new_values_list
            check_returned_variables(returned_variables)

        node_uri_refs_list = []

        for values_tuple in url_nodes_list:
            assert len(values_tuple) == len(properties_tuple)
            properties_dict = dict(zip(properties_tuple, values_tuple))
            node_uri_ref = self.CreateURIRef(graph, "CIM_Process", class_CIM_Process, properties_dict)
            node_uri_refs_list.append((node_uri_ref,))

            process_id = int(properties_dict[predicate_Handle])
            self.DefineExecutableFromProcess(variables_context, process_id, graph, returned_variables, node_uri_ref)

        assert isinstance(self.m_variable, rdflib.term.Variable)
        assert self.m_variable not in returned_variables
        returned_variables[(self.m_variable,)] = node_uri_refs_list
        check_returned_variables(returned_variables)
        return returned_variables


def CreateSparql_CIM_Object(class_name, the_subject):
    class_name_to_class = {
        "CIM_DataFile": Sparql_CIM_DataFile,
        "CIM_Directory": Sparql_CIM_Directory,
        "CIM_Process": Sparql_CIM_Process,
    }

    the_class = class_name_to_class[class_name]
    the_instance = the_class(class_name, the_subject)
    return the_instance

################################################################################


def _wmi_load_ontology():
    # classes_map[class_name] = {"base_class": base_class_name, "class_description": text_descr}
    # map_attributes[prop_obj.name] = { "predicate_type": prop_obj.type,"predicate_domain": class_name}
    global wmiExecutor

    import lib_wmi
    if _wmi_load_ontology.classes_map is None:
        _wmi_load_ontology.classes_map, _wmi_load_ontology.attributes_map = lib_ontology_tools.ManageLocalOntologyCache(
            "wmi", lib_wmi.ExtractWmiOntologyLocal)
        assert _wmi_load_ontology.classes_map
        assert _wmi_load_ontology.attributes_map
        wmiExecutor = lib_wmi.WmiSparqlExecutor()


_wmi_load_ontology.classes_map = None
_wmi_load_ontology.attributes_map = None

wmiExecutor = None


class Sparql_WMI_GenericObject(Sparql_CIM_Object):
    def __init__(self, class_name, node):
        _wmi_load_ontology()

        # This contains the leys of the class, for example["Handle"] if Win32_Process.
        ontology_keys = _wmi_load_ontology.classes_map[class_name]["class_keys_list"]
        super(Sparql_WMI_GenericObject, self).__init__(class_name, node, ontology_keys)

        # We could also use the ontology stored in RDF, but by sticking to the data structure created
        # from WMI, no information is lost, even if the container.
        self.m_class_node = lib_kbase.RdfsPropertyNode(class_name)

    def IteratorToObjects(self, rdflib_graph, iterator_objects):
        #sys.stderr.write("IteratorToObjects\n")

        # Set by the first row.
        list_variables = []

        # All the used properties of the object, variables or literals.
        # This is used to add nodes to the rdf graph. All properties must
        # be added, to be processed by Sparql.
        property_names_used = []

        list_current_values = []
        #sys.stderr.write("IteratorToObjects %s self.m_properties.keys()=%s\n" % (str(self.m_variable), str(self.m_properties)))

        for object_path, dict_key_values in iterator_objects:

            if not list_variables:
                # The first object is used to create the list of attributes.
                list_variables.append(self.m_variable)
                for wql_key_node, wql_value_dummy in dict_key_values.items():
                    #sys.stderr.write("IteratorToObjects wql_key_node=%s\n" % wql_key_node)
                    assert isinstance(wql_key_node, rdflib.term.URIRef)
                    if wql_key_node not in self.m_properties:
                        continue
                    wql_variable = self.m_properties[wql_key_node]
                    property_names_used.append(wql_key_node)
                    if isinstance(wql_variable, rdflib.term.Variable):
                        list_variables.append(wql_variable)
                    else:
                        dummy_variable_name = "temptemptemp" + "_dummy_subname"
                        dummy_variable_node = rdflib.term.Variable(dummy_variable_name)
                        list_variables.append(dummy_variable_node)
                assert all((isinstance(one_variable, rdflib.term.Variable) for one_variable in list_variables))

            # The Sparql query is:
            # { ?url_proc survol:Handle %d  .
            #  ?url_proc rdf:type survol:CIM_Process . }
            # WMI returns object_path = '\\RCHATEAU-HP\root\cimv2:Win32_Process.Handle="11568"'
            # Survol object URL must be like: http://rchateau-hp:8000/survol/entity.py?xid=CIM_Process.Handle=6936
            # Therefore, the WMI path cannot be used "as is", but instead use the original self.m_class_name.
            # sys.stderr.write("IteratorToObjects object_path=%s\n" % object_path)
            # sys.stderr.write("IteratorToObjects dict_key_values.keys()=%s\n"
            #                 % [lib_properties.PropToQName(one_uri_ref) for one_uri_ref in dict_key_values])
            uri_key_values = {}
            wmi_class_keys = self.class_keys()
            for one_class_key in wmi_class_keys:
                one_class_key_node = lib_kbase.RdfsPropertyNode(one_class_key)
                uri_key_values[one_class_key] = dict_key_values[one_class_key_node]
            node_uri_ref = lib_common.gUriGen.UriMakeFromDict(self.m_class_name, uri_key_values)

            rdflib_graph.add((node_uri_ref, rdflib.namespace.RDF.type, self.m_class_node))

            # The node of the path is always returned as first element.
            variable_values_list = [node_uri_ref,]
            for wql_key_node in property_names_used:
                assert isinstance(wql_key_node, rdflib.term.URIRef)
                wql_value = dict_key_values[wql_key_node]
                assert isinstance(wql_value, lib_util.scalar_data_types)
                variable_values_list.append(wql_value)
                wql_value_node = rdflib.term.Literal(wql_value)
                rdflib_graph.add((node_uri_ref, wql_key_node, wql_value_node))
            variable_values_tuple = tuple(variable_values_list)
            list_current_values.append(variable_values_tuple)

        #sys.stderr.write("IteratorToObjects list_variables=%s\n" % list_variables)
        assert all((isinstance(one_variable, rdflib.term.Variable) for one_variable in list_variables))
        tuple_variables = tuple(list_variables)
        returned_variables = {tuple_variables: list_current_values}
        #sys.stderr.write("IteratorToObjects END\n\n")
        check_returned_variables(returned_variables)
        return returned_variables

    def class_keys(self):
        wmi_class_keys = _wmi_load_ontology.classes_map[self.m_class_name]["class_keys_list"]
        assert all([isinstance(one_class_key, six.text_type) for one_class_key in wmi_class_keys])
        return wmi_class_keys

    def SelectWmiObjectFromProperties(self, graph, variables_context, filtered_where_key_values):
        #sys.stderr.write("SelectWmiObjectFromProperties filtered_where_key_values=%s\n" % str(filtered_where_key_values))
        iterator_objects = wmiExecutor.SelectObjectFromProperties(self.m_class_name, filtered_where_key_values)
        #####iterator_objects = list(iterator_objects)
        returned_variables = self.IteratorToObjects(graph, iterator_objects)
        check_returned_variables(returned_variables)
        #sys.stderr.write("SelectWmiObjectFromProperties returned_variables=:\n")
        check_returned_variables(returned_variables)
        return returned_variables

    def BuildWmiPathFromSurvolPath(self, variables_context):
        " This parses the Survol path to build WMI path which is very similar but not completely."
        # survol_path = 'http://rchateau-hp:80/LocalExecution/entity.py?xid=CIM_Directory.Name=c:/a/b/c.txt'
        survol_path = variables_context[self.m_variable]
        _, _, shortened_path = survol_path.partition("?xid=")
        class_name, _, kw_pairs_as_str = shortened_path.partition(".")
        assert class_name == self.m_class_name
        # key-value pairs are separated by commas.

        entity_id_dict = lib_util.SplitMoniker(kw_pairs_as_str)

        associator_path = self.m_class_name + "." + ",".join(
            ('%s="%s"' % (prop_key, prop_value) for prop_key, prop_value in entity_id_dict.items())
        )

        #sys.stderr.write("BuildWmiPathFromSurvolPath associator_path=%s\n" % associator_path)
        return associator_path

    def CreateAssociatorObjects(self, graph, variables_context):
        if self.m_associated:
            returned_variables = self.CreateAssociatorObjectsBidirectional(graph, variables_context, self.m_associated, 0)
            if returned_variables:
                #sys.stderr.write("CreateAssociatorObjects m_associated. returned_variables=%s\n" % returned_variables.keys())
                check_returned_variables(returned_variables)
                return returned_variables
        if self.m_associators:
            returned_variables = self.CreateAssociatorObjectsBidirectional(graph, variables_context, self.m_associators, 1)
            if returned_variables:
                #sys.stderr.write("CreateAssociatorObjects m_associators. returned_variables=%s\n" % returned_variables.keys())
                check_returned_variables(returned_variables)
                return returned_variables
        return {}

        # FIXME: What if both return values ???

    def CreateAssociatorObjectsBidirectional(self, graph, variables_context, assoc_list, role_index):

        # We might have several associators for one object.
        associator_urls_set = set()

        # Each associator must give values for all variables of this instance.
        keys_set_first_associator = None

        for associator_predicate, associated_instance in assoc_list.items():
            #sys.stderr.write("CreateAssociatorObjectsBidirectional associator_predicate=%s associated_instance=%s\n"
            #                 % (associator_predicate, associated_instance))
            assert isinstance(associator_predicate, rdflib.term.URIRef)
            assert isinstance(associated_instance, Sparql_CIM_Object)

            associated_variable = associated_instance.m_variable
            assert isinstance(associated_variable, rdflib.term.Variable)
            #sys.stderr.write("CreateAssociatorObjectsBidirectional role_index=%d associated_variable=%s\n" % (role_index, associated_variable))
            #sys.stderr.write("CreateAssociatorObjectsBidirectional variables_context.keys()=%s\n" % str(variables_context.keys()))

            if associated_variable not in variables_context:
                #sys.stderr.write("CreateAssociatorObjectsBidirectional Cannot find in variables_context: associated_variable=%s\n" % associated_variable)
                continue

            associator_name = lib_properties.PropToQName(associator_predicate)
            #sys.stderr.write("CreateAssociatorObjectsBidirectional associator_name=%s\n" % associator_name)

            associated_variable_value = variables_context[associated_variable]
            #sys.stderr.write("CreateAssociatorObjectsBidirectional associated_variable_value=%s\n" % associated_variable_value)
            assert isinstance(associated_variable_value, rdflib.URIRef)

            associator_path = associated_instance.BuildWmiPathFromSurvolPath(variables_context)

            iterator_objects = wmiExecutor.SelectBidirectionalAssociatorsFromObject(self.m_class_name, associator_name, associator_path, role_index)

            # This returns a map of one element only. The key is a tuple of variables.
            # The value is a list of tuples of the same size.
            returned_variables_one = self.IteratorToObjects(graph, iterator_objects)
            assert len(returned_variables_one) == 1
            first_key = next(iter(returned_variables_one))
            #sys.stderr.write("CreateAssociatorObjectsBidirectional first_key=%s\n"
            #                 % [str(one_key) for one_key in first_key])

            if not first_key:
                #sys.stderr.write("CreateAssociatorObjectsBidirectional no selection from %s/%s/%s/%s\n"
                #                 % (self.m_class_name, associator_name, associator_path, role_index))
                continue

            if keys_set_first_associator:
                assert keys_set_first_associator == first_key
            else:
                keys_set_first_associator = first_key

            # The variable containing the url must be there.
            #sys.stderr.write("CreateAssociatorObjectsBidirectional first_key=%s\n" % str(first_key))
            #sys.stderr.write("CreateAssociatorObjectsBidirectional self.m_variable=%s\n" % self.m_variable)
            index_url_key = first_key.index(self.m_variable)
            # This is most probably the first key.
            assert index_url_key == 0
            assert first_key[index_url_key] == self.m_variable

            urls_list = returned_variables_one[first_key]
            #sys.stderr.write("CreateAssociatorObjectsBidirectional urls_list=%s\n" % urls_list)
            assert isinstance(urls_list, list)

            # Now add the triples specifying the associator relation.
            for one_tuple_url in urls_list:
                assert isinstance(one_tuple_url, tuple)
                assert len(one_tuple_url) >= 1
                object_url = one_tuple_url[index_url_key]
                isinstance(object_url, rdflib.URIRef)
                if role_index == 0:
                    graph.add((associated_variable_value, associator_predicate, object_url))
                else:
                    graph.add((object_url, associator_predicate, associated_variable_value))

            #sys.stderr.write("CreateAssociatorObjectsBidirectional returned_variables_one=%s\n" % returned_variables_one)

            if not associator_urls_set:
                associator_urls_set.update(urls_list)
            else:
                associator_urls_set = associator_urls_set.intersection(set(urls_list))
            #sys.stderr.write("CreateAssociatorObjectsBidirectional returned_variables_set=%s\n" % associator_urls_set)
            # Because the variable is in the context, it is defined and its path is available.
            # Therefore, it is possible to fetch its associators only from the path.

        # sys.stderr.write("CreateAssociatorObjectsBidirectional associator_urls_set=%s\n" % associator_urls_set)
        associator_urls_list = list(associator_urls_set)
        if keys_set_first_associator:
            returned_variables = {keys_set_first_associator: associator_urls_list}
            check_returned_variables(returned_variables)
        else:
            returned_variables = {}
            sys.stderr.write("Nothing found in associators")
        return returned_variables

    def FetchAllVariables(self, graph, variables_context):
        filtered_where_key_values = dict()

        #sys.stderr.write("FetchAllVariables variables_context=%s\n" % " , ".join("%s=%s" % (str(k), str(v)) for k, v in variables_context.items()))
        #sys.stderr.write("FetchAllVariables self.m_properties=%s\n" % " , ".join("%s=%s" % (str(k), str(v)) for k, v in self.m_properties.items()))

        for predicate_node in self.m_properties:
            predicate_name = lib_properties.PropToQName(predicate_node)
            value_node = self.GetNodeValue(predicate_node, variables_context)
            #sys.stderr.write("FetchAllVariables predicate_node=%s\n" % predicate_node)
            if value_node:
                filtered_where_key_values[predicate_name] = str(value_node)

        if filtered_where_key_values:
            returned_variables = self.SelectWmiObjectFromProperties(graph, variables_context, filtered_where_key_values)
            check_returned_variables(returned_variables)
            return returned_variables

        #sys.stderr.write("associated:%d associators:%d\n" % (len(self.m_associated), len(self.m_associators)))

        if not filtered_where_key_values and not self.m_associated and not self.m_associators:
            sys.stderr.write("FetchAllVariables BEWARE FULL SELECT: %s\n" % self.m_class_name)
            returned_variables = self.SelectWmiObjectFromProperties(graph, variables_context, filtered_where_key_values)
            check_returned_variables(returned_variables)
            return returned_variables

        returned_variables = self.CreateAssociatorObjects(graph, variables_context)
        check_returned_variables(returned_variables)
        return returned_variables


def CreateSparql_CIM_Object_Wmi(class_name, the_subject):
    the_instance = Sparql_WMI_GenericObject(class_name, the_subject)
    return the_instance

################################################################################


def part_triples_to_instances_dict_function(part, sparql_instance_creator):
    """
    This takes the list of triples extracted from the Sparql query,
    and returns a list of instances of CIM classes, each of them
    containing the triples using its instances. The association is
    done based on the variable representing the instance.
    There might be several instances of the same class.
    """
    instances_dict = dict()
    sys.stderr.write("len(triples)=%d\n" % len(part.triples))
    for part_subject, part_predicate, part_object in part.triples:
        sys.stderr.write("    spo=%s %s %s\n" % (part_subject, part_predicate, part_object))
        if part_predicate == rdflib.namespace.RDF.type:
            if isinstance(part_subject, rdflib.term.Variable):
                class_as_str = str(part_object)
                sys.stderr.write("class_as_str=%s\n" % class_as_str)
                sys.stderr.write("survol_url=%s\n" % survol_url)
                if class_as_str.startswith(survol_url):
                    class_short = class_as_str[len(survol_url):]
                    sys.stderr.write("Class OK\n")
                    # sparql_instance_creator can also tell the difference between an associator and a property
                    instances_dict[part_subject] = sparql_instance_creator(class_short, part_subject)

    assert instances_dict
    sys.stderr.write("Created instances:%s\n" % instances_dict.keys())

    for part_subject, part_predicate, part_object in part.triples:
        current_instance = instances_dict.get(part_subject, None)
        if not current_instance: continue
        assert isinstance(current_instance, Sparql_CIM_Object)
        if part_predicate == rdflib.namespace.RDF.type: continue

        if part_predicate == rdflib.namespace.RDFS.seeAlso: continue

        associator_instance = instances_dict.get(part_object, None)
        if associator_instance:
            # If the variable of the object is also a subject variable defining an instance,
            # then it can only be an associator. This is why it is necessary to define the class
            # of a subject in a triplet. This is however necessary to instantiate it
            # and do any WMY query on it.
            assert isinstance(associator_instance, Sparql_CIM_Object)
            current_instance.m_associators[part_predicate] = associator_instance
            associator_instance.m_associated[part_predicate] = current_instance
        else:
            assert isinstance(part_object, (rdflib.term.Literal, rdflib.term.Variable))
            current_instance.m_properties[part_predicate] = part_object

    return instances_dict


def check_returned_variables(returned_variables):
    """
    Debugging purpose only.
    This checks the content of something similar to:
    {
        (rdflib.term.Variable(u'url_execfile'),):
        [(rdflib.term.URIRef(u'http://myhost:80/LocalExecution/entity.py?xid=CIM_DataFile.Name=c:/windows/system32/urlmon.dll'),), (r...,)]}
    """
    assert isinstance(returned_variables, dict)
    for first_key, values_list in returned_variables.items():
        assert isinstance(values_list, list)
        assert isinstance(first_key, tuple)
        # Maybe, several correlated variables of attributes of the same object.
        for one_value_tuple in values_list:
            assert isinstance(one_value_tuple, tuple)
            assert len(one_value_tuple) == len(first_key)
            for one_value in one_value_tuple:
                assert isinstance(one_value, (rdflib.term.Literal, rdflib.term.URIRef))


def product_variables_lists(returned_variables, iter_keys=None):
    """
    The input is a set of {variable: list-of-values.
    It returns a set of {variable: value}, which is the set of combinations
    of all possible values for each variable.
    A variable can also be a tuple of rdflib variables.
    In this case, the values must also be tuples.
    """
    check_returned_variables(returned_variables)
    try:
        if not iter_keys:
            iter_keys = iter(returned_variables.items())
        first_key, values_list = next(iter_keys)
        assert isinstance(values_list, list)

        max_display_count_values = 0
        #sys.stderr.write("product_variables_lists LOOP BEFORE\n")
        for one_dict in product_variables_lists(returned_variables, iter_keys):
            # sys.stderr.write("product_variables_lists len(values_list)=%d\n" % len(values_list))
            for one_value in values_list:
                new_dict = one_dict.copy()
                # This is one variable, or a tuple of variables of attributes of the same object.

                # Maybe, several correlated variables of attributes of the same object.
                assert isinstance(one_value, tuple)
                #sys.stderr.write("len(first_key)=%d\n" % len(first_key))
                #sys.stderr.write("len(one_value)=%d\n" % len(one_value))

                # This is to avoid the Travis message:
                # "The job exceeded the maximum log length, and has been terminated."
                if max_display_count_values > 0:
                    #sys.stderr.write("product_variables_lists first_key=%s\n" % ",".join(key_element for key_element in first_key))
                    #sys.stderr.write("product_variables_lists one_value=%s\n" % ",".join(value_element for value_element in one_value))
                    max_display_count_values -= 1
                    if max_display_count_values == 0:
                        sys.stderr.write("product_variables_lists STOP DISPLAYING EXCESSIVE NUMBER OF VALUES\n")
                assert len(first_key) == len(one_value)
                # Each key is a tuple of variables matched by each of the tuples of the list of values.
                assert all((isinstance(single_key, rdflib.term.Variable) for single_key in first_key))
                #sys.stderr.write("one_value.types:%s\n" % str([type(single_value) for single_value in one_value]))
                assert all((isinstance(single_value, (rdflib.term.Literal, rdflib.term.URIRef)) for single_value in one_value))

                new_dict.update(zip(first_key, one_value))

                #sys.stderr.write("product_variables_lists first_key=%s\n" % str(first_key))

                yield new_dict
        #sys.stderr.write("product_variables_lists LOOP AFTER\n")
    except StopIteration:
        yield {}


def findable_instance_key(instances_dict):
    """An instance which is completely known and can be used as a starting point."""
    sys.stderr.write("findable_instance_key\n")
    for instance_key, one_instance in instances_dict.items():
        one_instance.CalculateVariablesNumber()
        sys.stderr.write("    Key=%s Instance=%s\n" % (instance_key, one_instance))
        # Maybe we could return the instance with the greatest number of
        # literals ? Or the one whose implied instances are the fastest to find.
        # if one_instance.m_number_variables == 0 and one_instance.m_number_literals > 0:

        # We want to be able to retrieve at least one object, and as fast as possible.
        # This should check if wthe properties of the ontology are defined,
        # this is very important for WMI otherwise the performance can be awful.
        # On the other hand, in the general case, any property is enough, maybe none of them.
        # Realistically, in this examples, the ontologies properties are required.
        if one_instance.m_number_literals > 0:
            return instance_key

    # Could not find an instance with enough information.
    # The only possibility is to list all object. So, return the first instance.
    for instance_key, one_instance in instances_dict.items():
        return instance_key


def visit_all_nodes(instances_dict):
    """ Exploration of the graph, starting by the ones which can be calculated without inference."""

    # Find a string point to walk the entire graph.
    start_instance_key = findable_instance_key(instances_dict)
    start_instance = instances_dict[start_instance_key]

    for instance_key, one_instance in instances_dict.items():
        one_instance.m_visited = False

    visited_instances = []
    unvisited_instances = set([one_instance for one_instance in instances_dict.values()])

    def instance_recursive_visit(one_instance):
        assert isinstance(one_instance, Sparql_CIM_Object)
        one_instance.m_visited = True
        visited_instances.append(one_instance)
        unvisited_instances.remove(one_instance)
        for sub_instance in one_instance.m_associators.values():
            if not sub_instance.m_visited:
                instance_recursive_visit(sub_instance)
        for sub_instance in one_instance.m_associated.values():
            if not sub_instance.m_visited:
                instance_recursive_visit(sub_instance)

    instance_recursive_visit(start_instance)

    def get_property_variables_list(one_instance):
        return set([property_value
                    for property_value in one_instance.m_properties.values()
                    if isinstance(property_value, rdflib.term.Variable)])

    assert len(visited_instances) + len(unvisited_instances) == len(instances_dict)
    all_properties_set = set.union(*[get_property_variables_list(one_instance) for one_instance in visited_instances])

    while True:
        closest_properties_set = None
        biggest_intersection_num = 0
        best_instance = None
        for sub_instance in unvisited_instances:
            sub_property_variables = get_property_variables_list(sub_instance)
            properties_intersection = all_properties_set.intersection(sub_property_variables)
            num_intersection = len(properties_intersection)
            if num_intersection >= biggest_intersection_num:
                biggest_intersection_num = num_intersection
                best_instance = sub_instance
                closest_properties_set = sub_property_variables
        if not best_instance:
            break
        all_properties_set.update(closest_properties_set)
        instance_recursive_visit(best_instance)

    if len(unvisited_instances) > 0:
        visited_instances += unvisited_instances
        WARNING("visit_all_nodes len(unvisited_instances)=%d", len(unvisited_instances))
    assert len(visited_instances) == len(instances_dict)
    return visited_instances


def custom_eval_function(ctx, part):
    return custom_eval_function_generic(ctx, part, CreateSparql_CIM_Object)


def custom_eval_function_wmi(ctx, part):
    return custom_eval_function_generic(ctx, part, CreateSparql_CIM_Object_Wmi)


def custom_eval_function_wbem(ctx, part):
    return custom_eval_function_generic(ctx, part, None)


def custom_eval_function_generic(ctx, part, sparql_instance_creator):
    """Inspired from https://rdflib.readthedocs.io/en/stable/_modules/examples/custom_eval.html """

    # part.name = "SelectQuery", "Project", "BGP"
    if part.name == 'BGP':
        add_ontology(ctx.graph)

        sys.stderr.write("Instances:\n")
        instances_dict = part_triples_to_instances_dict_function(part, sparql_instance_creator)
        sys.stderr.write("Instances before sort:%d\n" % len(instances_dict))
        for instance_key, one_instance in instances_dict.items():
            sys.stderr.write("    Key=%s Instance=%s\n" % (instance_key, one_instance))

        visited_nodes = visit_all_nodes(instances_dict)
        assert len(instances_dict) == len(visited_nodes)

        # This is a dictionary of variables.
        variables_context = {}

        def display_variables_context(margin):
            for k, v in variables_context.items():
                sys.stderr.write("%s k=%s v=%s\n" % (margin, k, v))

        def recursive_instantiation(instance_index):
            if instance_index == len(visited_nodes):
                return
            sys.stderr.write("recursive_instantiation: ix=%d visited nodes=%s\n"
                             % (instance_index, str([nod.m_variable for nod in visited_nodes])))

            # This returns the first instance which is completely kown, i.e. its parameters
            # are iterals, or variables whose values are known in the current context.
            one_instance = visited_nodes[instance_index]
            #margin = " " + str(instance_index) + "    " * (instance_index + 1)
            #sys.stderr.write(margin + "one_instance=%s\n" % one_instance)

            #sys.stderr.write(margin + "variables_context BEFORE\n")
            #display_variables_context(margin)
            returned_variables = one_instance.FetchAllVariables(ctx.graph, variables_context)
            #sys.stderr.write(margin + "variables_context AFTER\n")
            #display_variables_context(margin)
            check_returned_variables(returned_variables)

            #sys.stderr.write(margin + "returned_variables=%s\n" % str(returned_variables))

            variables_combinations_iter = product_variables_lists(returned_variables)
            variables_context_backup = variables_context.copy()
            for one_subset in variables_combinations_iter:
                variables_context.update(one_subset)
                #sys.stderr.write(margin + "recursive_instantiation instance_index=%d variables_context.keys()=%s\n"
                #                 % (instance_index, ",".join(str(key) for key in variables_context.keys())))
                #sys.stderr.write(margin + "recursive_instantiation instance_index=%d variables_context_backup.keys()=%s\n"
                #                 % (instance_index, ",".join(str(key) for key in variables_context_backup.keys())))
                #sys.stderr.write(margin + "recursive_instantiation one_subset=%s\n" % str(one_subset))
                recursive_instantiation(instance_index+1)
            variables_context.clear()
            variables_context.update(variables_context_backup)

        recursive_instantiation(0)

        INFO("Graph after recursive_instantiation: %d triples", len(ctx.graph))
        if False:
            sys.stderr.write("Graph after recursive_instantiation: %d triples\n" % len(ctx.graph))
            for s,p,o in ctx.graph:
                sys.stderr.write("   s=%s p=%s o=%s\n" % (s, p, o))
        #sys.stderr.flush()

        # <type 'generator'>
        ret_BGP = rdflib.plugins.sparql.evaluate.evalBGP(ctx, part.triples)
        return ret_BGP

    raise NotImplementedError()

