import numpy
from operator import attrgetter

############### runtime classes

class RunTimeEvent:
    def __init__(self, name, time):
        self.name = name
        self.time = time
        self.processed = False

class Events:
    def __init__(self, events):
        self.events = sorted(events, key=attrgetter('time'))
    
    '''
    Returns the most recent event with the timestamp smaller than time.
    '''
    def getInputAt(self,time):
        event = None
        if self.events != []:
            # Claudio: this approach is assuming that the queue is sorted according the time of the events.
            for e in self.events:
                if e.time < time or numpy.isclose(e.time, time, 1e-05, 1e-08):
                    event = e
                else:
                    # stop at the first event that satisfies e.time > time
                    break
        return event
    
    '''
        Removes event all other older events from the queue.
    '''
    def popEvent(self, event):
        if event == None:
            return
        
        assert event in self.events
        # Claudio: this approach is assuming that the queue is sorted according the time of the events.
        # first collect list of events to be removed.
        # then remove them.
        eventsToBeRemoved = []
        for e in self.events:
            eventsToBeRemoved.append(e)
            if e == event:
                break
        assert len(eventsToBeRemoved)>0
        for e in eventsToBeRemoved:
            self.events.remove(e)
        

############### static classes - invariants

class Expression:
    def __init__(self):
        pass

class FSAModel:
    def __init__(self,states,transitions):
        self.transitions = transitions
        self.states = states
        self.initialState = None
        
    def getTransitionFrom(self, state, event, elapsed):
        assert state != None
        assert elapsed >= 0.0
        
        # Priotiry goes to event.
        selectedTransition = None
        
        if event!=None:
            selectedTransition = self.getTriggeredTransition(state, event)
        
        # Then to after 
        if (selectedTransition == None):
            selectedTransition = self.getTransitionAfter(state, elapsed)
        
        # And finally to default transitions
        if (selectedTransition == None):
            selectedTransition = self.getDefaultTransition(state)
        
        return selectedTransition
    
    def getDefaultTransition(self, state):
        assert state != None
        for t in self.transitions:
                if t.source == state and t.trigger == None:
                    return t
        
        return None
    
    
    def getTriggeredTransition(self, state, event):
        assert event != None
        for t in self.transitions:
                if t.source == state and isinstance(t.trigger,Event) and t.trigger.name == event.name:
                        return t
        
        return None
    
    def getTransitionAfter(self, state, elapsed):
        for t in self.transitions:
            if t.source == state and isinstance(t.trigger,After):
                if ExpressionVisitor(t.trigger.after).visit() <= elapsed:
                    return t
        return None 

class ExpressionVisitor:
    def __init__(self, expression):
        self.expression = expression
        
    def visit(self):
        if(isinstance(self.expression,AtomValue)):
            return self.expression.value
        if(isinstance(self.expression,Operation)):
            left = ExpressionVisitor(self.expression.left).visit()
            right = ExpressionVisitor(self.expression.right).visit()
            if(self.expression.op == '+'):
                return left + right
            if(self.expression.op == '-'):
                return left - right
            if(self.expression.op == '*'):
                return left * right
            if(self.expression.op == '/'):
                return left / right
            if(self.expression.op == 'and'):
                return left and right
            if(self.expression.op == 'or'):
                return left or right
        if(isinstance(self.expression,Not)):
            return not ExpressionVisitor(self.expression.expression).visit()

class State:
    def __init__(self, name, final = False):
        self.name = name
        self.final = final
        
    def getName(self):
        return self.name
    
    def __str__(self):
        return self.name
    
    
class Operation(Expression):
    def __init__(self, left, right, op):
        self.op = op
        self.left = left
        self.right = right
        
class Transition:
    def __init__(self,name, source,target):
        self.name = name
        self.source = source
        self.target = target
        self.trigger = None
        self.guard = None
    
    def __str__(self):
        return str(self.source) + "--" + self.name + ": " +  str(self.trigger) + "-->" + str(self.target)
    
class Trigger:
    def __init__(self):
        pass

class Event(Trigger):
    def __init__(self,name):
        self.name = name
        
    def __str__(self):
        return self.name
    
class After(Trigger):
    def __init__(self, expression):
        self.after = expression
    
    def __str__(self):
        return "after("+ str(self.after) + ")"
    
class Guard:
    def __init__(self, expression):
        self.expression = expression

class And(Operation):
    def __init__(self, lexpression, rexpression):
        self.left = lexpression
        self.right = rexpression
        self.op = "and"

class Or(Operation):
    def __init__(self, lexpression, rexpression):
        self.left = lexpression
        self.right = rexpression
        self.op = "or"

class Not(Expression):
    def __init__(self, expression):
        self.expression = expression

class Variable(Expression):
    def __init__(self, varname):
        self.name = varname

class AtomValue(Expression):
    def __init__(self, value):
        self.value = value
        
    def __str__(self):
        return str(self.value)
    

class Integer(AtomValue):
    def __init__(self, value):
        self.value = value
    
class Float(AtomValue):
    def __init__(self, value):
        self.value = value

class String(AtomValue):
    def __init__(self, value):
        self.value = value
