from visitor import Visitor
from constructs import INSTATE_SEQ
from compiler_exceptions import CompilerException
from lexer import Lexer, Token, TokenType

class StateReferenceException(CompilerException):
    pass

class StateLinker(Visitor):
    
    def __init__(self):
        self.visiting_statechart = None
        self.visiting_node = None
        self.lexer = Lexer()
    
    def visit_ClassDiagram(self, class_diagram): 
        for c in class_diagram.classes :
            c.accept(self)

    def visit_Class(self, c):
        c.statechart.accept(self)
        
    def visit_StateChart(self, statechart):
        self.visiting_statechart = statechart
        for node in statechart.basics + statechart.composites:
            node.accept(self)
                     
    def visit_StateChartNode(self, node):
        self.visiting_node = node
        node.enter_action.accept(self)
        node.exit_action.accept(self)
        for transition in node.transitions :
            transition.accept(self)
            
    def visit_StateChartTransition(self, transition):
        try :
            transition.target.accept(self)
        except StateReferenceException as exception :
            raise StateReferenceException("Transition from <" + self.visiting_node.full_name + "> has invalid target. " + exception.message)
        try :
            transition.action.accept(self)
        except StateReferenceException as exception :
            raise StateReferenceException("Transition from <" + self.visiting_node.full_name + "> has invalid action. " + exception.message)
        try :
            if transition.guard :
                transition.guard.accept(self)
        except StateReferenceException as exception :
            raise StateReferenceException("Transition from <" + self.visiting_node.full_name  + "> has invalid guard. " + exception.message)
        
    def visit_StateReference(self, state_reference):
        state_reference.target_nodes = []
        
        current_node = None #Will be used to find the target state(s)
        split_stack = [] #used for branching

        self.lexer.input(state_reference.path_string)

        for token in self.lexer.tokens() :
            
            if current_node == None : #current_node is not set yet or has been reset, the CHILD token can now have a special meaning
                if token.type == TokenType.SLASH :
                    #Root detected
                    current_node = self.visiting_statechart.root
                    #Token consumed so continue
                    continue
                else :
                    current_node = self.visiting_node
                    
            if token.type == TokenType.DOT :
                #Advance to next token
                token = self.lexer.nextToken()
                
                if token is None or token.type == TokenType.SLASH :
                    #CURRENT operator "." detected
                    continue
                elif token.type == TokenType.DOT :
                    #Advance to next token
                    token = self.lexer.nextToken()
                    if token is None or token.type == TokenType.SLASH :
                        #PARENT operator ".." detected
                        current_node = current_node.parent
                        if current_node is None :
                            raise StateReferenceException("Illegal use of PARENT \"..\" operator at position " + str(token.pos) + " in state reference. Root of statechart reached.")
                    
                    else :
                        raise StateReferenceException("Illegal use of PARENT \"..\" operator at position " + str(token.pos) + " in state reference.")
    
                else :
                    raise StateReferenceException("Illegal use of CURRENT \".\" operator at position " + str(token.pos) + " in state reference.")
                    
            elif token.type == TokenType.SLASH :
                continue
            elif token.type == TokenType.WORD :
                #try to advance to next child state
                cname = token.val
                found = False
                for child in current_node.children :
                    if child.name == cname : 
                        found = True
                        current_node = child
                        break
                if not found :
                    raise StateReferenceException("Refering to non exiting node at posisition " + str(token.pos) + " in state reference.")
            elif token.type == TokenType.LBRACKET :
                split_stack.append(current_node)
            elif token.type == TokenType.RBRACKET :
                if len(split_stack) > 0 :
                    split_stack.pop()
                else :
                    raise StateReferenceException("Invalid token at position " + str(token.pos) + " in state reference.")
            elif token.type == TokenType.COMMA :
                state_reference.target_nodes.append(current_node)
                if len(split_stack) > 0 :
                    current_node = split_stack[-1]
                else :
                    current_node = None
            
            else :
                raise StateReferenceException("Invalid token at position " + str(token.pos) + " in state reference.")
        
        if (len(split_stack) != 0) or (current_node is None) : #RB missing or extra COMMA
            raise StateReferenceException("State reference ends unexpectedly.")
        
        #TODO better validation of the target! When is it a valid state configuration?
        for node in state_reference.target_nodes :
            if current_node == node :
                raise StateReferenceException("A state reference can't reference the same node more than once.")
            if node.isDescendantOrAncestorOf(current_node) :
                raise StateReferenceException("A state reference can't reference a node and one of its descendants.");
        
        state_reference.target_nodes.append(current_node)
            
        if len(state_reference.target_nodes) == 0 :
            raise StateReferenceException("Meaningless state reference.")
                
    def visit_EnterExitAction(self, action):
        if action.action :
            action.action.accept(self)
            
    def visit_Action(self, action):
        for subaction in action.sub_actions :
            subaction.accept(self)
            
    def visit_InStateCall(self, call):
        try :
            call.target.accept(self)
        except StateReferenceException :
            raise StateReferenceException("Invalid state reference for " + INSTATE_SEQ + " call.")
        
    def visit_Assign(self, assign):
        assign.expression.accept(self)