package ua.ansymo.hintco.test

import java.io.IOException
import java.util.LinkedList
import java.util.List
import java.util.Map
import java.util.zip.ZipException
import javax.xml.xpath.XPathExpressionException
import org.apache.commons.io.IOUtils
import org.intocps.fmi.Fmi2Status
import org.intocps.fmi.Fmi2StatusKind
import org.intocps.fmi.FmiInvalidNativeStateException
import org.intocps.fmi.FmuInvocationException
import org.intocps.fmi.FmuMissingLibraryException
import org.intocps.fmi.FmuResult
import org.intocps.fmi.IFmiComponent
import org.intocps.fmi.IFmiComponentState
import org.intocps.fmi.IFmu
import org.intocps.fmi.IFmuCallback
import org.intocps.fmi.InvalidParameterException
import org.junit.Test
import ua.ansymo.hintco.AdaptedFMUInstance
import ua.ansymo.hintco.CandidatesGenerator
import ua.ansymo.hintco.ConstraintChecker
import ua.ansymo.hintco.CosimUnitInstance
import ua.ansymo.hintco.IFmuLoader
import ua.ansymo.hintco.IOutputProcessor
import ua.ansymo.hintco.MathUtils
import ua.ansymo.hintco.ModelStorage
import ua.ansymo.hintco.OutputPortInstance
import ua.ansymo.hintco.PrecendenceNode
import ua.ansymo.hintco.RootCandidateScenario
import ua.ansymo.hintco.Scenario
import ua.ansymo.hintco.SingleCosimRunner
import ua.ansymo.hintco.UnitInstance
import ua.ansymo.hintco.VariantProcessor
import ua.ansymo.hintco.VariantValidator

import static org.junit.Assert.*

class AdaptedFMUTest {
	
	@Test
	def void InterpolationTest(){
		val loader = new ModelStorage()
		
		val src = loader.loadCandidates("instances/interpolation_test.xmi")
		
		val source = src.eAllContents.filter(CosimUnitInstance).findFirst[u | u.identifier == "Source"]
		val sink = src.eAllContents.filter(CosimUnitInstance).findFirst[u | u.identifier == "Sink"]
		
		val numInnerSteps = new LinkedList()
		
		val mockLoader = new IFmuLoader{
			override instantiate(UnitInstance u, Map<Scenario, List<PrecendenceNode>> scenarioNodesMap) {
				assertTrue(u===source || u===sink)
				if (u===source){
					return new AdaptedFMUInstance(u, new MockSourceFmuInstance[t, H | 
						t+H
					], null)
				}
				if (u===sink){
					return new AdaptedFMUInstance(u, new MockSinkFmuInstance[t, H, in | 
						numInnerSteps.add(t)
						assertTrue(MathUtils.isApproximatelyEqual(in, t+H, 1e-6))
					], null)
				}
				return null
			}
			override free() {}
		}
		
		val outputCheck = new IOutputProcessor{
			override initialize(RootCandidateScenario scenario, String variantID) {}
			override setOutputs(int i, double d, Map<OutputPortInstance, Double> map) {
				val o = map.get(map.keySet.head)
				assertTrue(MathUtils.isApproximatelyEqual(o, d, 1e-6))
			}
			override terminate() {}
		}
		val runner = new SingleCosimRunner(outputCheck, mockLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.createVariantTree(src)
		generator.generateVariants(src)
		runner.close()
		assertEquals(110, numInnerSteps.size)
	}
	
	@Test
	def void zeroOrderInterpolationTest(){
		val loader = new ModelStorage()
		
		val src = loader.loadCandidates("instances/zero_interpolation_test.xmi")
		
		val source = src.eAllContents.filter(CosimUnitInstance).findFirst[u | u.identifier == "Source"]
		val sink = src.eAllContents.filter(CosimUnitInstance).findFirst[u | u.identifier == "Sink"]
		
		val numInnerSteps = new LinkedList()
		
		val mockLoader = new IFmuLoader{
			override instantiate(UnitInstance u, Map<Scenario, List<PrecendenceNode>> scenarioNodesMap) {
				assertTrue(u===source || u===sink)
				if (u===source){
					return new AdaptedFMUInstance(u, new MockSourceFmuInstance[t, H | 
						t+H
					], null)
				}
				if (u===sink){
					return new AdaptedFMUInstance(u, new MockSinkFmuInstance[t, H, in | 
						numInnerSteps.add(t)
						assertTrue(MathUtils.isApproximatelyEqual(in, t+H, 1e-6))
					], null)
				}
				return null
			}
			override free() {}
		}
		
		val outputCheck = new IOutputProcessor{
			override initialize(RootCandidateScenario scenario, String variantID) {}
			override setOutputs(int i, double d, Map<OutputPortInstance, Double> map) {
				val o = map.get(map.keySet.head)
				assertTrue(MathUtils.isApproximatelyEqual(o, d, 1e-6))
			}
			override terminate() {}
		}
		
		val runner = new SingleCosimRunner(outputCheck, mockLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.createVariantTree(src)
		generator.generateVariants(src)
		runner.close()
		assertEquals(11, numInnerSteps.size)
	}
	
	@Test
	def void ExtrapolationTest(){
		val loader = new ModelStorage()
		
		val src = loader.loadCandidates("instances/extrapolation_test.xmi")
		
		val source = src.eAllContents.filter(CosimUnitInstance).findFirst[u | u.identifier == "Source"]
		val sink = src.eAllContents.filter(CosimUnitInstance).findFirst[u | u.identifier == "Sink"]
		
		val numInnerSteps = new LinkedList()
		
		val mockLoader = new IFmuLoader{
			override instantiate(UnitInstance u, Map<Scenario, List<PrecendenceNode>> scenarioNodesMap) {
				assertTrue(u===source || u===sink)
				if (u===source){
					return new AdaptedFMUInstance(u, new MockSourceFmuInstance[t, H | 
						t+H
					], null)
				}
				if (u===sink){
					return new AdaptedFMUInstance(u, new MockSinkFmuInstance[t, H, in | 
						numInnerSteps.add(t)
						if (t > 2.0) {
							assertTrue(MathUtils.isApproximatelyEqual(in, t+H, 1e-6))
						}
					], null)
				}
				return null
			}
			override free() {}
		}
		
		val outputCheck = new IOutputProcessor{
			override initialize(RootCandidateScenario scenario, String variantID) {}
			override setOutputs(int i, double t, Map<OutputPortInstance, Double> map) {
				val o = map.get(map.keySet.head)
				val match = MathUtils.isApproximatelyEqual(o, t, 1e-6)
				assertTrue(match)
			}
			override terminate() {}
		}
		val runner = new SingleCosimRunner(outputCheck, mockLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.createVariantTree(src)
		generator.generateVariants(src)
		runner.close()
		assertEquals(110, numInnerSteps.size)
	}
	
}


class MockSourceFmuInstance extends MockFMUInstance {
	var output = 0.0d
	
	var (double, double)=>double computeOutput
	
	new((double, double)=>double c){
		computeOutput = c
	}
	
	override doStep(double currentCommunicationPoint, double communicationStepSize,
		boolean noSetFMUStatePriorToCurrentPoint) throws FmuInvocationException {
		output = computeOutput.apply(currentCommunicationPoint, communicationStepSize)
		return Fmi2Status.OK
	}
	override getReal(long[] scalarValueIndices) throws FmuInvocationException {
		return new FmuResult(Fmi2Status.OK, #[output])
	}
}

class MockSinkFmuInstance extends MockFMUInstance {
	var input = 0.0d
	var (double, double, double)=>void step
	new((double, double, double)=>void s){
		step = s
	}
	override doStep(double currentCommunicationPoint, double communicationStepSize,
		boolean noSetFMUStatePriorToCurrentPoint) throws FmuInvocationException {
		step.apply(currentCommunicationPoint, communicationStepSize, input)
		return Fmi2Status.OK
	}
	
	override setReals(long[] scalarValueIndices,
		double[] values) throws InvalidParameterException, FmiInvalidNativeStateException {
		input = values.head
		Fmi2Status.OK
	}
}

class MockFMUInstance implements IFmiComponent {
	override doStep(double currentCommunicationPoint, double communicationStepSize, boolean noSetFMUStatePriorToCurrentPoint) throws FmuInvocationException {Fmi2Status.OK}
	override getReal(long[] scalarValueIndices) throws FmuInvocationException {}
	override enterInitializationMode() throws FmuInvocationException {Fmi2Status.OK}
	override exitInitializationMode() throws FmuInvocationException {Fmi2Status.OK}
	override freeInstance() throws FmuInvocationException {}
	override freeState(IFmiComponentState state) throws FmuInvocationException {Fmi2Status.OK}
	override getBooleanStatus(Fmi2StatusKind kind) throws FmuInvocationException {}
	override getBooleans(long[] scalarValueIndices) throws FmuInvocationException {}
	override getDirectionalDerivative(long[] vUnknownRef, long[] vKnownRef,
		double[] dvKnown) throws FmuInvocationException {}
	override getInteger(long[] scalarValueIndices) throws FmuInvocationException {}
	override getIntegerStatus(Fmi2StatusKind kind) throws FmuInvocationException {}
	override getMaxStepSize() throws FmiInvalidNativeStateException {}
	override getRealOutputDerivatives(long[] scalarValueIndices, int[] order) throws FmuInvocationException {}
	override getRealStatus(Fmi2StatusKind kind) throws FmuInvocationException {}
	override getState() throws FmuInvocationException {}
	override getStatus(Fmi2StatusKind kind) throws FmuInvocationException {}
	override getStringStatus(Fmi2StatusKind kind) throws FmuInvocationException {}
	override getStrings(long[] scalarValueIndices) throws FmuInvocationException {}
	override isValid() { true }
	override reset() throws FmuInvocationException {}
	override setBooleans(long[] scalarValueIndices,
		boolean[] values) throws InvalidParameterException, FmiInvalidNativeStateException {}
	override setDebugLogging(boolean loggingOn, String[] categories) throws FmuInvocationException {}
	override setIntegers(long[] scalarValueIndices,
		int[] values) throws InvalidParameterException, FmiInvalidNativeStateException {}
	override setRealInputDerivatives(long[] scalarValueIndices, int[] order,
		double[] derivatives) throws FmuInvocationException {}
	override setReals(long[] scalarValueIndices,
		double[] values) throws InvalidParameterException, FmiInvalidNativeStateException {Fmi2Status.OK}
	override setState(IFmiComponentState state) throws FmuInvocationException {}
	override setStrings(long[] scalarValueIndices,
		String[] values) throws InvalidParameterException, FmiInvalidNativeStateException {}
	override setupExperiment(boolean toleranceDefined, double tolerance, double startTime, boolean stopTimeDefined,
		double stopTime) throws FmuInvocationException {}
	override terminate() throws FmuInvocationException {}
	override getFmu() {
		return new IFmu {
			override getModelDescription() throws ZipException, IOException {
				return IOUtils.toInputStream('''
					<?xml version="1.0" encoding="UTF-8"?>
					<fmiModelDescription
					  fmiVersion="2.0"
					  modelName="mock"
					  guid=""
					  numberOfEventIndicators="0">
					  <CoSimulation
					    modelIdentifier="mock"/>
					  <ModelVariables>
					    <ScalarVariable name="out" valueReference="0"
					     description=""
					     causality="output"
					     variability="continuous"
					  initial="exact">
					     <Real start="0"/>
					    </ScalarVariable>
					    <ScalarVariable name="in" valueReference="1"
					     description=""
					     causality="input"
					     variability="continuous"
					  initial="exact">
					     <Real start="0"/>
					    </ScalarVariable>
					  </ModelVariables>
					  <ModelStructure>
					      <Derivatives>
					        <Unknown index="1" />
					      </Derivatives>
					      <InitialUnknowns>
					        <Unknown index="1"/>
					      </InitialUnknowns>
					    </ModelStructure>
					</fmiModelDescription>
				''', "UTF-8")
			}
			override getTypesPlatform() throws FmiInvalidNativeStateException {}
			override getVersion() throws FmiInvalidNativeStateException {}
			override instantiate(String guid, String name, boolean visible, boolean loggingOn,
				IFmuCallback callback) throws XPathExpressionException, FmiInvalidNativeStateException {}
			override isValid() {true}
			override load() throws FmuInvocationException, FmuMissingLibraryException {}
			override unLoad() throws FmiInvalidNativeStateException {}
		}
	}
}

