#TODO mention inserted libraries
#doxwnoaded plingeling http://fmv.jku.at/lingeling/

#TODO log: used command >> gcc -fPIC -std=c99 -shared formulas.c nauty/gtools.c -rdynamic jmgraph.c -o jmgraph.so

#TODO tell -> (p?)lingeling adresu som prepisal vo vlastnych suboroch, asi treba zmenit naspet

import networkx as nx
import copy
import matplotlib.pyplot as plt
import sys
from ctypes import cdll

cgraph = cdll.LoadLibrary('../gen_c/jmgraph.so')


def cubicCheck(graph):
    for n in graph.nodes:
        if(graph.degree[n] != 3):
            print("ERROR: "+str(graph.edges))
            raise ValueError('Not a cubic graph')
counter = 0

def counterCheck(num):
    global counter
    if(num < counter):
        print("WRONG SIZE!!!!!!!!!!!!!!")
    if(counter < num):
        counter = num


def addTwoNodesOnEdge(graph, edge):
    n = graph.number_of_nodes()
    g1 = copy.deepcopy(graph)
    g1.remove_edge(edge[0],edge[1])
    g1.add_node(n + 1)
    g1.add_node(n + 2)
    g1.add_edge(edge[0], n + 1)
    g1.add_edge(edge[1], n + 2)
    g1.add_edge(n + 1, n + 2)
    g1.add_edge(n + 1, n + 2)
    return g1


def addTwoLoopsInsteadEdge(graph, edge):
    n = graph.number_of_nodes()
    g1 = copy.deepcopy(graph)
    g1.remove_edge(edge[0],edge[1])
    g1.add_node(n+1)
    g1.add_node(n+2)
    g1.add_edge(edge[0],n+1)
    g1.add_edge(edge[1],n+2)
    g1.add_edge(n+1, n + 1)
    g1.add_edge(n + 2, n + 2)

    cubicCheck(g1)
    counterCheck(graph.number_of_nodes())
    return g1

def addTriangle(graph, node):
    neighborEdges = graph.edges(node)
    neighborNodes = []
    for nbEdge in neighborEdges:
        if(nbEdge[0] == node):
            neighborNodes.append(nbEdge[1])
        else:
            neighborNodes.append(nbEdge[0])
    num = graph.number_of_nodes()
    g1 = copy.deepcopy(graph)
    nb = list(g1[node])
    numOfNeighbors = len(nb)
    g1.add_edge(node, num + 1)
    g1.add_edge(node, num + 2)
    g1.add_edge(num + 1, num + 2)

    hasSelfLoop = node in nb

    if(numOfNeighbors == 1):
        g1.add_edges_from([(neighborNodes[0],node),(neighborNodes[0],num+1),(neighborNodes[0],num+2)])
    if (numOfNeighbors == 2):
        if(hasSelfLoop):
            g1.add_edges_from([(neighborNodes[0], num + 1), (neighborNodes[1], num + 2)])
        else:
            g1.add_edges_from([(neighborNodes[0], node), (neighborNodes[1], num + 1),(neighborNodes[2], num + 2)])
    if (numOfNeighbors == 3):
        g1.add_edges_from([(neighborNodes[0], node), (neighborNodes[1], num + 1), (neighborNodes[2], num + 2)])

    for n in neighborNodes:

        g1.remove_edge(n, node)

    cubicCheck(g1)
    counterCheck(g1.number_of_nodes())
    return g1

def addSquare(graph, edge1, edge2):

    n = graph.number_of_nodes()
    g1 = copy.deepcopy(graph)
    g1.add_edge(n + 1, n + 2)
    g1.add_edge(n + 2, n + 3)
    g1.add_edge(n + 3, n + 4)
    g1.add_edge(n + 1, n + 4)

    g1.remove_edge(edge1[0], edge1[1])
    g1.remove_edge(edge2[0], edge2[1])

    g1.add_edge(edge1[0], n + 1)
    g1.add_edge(edge1[1], n + 4)
    g1.add_edge(edge2[0], n + 2)
    g1.add_edge(edge2[1], n + 3)

    cubicCheck(g1)
    counterCheck(g1.number_of_nodes())
    return g1

def joinGraphsOnEdges(g1,g2,e1,e2):
    #print("g1 - g2: " + str(g1.number_of_nodes()) + " - " + str(g2.number_of_nodes()))

    gr1 = copy.deepcopy(g1)
    gr2 = copy.deepcopy(g2)
    num1 = g1.number_of_nodes()
    gr1.remove_edge(e1[0],e1[1])
    gr2.remove_edge(e2[0], e2[1])
    gr2aux = nx.relabel_nodes(gr2, lambda x: x + num1)
    newGraph = nx.compose(gr1, gr2aux)
    newGraph.add_edges_from([(e1[0],e2[0]+num1),(e1[1],e2[1]+num1)])
    cubicCheck(newGraph)

    #print("result graph: " + str(newGraph.number_of_nodes()))
    counterCheck(newGraph.number_of_nodes())

    return newGraph

def joinGraphsOnNodes(g1, g2, n1, n2):

    neighbourNum1 = len((list(g1.neighbors(n1))))
    neighbourNum2 = len((list(g2.neighbors(n2))))

    gr1 = copy.deepcopy(g1)
    gr2 = copy.deepcopy(g2)

    if (neighbourNum1 == 1 or neighbourNum2 == 1):
        return None

    nodeOffset = g1.number_of_nodes()
    n2 = n2 + nodeOffset
    gr2aux = nx.relabel_nodes(gr2, lambda x: x+nodeOffset)

    nb1 = list(gr1.neighbors(n1))
    nb2 = list(gr2aux.neighbors(n2))

    hasSelfLoop1 = n1 in nb1
    hasSelfLoop2 = n2 in nb2

    if (hasSelfLoop1):
        gr1.remove_edge(n1, n1)
        nb1.remove(n1)
    if (hasSelfLoop2):
        gr2aux.remove_edge(n2, n2)
        nb2.remove(n2)
    gr1.remove_node(n1)
    gr2aux.remove_node(n2)

    newGraph = nx.compose(gr1,gr2aux)
    if (hasSelfLoop1 and hasSelfLoop2):
        newGraph.add_edge(nb1[0],nb2[0])
        newGraphRelab = nx.convert_node_labels_to_integers(newGraph,1)
        cubicCheck(newGraphRelab)
        print("g1 - g2: " + str(g1.number_of_nodes()) + " - " + str(g2.number_of_nodes()))
        print("result graph: " + str(newGraphRelab.number_of_nodes()))
        counterCheck(newGraphRelab.number_of_nodes())

        return newGraphRelab
    if (neighbourNum1 == 3 and neighbourNum2 == 3):
        newGraph.add_edges_from([(nb1[0],nb2[0]),(nb1[1],nb2[1]),(nb1[2],nb2[2])])
        newGraph = nx.convert_node_labels_to_integers(newGraph, 1)
        cubicCheck(newGraph)
        print("g1 - g2: " + str(g1.number_of_nodes()) + " - " + str(g2.number_of_nodes()))
        counterCheck(newGraph.number_of_nodes())

        return newGraph
    return None

def isColorable(graph):
    simpleGraph = nx.Graph()
    simpleGraph.add_edges_from(graph.edges)

    bytesOfSimple = nx.to_graph6_bytes(simpleGraph, header=False)
    jmgraph = cgraph.readGraph6(bytesOfSimple.rstrip())

    return cgraph.isColourable(jmgraph)


graphContainer = []

def generate(size):
    global graphContainer
    g1 = nx.MultiGraph()
    g1.add_node(1)
    g1.add_node(2)
    g1.add_edge(1,2)
    g1.add_edge(1,2)
    g1.add_edge(1,2)

    g2 = nx.MultiGraph()
    g2.add_node(1)
    g2.add_node(2)
    g2.add_edge(1, 2)
    g2.add_edge(1, 1)
    g2.add_edge(2, 2)

    twoNodeGraphs = [g1,g2]
    graphContainer.append(twoNodeGraphs)

    for i in range(0,(size//2)-1):
        nextGraphSet = []
        prev = graphContainer[i]
        #TODO check isomorphism
        print("addNodes/Lopps/3angle: "+str(i*2 + 4))

        for gr in prev:

            for u,v,x  in gr.edges:
                #TODO check colourability

                newGraph1 = addTwoNodesOnEdge(gr,(u,v))
                nextGraphSet.append(newGraph1)


                newGraph2 = addTwoLoopsInsteadEdge(gr,(u,v))
                nextGraphSet.append(newGraph2)

            for n in gr.nodes:

                newGraph3 = addTriangle(gr,n)
                nextGraphSet.append(newGraph3)

        if(i > 0):
            prev2 = graphContainer[i-1]
            print("addSquare: "+str(i*2 +4))
            for gr in prev2:
                for u1,v1,x in gr.edges:
                    for u2,v2,y in gr.edges:
                        if(x != y):

                            newGraph4 = addSquare(gr, (u1,v1), (u2,v2))
                            #print(newGraph4.number_of_nodes())
                            nextGraphSet.append(newGraph4)


        for groupSeqNum in range(0,(i//2)+1):

            smallGraphs = graphContainer[groupSeqNum]
            bigGraphs = graphContainer[i- groupSeqNum]

            #print("groupSeqNum <==> i-groupSeqNum:"+str(groupSeqNum)+" ---- "+str(i-groupSeqNum))
            print("joinGraphsOnNodes: "+str(i*2+2))
            for small in smallGraphs:
                for big in bigGraphs:
                    for n1 in small.nodes:
                        for n2 in big.nodes:

                            newGraph = joinGraphsOnNodes(small, big, n1, n2)
                            if(newGraph != None):
                                #print("ON NODES#########")
                                #print(newGraph.number_of_nodes())
                                nextGraphSet.append(newGraph)
        if(i>0):
            for groupSeqNum in range(0, ((i-1) // 2) + 1):

                smallGraphs = graphContainer[groupSeqNum]
                bigGraphs = graphContainer[i-1- groupSeqNum]

                #print("QQQQQ groupSeqNum <==> i-groupSeqNum:" + str(groupSeqNum) + " ---- " + str(i - groupSeqNum-1))
                print("joinGraphsOnEdges: "+str(i*2+2))
                for small in smallGraphs:
                    for big in bigGraphs:
                        for e1,e2,x1 in small.edges:
                            for f1,f2,x2 in big.edges:

                                newGraph = joinGraphsOnEdges(small, big, (e1,e2), (f1,f2))
                                if (newGraph != None):
                                    #print("ON EDGES")
                                    #print(newGraph.number_of_nodes())
                                    nextGraphSet.append(newGraph)

        graphContainer.append(nextGraphSet)

#generate(8)

#TODO connecting - wrong size graphs being put to grahContainer?????
#TODO implement isomorphism chech with nauty
#TODO use colorability - does it work properly


grt = nx.Graph()

grt.add_edges_from([(1,2),(2,3),(1,3),(1,4),(3,4),(2,5),(4,5)])

#,(3,4),(4,5),(5,1),(1,3),(1,4),(2,4),(2,5),(3,5)

print(grt.edges)

print(isColorable(grt))

#for set in graphContainer:
#    for g in set:
#        print(g.edges)