from maquinas.regular.fa import FiniteAutomaton
from maquinas.exceptions import *
from IPython.core.display import display, HTML
from graphviz import Digraph

class DeterministicFiniteAutomaton(FiniteAutomaton):
    """Class for Deterministic Finite Automaton

    :param Q: Ordered set of states (default is empty).
    :param sigma: Ordered set of symbols (default is empty).
    :param q_0: Initial state (default None).
    :param A: Set of acceptor states (default is empty).
    :param delta: List of transitions with the form tupla of tupla of q_i and a, and q_f (default is empty).
    :param force: If True and states or symbols do not exists create them (default is False).
    :type force: bool
    """

    def __init__(self, Q=[], sigma=[], q_0=None, A=[], delta={}, force=False):
        super().__init__(Q,sigma,q_0,A,delta)

    def delta(self,q,a):
        """ Applies delta function

        :param q: Source state
        :param a: Symbol

        :returns: Destination state"""
        return self.get_transition(q,a)

    def delta_extended(self,q,w):
        """ Applies delta extended function

        :param q: Source state
        :param w: String

        :returns: Destination state after processing the full string"""
        if q is None:
            q=self.q_0
        if len(w)==0:
            return q
        else:
            *w_,a=w
            return self.delta(self.delta_extended(q,w_),a)

    def delta_stepwise(self,w,q=None):
        """ Applies a step of delta extended function

        :param w: String
        :param q: Source state (default is initial state)

        :returns: Tuple with state of precessing at step, consisting of: destination state, procesed symbol and processed string"""

        q_c= self.q_0 if q is None else q
        yield q_c,"",w
        for ix,a in enumerate(w):
            q_c=self.delta(q_c,a)
            yield q_c,a,w[ix+1:]

    def graph(self,q_c=set(),a_c=set(),q_prev=set(),symbols={},states={},format="svg",dpi="60.0",string=None,**args):
        """ Graphs DFA

        :param q_c: Set of current states to be highlited (default is empty)
        :param a_c: Set of current symbols to be highlited (default is empty)
        :param q_prev: Set of previos states to be highlited (default is empty)
        :param symbols: Replacements of the symbols to show (default is empty)
        :param states: Replacements of the states to show (default is empty)
        :param format: Format of image (default is svg)
        :param dpi: Resolution of image (default is "60.0")
        :param string: Label of string being analysed (default is None)
        :returns: Returns Digraph object from graphviz"""

        f=Digraph(comment="dfa",format=format)
        f.attr(rankdir='LR',dpi=dpi)
        color_state="lightblue2"
        if string:
            l,c,r=string
            f.attr(label=f"<{l} <B>{c}</B> {r}>")
            if len(r)==0:
                if len(self.A.intersection(q_c))>0:
                    color_state="limegreen"
                else:
                    color_state="orangered"

        f.node(name="__initial__",label="",shape="none",height=".0",width=".0")
        for q,q_ in self.Q.map.items():
            if q in self.A:
                shape="doublecircle"
            else:
                shape="circle"
            if q in q_c:
                f.node(name=q,label=states.get(q,q),shape=shape,color=color_state,style="filled")
            else:
                f.node(name=q,label=states.get(q,q),shape=shape)
        f.edge("__initial__",self.q_0)
        for e,info in enumerate(self.items()):
            (q_i,a),q_f = info
            if q_i in q_prev and a in a_c:
                f.edge(q_i,q_f,label=symbols.get(a,a),color=color_state,fontcolor=color_state)
            else:
                f.edge(q_i,q_f,label=symbols.get(a,a))
        return f

    def table(self,symbols={},states={},q_order=None,s_order=None,color_final="#32a852"):
        """ Creates an HTML object for the table of the DFA

        :param symbols: Replacements of the symbols to show (default is empty)
        :param states: Replacements of the states to show (default is empty)
        :param  q_order: Order to use for states
        :param  s_order: Order to use for symbols
        :param color_final: RGB string for color of final state (default is "#32a852")

        :returns: Display object for IPython"""
        if not s_order:
            s_order=list(self.sigma)
            s_order.sort()
        if not q_order:
            q_order=list(self.Q)
            q_order.sort()
        symbs_h="</strong></td><td><strong>".join([states.get(q,q) for q in s_order])
        table=f"<table><tr><td></td><td><strong>{symbs_h}</strong></td></tr>"
        for q_i in q_order:
            vals=[]
            initial="⟶" if q_i == self.q_0 else "" 
            final=f'bgcolor="{color_final}"' if q_i in self.A else ""
            vals.append(f"<strong>{initial}{states.get(q_i,q_i)}</strong>")
            for a in s_order:
                try:
                    q_f=self[q_i,a]
                    vals.append(states.get(q_f,q_f))
                except DoesNotExistsTransition:
                    vals.append("")
            row="</td><td>".join(vals)
            table+=f"<tr><td {final}>{row}</td></tr>"
        table+="</table>"
        return display(HTML(table))
