import networkx

class Clustering:

    EPSILON = 0.001  # Weight to use on matching graph instead of 0.

    def __init__(self, alpha, clustering_lol):
        self.alpha = alpha
        self.clustering = clustering_lol
        self.k = len(self.clustering)
        self.labeling = [ i for i in range(0, self.k) ]

    def set_labeling(self, label_list):
        self.labeling = label_list

    def get_labeling(self):
        return self.labeling

    def get_dict_id_labels(self):
        id_labels = {}
        for cluster, label in zip(self.clustering, self.labeling):
            for point in cluster:
                id_labels[point] = label
        return id_labels

    def node_label(cat, num):
        return str(cat) + "." + str(num)

    def get_index(node_label):
        return int(node_label.split(".")[-1])

    def get_intersection_weight(clustlist1, clustlist2):
        intersection = set(clustlist1).intersection(clustlist2)
        weight = len(intersection)
        return weight

    def get_percent_weight(clustlist1, clustlist2):
        print("    clust1 size:" + str(len(clustlist1)) + "  clust2 size:" + str(len(clustlist2)))
        intersection = set(clustlist1).intersection(clustlist2)
        weight = len(intersection)
        print("    intersection size:" + str(weight))
        if weight == 0:
            return 0
        percent = weight / len(clustlist1)
        print("    percent:" + str(percent))
        return percent

    def make_bipartite_graph(clustering1, clustering2):
        k = clustering1.k
        assert(k == clustering2.k)

        graph = networkx.Graph()
        for i in range(0, k):
            n1 = Clustering.node_label(1, i)
            graph.add_node(n1, bipartite=0)
        for i in range(0, k):
            n2 = Clustering.node_label(2, i)
            graph.add_node(n2, bipartite=1)

        for i in range(0, k):
            for j in range(0, k):
                clust1 = clustering1.clustering[i]
                clust2 = clustering2.clustering[j]
                n1 = Clustering.node_label(1, i)
                n2 = Clustering.node_label(2, j)
                weight = Clustering.get_intersection_weight(clust1, clust2)
                print("n1:" + n1 + " n2:" + n2 + "  weight:" + str(weight))
                if weight == 0:
                    weight = Clustering.EPSILON
                graph.add_edge(n1, n2, weight = weight)

        return graph

    def matching_to_dict(matching_dict_tuples):
        matching = {}
        for start, end in matching_dict_tuples:
            matching[start] = end
            matching[end] = start
        return matching

    def set_labeling_maxmatching(clustering_start, clustering_end):
        k = clustering_start.k
        graph = Clustering.make_bipartite_graph(clustering_start, clustering_end)
        # matching = networkx.bipartite.maximum_matching(graph)
        matching_tuples = networkx.algorithms.max_weight_matching(
                              graph, maxcardinality=False, weight='weight')
        matching = Clustering.matching_to_dict(matching_tuples)
        print(matching)
        startlabeling = clustering_start.get_labeling()
        print("start labels:" + str(startlabeling))
        endlabeling = []
        for i in range(0, k):
            startnode = Clustering.node_label(1, i)
            nodematch = matching[startnode]
            j = Clustering.get_index(nodematch)
            startlabel = startlabeling[j]
            endlabeling.append(startlabel)
        clustering_end.set_labeling(endlabeling)

# c1 = Clustering(0.5, [[1,2],[2,3],[1,3]])
# c2 = Clustering(0.5, [[1,2],[1,3],[2,3]])
# Clustering.set_labeling_maxmatching(c1, c2)


import networkx

class Clustering:

    EPSILON = 0.001  # Weight to use on matching graph instead of 0.

    def __init__(self, alpha, clustering_lol):
        self.alpha = alpha
        self.clustering = clustering_lol
        self.k = len(self.clustering)
        self.labeling = [ i for i in range(0, self.k) ]

    def set_labeling(self, label_list):
        self.labeling = label_list

    def get_labeling(self):
        return self.labeling

    def get_dict_id_labels(self):
        id_labels = {}
        for cluster, label in zip(self.clustering, self.labeling):
            for point in cluster:
                id_labels[point] = label
        return id_labels

    def node_label(cat, num):
        return str(cat) + "." + str(num)

    def get_index(node_label):
        return int(node_label.split(".")[-1])

    def get_intersection_weight(clustlist1, clustlist2):
        intersection = set(clustlist1).intersection(clustlist2)
        weight = len(intersection)
        return weight

    def get_percent_weight(clustlist1, clustlist2):
        print("    clust1 size:" + str(len(clustlist1)) + "  clust2 size:" + str(len(clustlist2)))
        intersection = set(clustlist1).intersection(clustlist2)
        weight = len(intersection)
        print("    intersection size:" + str(weight))
        if weight == 0:
            return 0
        percent = weight / len(clustlist1)
        print("    percent:" + str(percent))
        return percent

    def make_bipartite_graph(clustering1, clustering2):
        k = clustering1.k
        assert(k == clustering2.k)

        graph = networkx.Graph()
        for i in range(0, k):
            n1 = Clustering.node_label(1, i)
            graph.add_node(n1, bipartite=0)
        for i in range(0, k):
            n2 = Clustering.node_label(2, i)
            graph.add_node(n2, bipartite=1)

        for i in range(0, k):
            for j in range(0, k):
                clust1 = clustering1.clustering[i]
                clust2 = clustering2.clustering[j]
                n1 = Clustering.node_label(1, i)
                n2 = Clustering.node_label(2, j)
                weight = Clustering.get_intersection_weight(clust1, clust2)
                print("n1:" + n1 + " n2:" + n2 + "  weight:" + str(weight))
                if weight == 0:
                    weight = Clustering.EPSILON
                graph.add_edge(n1, n2, weight = weight)

        return graph

    def matching_to_dict(matching_dict_tuples):
        matching = {}
        for start, end in matching_dict_tuples:
            matching[start] = end
            matching[end] = start
        return matching

    def set_labeling_maxmatching(clustering_start, clustering_end):
        k = clustering_start.k
        graph = Clustering.make_bipartite_graph(clustering_start, clustering_end)
        # matching = networkx.bipartite.maximum_matching(graph)
        matching_tuples = networkx.algorithms.max_weight_matching(
                              graph, maxcardinality=False, weight='weight')
        matching = Clustering.matching_to_dict(matching_tuples)
        print(matching)
        startlabeling = clustering_start.get_labeling()
        print("start labels:" + str(startlabeling))
        endlabeling = []
        for i in range(0, k):
            startnode = Clustering.node_label(1, i)
            nodematch = matching[startnode]
            j = Clustering.get_index(nodematch)
            startlabel = startlabeling[j]
            endlabeling.append(startlabel)
        clustering_end.set_labeling(endlabeling)

# c1 = Clustering(0.5, [[1,2],[2,3],[1,3]])
# c2 = Clustering(0.5, [[1,2],[1,3],[2,3]])
# Clustering.set_labeling_maxmatching(c1, c2)


# Weight to use on matching graph instead of 0. # matching = networkx.bipartite.maximum_matching(graph) # c1 = Clustering(0.5, [[1,2],[2,3],[1,3]]) # c2 = Clustering(0.5, [[1,2],[1,3],[2,3]]) # Clustering.set_labeling_maxmatching(c1, c2)