'''
@author: Claudio Gomes, Bruno Barroca and Sadaf Mustafiz
'''

from fsa_cbd_woven_classes import WhenCrossesZeroTrigger, InputConstantBlock,\
    CBDState
from CBDMultipleOutput.Source.CBD import OutputPortBlock
import numpy
import os
import csv
from fsaclasses import Event, After

class FSACBDLib():
    BLOCK_NAME_OUT = "out_"
    OUTPUT_SIGNALS = "./signals.csv"
    OUTPUT_EVENTS = "./events.csv"
        
    def __init__(self):
        pass
    
    def getTriggeredWhenTransition(self, fsaCbdModel, currentFSAState, globalState, previousGlobalState):
        '''
        This method will check whether there is any transition triggered by a zero crossing detection and if so,
        if that zero crossing has in fact occurred when comparing the new globalState with the previousGlobalState.
        '''
        triggeredTransiton = None
        
        for t in fsaCbdModel.transitions:
            if t.source == currentFSAState and isinstance(t.trigger,WhenCrossesZeroTrigger):
                varNameToCheck = t.trigger.varname
                if varNameToCheck in globalState and varNameToCheck in previousGlobalState:
                    if t.trigger.up_direction and previousGlobalState[varNameToCheck] <= 0 and 0 < globalState[varNameToCheck] :
                        triggeredTransiton = t
                        break
                    if not t.trigger.up_direction and globalState[varNameToCheck] < 0 and 0 <= previousGlobalState[varNameToCheck] :
                        triggeredTransiton = t
                        break
        
        assert triggeredTransiton == None or t.source == currentFSAState
        
        return triggeredTransiton
    
    def updateGlobalState(self ,globalState, cbdModel, cbdTime):
        '''
        The globalState is a dictionary mapping variable names to values.
        This function will get the signals for the outputPorts in the cbdModel and then
        update the globalState with the values of the outputPorts at time cbdTime
        '''
        for block in cbdModel.getBlocks():
            if (isinstance(block, OutputPortBlock)):
                port = block.getBlockName()
                signal = block.getSignal()
                # Convention: port is of the form "out_varName" to represent the global varName
                assert port.startswith(self.BLOCK_NAME_OUT), "Convention violated for output port: " + str(port)
                varName = self.stripConventionOutname(port)
                assert len(varName)>0 , "Convention violated for output port: " + str(port)
                
                mostRecentSignal = signal[-1]
                
                print mostRecentSignal.time,cbdTime
                assert numpy.isclose(mostRecentSignal.time,cbdTime, 1e-05, 1e-08), "Signal has not been computed up to time " + str(cbdTime)
                
                signalValue = mostRecentSignal.value
                globalState[varName] = signalValue
    
    def stripConventionOutname(self, name):
        return name.replace(self.BLOCK_NAME_OUT, "", 1)
    
    def updateCBDState(self, globalState, cbdModel):
        '''
        The globalState is a dictionary mapping variable names to values.
        
        This function is to be called when initializing the cbdModel.
        This function will iterate the input blocks of the CBD, which have conventioned name as "in_varName" and it will set the values for those blocks. 
        '''    
        for block in cbdModel.getBlocks():
            if isinstance(block, InputConstantBlock):
                assert block.getBlockName().startswith("in_"), "Convention violated for input block of the CBD: " + str(block.getBlockName())
                varName = block.getBlockName().replace("in_", "", 1)
                assert len(varName)>0 , "Convention violated for input block of the CBD: " + str(block.getBlockName())
                assert varName in globalState, "Input block not initialized before: " + str(block.getBlockName())
                
                block.setValue(globalState[varName])
        
        
    def dumpGlobalState(self, fsaCbdModel, globalState, fsaTime, fsaState):
        create_header = not os.path.exists(self.OUTPUT_SIGNALS);
        
        if create_header:
            self.addRemainingSignals(globalState, fsaCbdModel)
        
        # It is not a good idea to write this directly to the global state but it is more efficient.
        
        globalState["fsatime"] = fsaTime
        
        if isinstance(fsaState, CBDState):
            assert fsaState.cbd != None
            if fsaState.cbd.getClock() != None:
                globalState["current_cbdtime"] = fsaState.cbd.getClock().getTime()
            else:
                # Clock not yet initialized. Can happen.
                globalState["current_cbdtime"] = -1.0
        else:
            globalState["current_cbdtime"] = -1.0
        
        globalState["fsastate"] = fsaState.name
        
        with open(self.OUTPUT_SIGNALS, "a") as csvFile:
            writerCsv = csv.DictWriter(csvFile, fieldnames=globalState.keys())
            if create_header:
                writerCsv.writeheader()
            writerCsv.writerow(globalState)
    
    def addRemainingSignals(self, stateDict, fsaCbdModel):
        
        for state in fsaCbdModel.states:
            if (isinstance(state, CBDState)):
                for block in state.cbd.getBlocks():
                    if isinstance(block, OutputPortBlock):
                        varName = self.stripConventionOutname(block.getBlockName())
                        if not varName in stateDict:
                            stateDict[varName] = None
        
        
        return stateDict
    
    def dumpDiscreteEvent(self, fsaCbdModel, fsaTime, fsa_currentState, fsa_selectedTransition):
        create_header = not os.path.exists(self.OUTPUT_EVENTS);
        
        event = None
        if (fsa_selectedTransition != None):
            if fsa_selectedTransition.trigger != None:
                if isinstance(fsa_selectedTransition.trigger, Event):
                    event = fsa_selectedTransition.trigger.name
                elif isinstance(fsa_selectedTransition.trigger, After):
                    event = "after("+str(fsa_selectedTransition.trigger.expression)+")"
                elif isinstance(fsa_selectedTransition.trigger, WhenCrossesZeroTrigger):
                    event = "when("+str(fsa_selectedTransition.trigger.varname)+" " + ("-+" if fsa_selectedTransition.trigger.up_direction else "+-") + ")"
        
        if (event != None):
            with open(self.OUTPUT_EVENTS, "a") as csvFile:
                eventDict = {"fsatime" : fsaTime , "Event" : event}
                writerCsv = csv.DictWriter(csvFile, fieldnames=eventDict.keys())
                if create_header:
                    writerCsv.writeheader()
                writerCsv.writerow(eventDict)
                
                





















                