package ua.ansymo.hintco

import java.io.IOException
import java.util.List
import java.util.Map
import org.eclipse.core.runtime.Assert
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import static ua.ansymo.hintco.CosimRunUtils.*

class SingleCosimRunner implements ICosimRunner {
	
	protected IOutputProcessor outProcessor
	protected Logger logger = LoggerFactory.getLogger(typeof(SingleCosimRunner))
	protected IFmuLoader fmuLoader
	
	new (IOutputProcessor o, IFmuLoader l){
		outProcessor = o
		fmuLoader = l
	}
	
	override run(RootCandidateScenario rootScenario, Map<Scenario, List<PrecedenceNode>> scenarioNodesMap, String variantID) {
		Assert.isTrue(scenarioNodesMap.values.forall[sortedNodes | sortedNodes.forall[n1 | rootScenario.eAllContents.exists[n2 | n1===n2]]])
		
		val nodes = scenarioNodesMap.get(rootScenario)
		
		val units = nodes.filter(UnitInstance).toList
		
		val outVals = newHashMap() // Will store output values
		
		val Map<UnitInstance, IFmuInstance> fmuInstanceMap = newHashMap()
		
		// Instantiate each unit, and record the instance reference.
		instantiateUnits(units, fmuInstanceMap, fmuLoader, scenarioNodesMap)
		
		// setup experiment
		setupExperiment(units, fmuInstanceMap)
		
		// Go into init mode
		intoInitMode(units, fmuInstanceMap)
		
		// Takes into account hierarchical unit ports.
		val childOutputPorts = nodes.filter(OutputPortInstance).filter[!it.isInput].toList
		val childInputPorts = nodes.filter(InputPortInstance).filter[it.isInput].toSet
		
		fixPointOutputs(childOutputPorts, fmuInstanceMap, rootScenario.maxInitIterations)
		
		// Init output processor with experiment parameters
		outProcessor.initialize(rootScenario, variantID)
		logger.debug("Output processor initialized.")
		
		// Add first line
		outVals.clear()
		outProcessor.setOutputs(0, 0.0, getOutputSnapshot(units, fmuInstanceMap, outVals, null))
		logger.debug("Output line recorded for time {}.", 0.0)
		
		exitInitMode(units, fmuInstanceMap)
		
		// Run the co-sim scenario
		var time = 0.0d
		val stepSize = rootScenario.stepSize
		val stopTime = rootScenario.stopTime
		var i = 0
		while (time < stopTime || MathUtils.isApproximatelyEqual(time, stopTime, stepSize*1e-3)){
			
			executeStep(rootScenario, time, stepSize, nodes, units, childInputPorts, fmuInstanceMap, null)
			
			// Advance time
			logger.debug("Time advance: {} -> {}", time, time + stepSize)
			
			time = time + stepSize
			i = i + 1
			
			// Commit outputs
			// Record all the outputs that where not produced during the co-simulation step.
			// Note that the values in outVals might not be all in time time because getOutputs can be invoked before doStep of the same fmu.
			
			// Refresh outputs
			outVals.clear()
			outProcessor.setOutputs(i, time, getOutputSnapshot(units, fmuInstanceMap, outVals, null))
			logger.debug("Output line recorded for time {} and step {}.", time, i)
		}
		
		// Cleanup
		outProcessor.terminate()
		logger.debug("Output processor terminated.")
		freeUnits(units, fmuInstanceMap)
	}
	
	override close() throws IOException {
		fmuLoader.free()
	}
	
}