import os
import time
from traceback import print_exc


class GGCompiler:
    def __init__(self, graphGrammar, outputFolder):
        self.GG = graphGrammar
        self.outputFolder = outputFolder
        self.output = None

    def compileGG(self):
        print 'Generating rule of the grammar', self.GG.Name.getValue(), '...'
        # Generate all the rules of the grammar in sorted order
        ruleList = self.GG.Rules.getValue()
        ruleDict = {}
        for rule in ruleList:
            orderInt = rule.Order.getValue()
            if not ruleDict.has_key(orderInt):
                ruleDict[orderInt] = [rule]
            else:
                ruleDict[orderInt].append(rule)

        sortedKeys = ruleDict.keys()
        sortedKeys.sort()

        uniqueRuleNumber = 1
        for key in sortedKeys:
            for rule in ruleDict[key]:
                self.output = open(os.path.join(self.outputFolder, rule.Name.toString().capitalize() + '.py'), "w")
                try:
                    self.compileRule(rule)
                    print 'Generated', os.path.join(self.outputFolder, rule.Name.toString().capitalize() + '.py')
                except:
                    print_exc
                uniqueRuleNumber += 1

    def compileRule(self, rule):
        LHS = rule.LHS
        RHS = rule.RHS
        self.output.write("""'''
_____________________________________________________________

Generated File by the Rule Compiler Generator.
Written by Eugene Syriani

File generated on %s
_____________________________________________________________
'''
""" % (time.ctime()))
        for className in LHS.listNodes:
            self.output.write("""
from %s import *""" % (className))

        self.output.write("""
class %s:
  def __init__(self, name='%s'):
    self.name = name

  def __str__(self):
    return self.name

  def execute(self, listNodes, pivot=None):
    ''' Returns a new graph after the rule has been applied and the pivot of where the matching started.
        Returns None if LHS didn't match.
        A pivot node can be passed to the rule to optimize matching.
    '''
""" % ((rule.Name.toString().capitalize(), ) * 2))
        code = '\n'
        # Build N={label:[Nodes]} the list of the potential nodes of the isomorphic subgraph
        # with the LHS label as key
        code += """service = MetaEditAPILocator()
    kw = { 'tracefile' : sys.stdout }
    port = service.getMetaEditAPIPortType(**kw)

    graphType = ns0.METype_Def("FSA")
    graphType._name = "FiniteStateAutomata"

    agir = allGoodInstancesRequest()
    agir._receiver = graphType
    graph = port.allGoodInstances(agir)

    objsr = objectSetRequest()
    objsr._receiver = graph._result[0]
    listOfObjs = port.objectSet(objsr)

    relsr = relationshipSetRequest()
    relsr._receiver = graph._result[0]
    listOfRels = port.relationshipSet(relsr)
        """
        code += "C = {}\n"
        code += "N = {}\n"
        for nt in LHS.listNodes:
            for node in LHS.listNodes[nt]:
                code += "C[%s] = '%s'\n" % (node.GGLabel.getValue(), node.getClass())
                code += "N[%s] = []\n" % (node.GGLabel.getValue())

        code += """
# Build N={label:[Nodes]} the list of the potential nodes of the isomorphic subgraph
# with the LHS label as key
for label in C:
  N[label] += listNodes[C[label]]

################
# LHS Matching #
################

# Check if all nodes in LHS are in the host graph
for k in N:
  if not N[k]: return listNodes

"""

        self.write(code)
        code = ""

        # Add the eval attribute to the LHS nodes
        # flag to know if it has been evaluated
        for nt in LHS.listNodes:
            for node in LHS.listNodes[nt]:
                node.eval = False

        self.write("""
# Verify links between nodes
M = {}      # holds all the matched nodes with the corresponding label as key
""")   # the matched nodes with the corresponding label as key

        # Start with a root node (no incoming edges)
        hasRoot = False
        for nt in LHS.listNodes:
            for node in LHS.listNodes[nt]:
                if not node.in_connections_:
                    self.match(node)
                    hasRoot = True
                    break
            if hasRoot:
                break

        for nt in LHS.listNodes:
            for node in LHS.listNodes[nt]:
                if not node.eval:
                    self.match(node)

        self.write("""
# Last check if all the nodes in LHS are matched
if len(M) != len(N): return listNodes

######################
# Transform subgraph #
######################

# Remove
""")

        # Collect a dictionary of all the RHS nodes with their label as key
        RHS_labels = []
        for nt in RHS.listNodes:
            for node in RHS.listNodes[nt]:
                RHS_labels += [node.GGLabel.getValue()]

        LHS_labels = []
        for nt in LHS.listNodes:
            for node in LHS.listNodes[nt]:
                LHS_labels += [node.GGLabel.getValue()]

        modifyAttr = False
        for label in LHS_labels:
            # Remove
            if label not in RHS_labels:
                # Remove connections and node
                self.write("""# %s""")
                """
for node in M[%s].in_connections_:
  node.out_connections_.remove(M[%s])
for node in M[%s].out_connections_:
  node.in_connections_.remove(M[%s])
graph.listNodes['%s'].remove(M[%s])

                """ #% (LHS.nodeWithLabel(label).getClass(), label, label, label, label, LHS.nodeWithLabel(label).getClass(), label))

            # Modify attributes
            elif RHS.nodeWithLabel(label).generatedAttributes:
                if not modifyAttr:
                    self.write("""# Modify attributes
""")
                    modifyAttr = True       # only the first time
                self.write("""# %s
""" % (LHS.nodeWithLabel(label).getClass()))

                for attr in RHS.nodeWithLabel(label).generatedAttributes:
                    if not RHS.nodeWithLabel(label).getAttrValue(attr).isNone():
                         self.write("""M[%s].%s.setValue(%s)

""" % (label, attr, str(RHS.nodeWithLabel(label).getAttrValue(attr).getValue())))

        if not modifyAttr:      # for esthetic
            self.write("""# Modify attributes
""")
        # Create new nodes
        self.write("""# Create new nodes
""")
        new_labels = []
        for label in RHS_labels:
            if label not in LHS_labels:
                new_labels += [label]
                className = RHS.nodeWithLabel(label).getClass()
                self.write("""# %s
M[%s] = %s()
listNodes['%s'] = M[%s]
""" % (className, label, className, className, label))
                for attr in RHS.nodeWithLabel(label).realOrder:
                    if RHS.nodeWithLabel(label).getAttrValue(attr):
                         self.write("""# %s
M[%s].%s.setValue(%s)

""" % (LHS.nodeWithLabel(label).getClass(), label, attr, str(RHS.nodeWithLabel(label).getAttrValue(attr).getValue())))
        # Link the new nodes




        for label in new_labels:
            for inp in RHS.nodeWithLabel(label).in_connections_:
                self.write("""# %s -> %s
M[%s].out_connections_.append(M[%s])
M[%s].in_connections_.append(M[%s])
""" % (inp.getClass(), RHS.nodeWithLabel(label).getClass(), inp.GGLabel.getValue(), label, label, inp.GGLabel.getValue()))
            for out in RHS.nodeWithLabel(label).out_connections_:
                self.write("""# %s -> %s
M[%s].in_connections_.append(M[%s])
M[%s].out_connections_.append(M[%s])
""" % (RHS.nodeWithLabel(label).getClass(), out.getClass(), out.GGLabel.getValue(), label, label, out.GGLabel.getValue()))

        self.write("""
return graph, None
""")


    def match(self, node, previous=[], direction='', indent=0):
        ''' node: node to match
            previous: queue of (n,l) where:
                n: variable name of previously matched nodes
                l: label of n
            direction: direction form n to node
            indent: for formatting code generation
        '''
        ind = '  '
        label = str(node.GGLabel.getValue())
        var = node.getClass().lower() + label       # the variable name

        self.write(ind * indent + """for %s in N[%s]:
""" % (var, label))

        # Check conditions on attributes
        cond = ''
        for attr in node.realOrder:
            if not node.getAttrValue(attr).isNone():    # if it is not set to <ANY>
                cond +=  "%s.%s.getValue() == %s and " % (var, attr, str(node.getAttrValue(attr).getValue()))
        if cond:
            indent += 1
            self.write(ind * indent + "if " + cond[:len(cond) - 5] + ":\n")

        if previous and direction:
            indent += 1
            self.write(ind * indent + """if %s in %s.%s_connections_:
""" % (var, previous[-1][0], direction))

        # Match the connected nodes
        node.eval = True
        isLeaf = True
        for inp in node.in_connections_:
            if not inp.eval:
                isLeaf = False
                self.match(inp, previous + [(var, label)], 'in', indent + 1)
        for out in node.out_connections_:
            if not out.eval:
                isLeaf = False
                self.match(out, previous + [(var, label)], 'out', indent + 1)

        if not isLeaf: return
        # Store the nodes if match succeeded
        reachedEnd = True
        for inp in node.in_connections_:
            if not inp.eval:
                reachedEnd = False
                break
        for out in node.out_connections_:
            if not out.eval:
                reachedEnd = False
                break
        if reachedEnd:
            indent += 1
            self.write(ind * indent + """M[%s] = %s
""" % (label, var))
            if previous:
                rev_prev = previous
                rev_prev.reverse()
                for n,l in rev_prev:
                    self.write(ind * indent + """M[%s] = %s
""" % (l, n))
            self.write(ind * indent + """break
""")


    def write(self, code):
        code = code.replace('\n', '\n    ')
        self.output.write(code)
