import re
import copy
import importlib as imp

from comsdk.graph import Graph, Func, State
from comsdk.edge import Edge

class Params():
    __slots__=(
        'module',
        'entry_func',
        'predicate',
        'selector',
        'function',
        'morphism',
        'parallelism',
        'comment',
        'order'
    )
    def __init__(self):
        for slot in self.__slots__:
            setattr(self, slot, None)
    
    def __str__(self):
        stri = ""
        for s in self.__slots__:
            stri += s+": {}, ".format(getattr(self, s))
        return stri

entities = {}

class GraphFactory():
    __slots__ = (
        'states', 
        'graph',
        'tocpp',
        'entities'
    )
    def __init__(self, tocpp=False):
        self.states = {}
        self.tocpp = tocpp
    
    def add_state(self, statename, selectorname=None):
        if selectorname is not None:
            sp = entities[selectorname]
            if self.tocpp:
                sel_f = Func(sp.module, sp.entry_func, dummy=True)
            else:
                sel_f = Func(sp.module, sp.entry_func, getattr(imp.import_module(sp.module), sp.entry_func))
        else:
            sel_f = Func()
        if statename not in self.states:
            newstate = State(statename, selector=sel_f)
            self.states[statename] = newstate
    
    def _create_morphism(self, morphname=None):
        if morphname is None:
            return Func(), Func()
        pred_f, func_f  = Func(), Func()
        morph = entities[morphname]
        for m in morph.__slots__:
            if getattr(morph,m) != None:
                if m!="predicate" and m!="function":
                    print("ERROR: Morphisms could not have any params exept predicate and function!\n{}".format(morphname))
                    exit(0)
                if m=="predicate":
                    if getattr(morph,m) not in entities:
                        print("\tERROR: Predicate {} is not defined!".format(getattr(morph, m)))
                        exit(0)
                    pred = entities[getattr(morph, m)]
                    if self.tocpp:
                        pred_f = Func(pred.module, pred.entry_func, dummy=True)
                    else:
                        pred_f = Func(pred.module, pred.entry_func)
                if m=="function":
                    if getattr(morph,m) not in entities:
                        print("\tERROR: Function: {} is not defined!".format(getattr(morph, m)))
                        exit(0)
                    fu = entities[getattr(morph, m)]
                    if self.tocpp:
                        func_f = Func(fu.module, fu.entry_func, dummy=True)
                    else:
                        func_f = Func(fu.module, fu.entry_func)
        return pred_f, func_f


    def add_connection(self, st1, st2, morphism=None):
        pred, entr = self._create_morphism(morphism)
        self.states[st1].connect_to(self.states[st2], edge=Edge(pred, entr))
        # print("{} --{}-{}--> {}".format(st1, pred, entr, st2))

    def build(self):
        self.graph = Graph(self.states["__BEGIN__"], self.states["__END__"])
        self.graph.init_graph()
        return self.graph

class Parser():
    __slots__ = (
        'entities',
        'fact'
    )

    def __init__(self, tocpp=True):
        self.entities = {}
        self.fact = GraphFactory(tocpp=True)
    
    def _check_brackets(self, rawfile):
        br = 0
        qu = 0
        for char in rawfile:
            if char == "[":
                br+=1
            elif char == "{":
                br+=1
            elif char == "(":
                br+=1
            elif char == "]":
                br-=1
            elif char == "}":
                br-=1
            elif char == ")":
                br-=1
            elif char =="\"":
                qu+=1
        if br!=0 or qu%2!=0:
            print("Brackets or quotes do not match! Check your file")
            exit(-1)

    def _split_multiple(self,param):
        res = []
        first=True
        for s in param.__slots__:
            attr = getattr(param,s)
            if attr is not None and '\0' in attr:
                vals = attr.split('\0')
                for v in vals:
                    par = copy.copy(param)
                    setattr(par, s, v)
                    res.append(par)
        return res

    #Props is line "[proFp=smth, ...]"
    def _param_from_props(self,props):
        parm = Params()
        props = props.replace("]", '')
        if '(' in props:
            mchs = [m for m in re.finditer(r'\((\w+,)*\w+\)', props)]
            for m in mchs:
                props=props[:m.span()[0]]+(props[m.span()[0]:m.span()[1]]).replace(',','\0')+props[m.span()[1]:]
        props = props.replace("(","")
        props = props.replace(")","")
        rs =props.split(r",") #.split(r", ")
        for r in rs:
            r=r.split(r"=", 1)
            if r[0] in parm.__slots__:
                setattr(parm, r[0], r[1])
            else:
                print("\tERROR:Unknown parameter: "+ r[0])
                exit(-1)
        print(parm)
        return parm

    def _param_from_entln(self, raw):
        res = re.split(r"\[", raw, 1)
        return res[0], self._param_from_props(res[1])

    def _topology(self,raw):
        spl = re.split(r"\s*(=>|->|\[|\])\s*", raw)
        spl = list(filter(lambda x: x!="[" and x!="]" and x!="", spl))
        left = spl[0].split(",")
        right = spl[2].split(",")
        if (len(left)>1) and (len(right)>1):
            print("ERROR:Ambigious multiple connection in line:\n\t{}".format(raw))
            exit()
        # many to one conection
        elif len(left)>1:
            p = self._param_from_props(spl[3])
            morphs = self._split_multiple(p)
            if len(morphs)!=len(left):
                print("\tERROR:Count of edges do not match to count of states in many to one connection!\n\t\t{}".format(raw))
                exit()
            self.fact.add_state(right[0])
            for i, st in enumerate(left):
                self.fact.add_state(st)
                self.fact.add_connection(st, right[0], morphs[i].morphism)
        # one to many connection, here could be selector
        elif len(right)>1:
            p = self._param_from_props(spl[3])
            self.fact.add_state(left[0],selectorname=p.selector)
            morphs = self._split_multiple(p)
            for m in morphs:
                print(m)
            if len(morphs)!=len(right):
                print("\tERROR:Count of edges do not match to count of states in one to many connection!\n\t\t{}".format(raw))
                exit()
            for i, st in enumerate(right):
                self.fact.add_state(st)
                self.fact.add_connection(left[0], st, morphs[i].morphism)
        # one to one connection
        else:
            self.fact.add_state(left[0])
            self.fact.add_state(right[0])
            if len(spl)==4:
                pr =self._param_from_props(spl[3])
                self.fact.add_connection(left[0], right[0], pr.morphism)
            elif len(spl)==3:
                self.fact.add_connection(left[0], right[0], None)

    def parse_file(self, filename):
        file = open(filename, "r")
        dot = file.read()
        self._check_brackets(dot)
        
        dot = re.sub(r"[ \t\r]", "", dot) #deleting all spaces
        dot = re.sub(r"((digraph\w+\n?)|}|{)", "", dot)
        dot = re.sub(r"\/\/.*", "", dot)
        dot = re.sub(r"^\n$", "", dot)
        #print("Checking graph...")
        #graphcheck = re.search(r"\A(digraph)\w+\n?{((\/\/)*.*\n*)+}\Z", dot)
        #if graphcheck is None:
        #    print("Incorrect graph, check syntax!")
        #    exit()
        #
        #print("Graph id good, processing!")
        #dot = re.sub(r"//*$", "", dot) 
        dotlines = dot.splitlines()
        dotlines = list(filter(None, dotlines))
        # ent_re - regular expr for edges, states, functions properties
        ent_re = re.compile(r"^\w+\[.*\]$")
        # top_re - regular expr for topology properties, most time consuming one
        top_re = re.compile(r"^(\w+,?)+(->|=>)(\w+,?)+(\[(\w+=(\(?\w+,?\)?)+,?)+\])?")
        # (r"^\w[\w\s,]*(->|=>)\s*\w[\w\s,=\[\]()]*$")
        for i, ln in enumerate(dotlines):
            if ent_re.match(ln):
                name, parm = self._param_from_entln(ln)
                entities[name] = parm
            elif top_re.match(ln):
                self._topology(ln)
        self.entities = entities
        return self.fact.build()

pars = Parser()
graph = pars.parse_file("./test.adot")