package ua.ansymo.hintco.test

import java.io.File
import java.util.Map
import org.apache.commons.io.FileUtils
import org.apache.commons.lang3.SystemUtils
import org.intocps.fmi.Fmi2Status
import org.intocps.fmi.IFmuCallback
import org.intocps.fmi.jnifmuapi.Factory
import org.intocps.orchestration.coe.modeldefinition.ModelDescription
import org.junit.Assume
import org.junit.Ignore
import org.junit.Test
import ua.ansymo.hintco.CandidatesGenerator
import ua.ansymo.hintco.ConstraintChecker
import ua.ansymo.hintco.FmuLoader
import ua.ansymo.hintco.IOutputProcessor
import ua.ansymo.hintco.ModelStorage
import ua.ansymo.hintco.OutputPortInstance
import ua.ansymo.hintco.OutputProcessor
import ua.ansymo.hintco.RootCandidateScenario
import ua.ansymo.hintco.SingleCosimRunner
import ua.ansymo.hintco.VariantProcessor
import ua.ansymo.hintco.VariantValidator

import static org.junit.Assert.*
import static ua.ansymo.hintco.ModelQuery.*

class CosimRunnerTest {
	@Test
	def void loadAndRunFMUTest(){
		val fmu = Factory.create(new File("resources/watertank.fmu"))
		assertNotNull(fmu)
		fmu.load()
		val fmuInstance = fmu.instantiate("{8c4e810f-3df3-4a00-8276-176fa3c9f001}", "watertank", true, true, new IFmuCallback()
		{
			override log(String instanceName, Fmi2Status status, String category, String message) {
				println("log: " + instanceName + " " + status + " "
						+ category + " " + message);
			}
			
			override stepFinished(Fmi2Status status) {
				
			}
		})
		
		val valve = #[4l]
		val level = #[2l]
		
		fmuInstance.setupExperiment(false,0.0,0.0,false,0.0)
		fmuInstance.enterInitializationMode()
		fmuInstance.setBooleans(valve, #[false])
		fmuInstance.exitInitializationMode()
		
		var l = fmuInstance.getReal(level)
		assertTrue(l.status == Fmi2Status.OK)
		assertTrue(l.result.head > 0.9 && l.result.head < 1.1)
		
		fmuInstance.setBooleans(valve, #[false])
		fmuInstance.doStep(0.0, 1.0, true)
		
		l = fmuInstance.getReal(level)
		assertTrue(l.status == Fmi2Status.OK)
		assertTrue(l.result.head > 1.5)
		
		fmuInstance.freeInstance()
		fmu.unLoad()
	}
	
	
	@Test
	@Ignore // Just to show the memory leak in the into-cps fmi library.
	def void loadAndRunFMUTestLong(){
		val fmu = Factory.create(new File("resources/watertank.fmu"))
		assertNotNull(fmu)
		fmu.load()
		val fmuInstance = fmu.instantiate("{8c4e810f-3df3-4a00-8276-176fa3c9f001}", "watertank", true, false, new IFmuCallback()
		{
			override log(String instanceName, Fmi2Status status, String category, String message) {
			}
			
			override stepFinished(Fmi2Status status) {
			}
		})
		
		val valve = #[4l]
		val level = #[2l]
		val falseVal = #[false]
		val trueVal = #[true]
		
		fmuInstance.setupExperiment(false,0.0,0.0,false,0.0)
		fmuInstance.enterInitializationMode()
		fmuInstance.setBooleans(valve, falseVal)
		fmuInstance.exitInitializationMode()
		
		for (var i = 0L; i<100000000000L; i++) {
			fmuInstance.setBooleans(valve, trueVal)
			fmuInstance.doStep(0.0, 1.0, true)
			var l = fmuInstance.getReal(level)
			assertTrue(l.result.head*l.result.head >= 0.0)
		}		
		
		fmuInstance.freeInstance()
		fmu.unLoad()
	}
	
	@Test
	def void setGetVariableByNameTest(){
		val fmu = Factory.create(new File("resources/watertank.fmu"))
		assertNotNull(fmu)
		fmu.load()
		val fmuInstance = fmu.instantiate("{8c4e810f-3df3-4a00-8276-176fa3c9f001}", "watertank", true, false, new IFmuCallback()
		{
			override log(String instanceName, Fmi2Status status, String category, String message) {
				println("log: " + instanceName + " " + status + " "
						+ category + " " + message);
			}
			
			override stepFinished(Fmi2Status status) {
				
			}
		})
		
		val modDesc = new ModelDescription(fmuInstance.fmu.modelDescription)
		
		fmuInstance.setupExperiment(false,0.0,0.0,false,0.0)
		fmuInstance.enterInitializationMode()
		
		val in = modDesc.scalarVariables.findFirst[v | v.causality === ModelDescription.Causality.Input]
		fmuInstance.setBooleans(#[in.valueReference], #[false])

		fmuInstance.exitInitializationMode()
		
		val out = modDesc.scalarVariables.findFirst[v | v.causality === ModelDescription.Causality.Output]
		val res = fmuInstance.getReal(#[out.valueReference])
		
		assertTrue(res.status == Fmi2Status.OK)
		assertTrue(res.result.head > 0.9 && res.result.head < 1.1)
		
		fmuInstance.freeInstance()
		fmu.unLoad()
	}
	
	@Test
	def void executeCosimulationWaterTankTest(){
		//val resultsDirPath = "results-gen/cosimTest"
		val loader = new ModelStorage()
		val src = loader.loadCandidates("instances/execute_cosim_test_watertanks.xmi")
		
		val Tank2WaterLevel = findIded("Tank2WaterLevel",src)
		
		val runner = new SingleCosimRunner(new IOutputProcessor{
			override initialize(RootCandidateScenario scenario, String variantID) {}
			override terminate() {}
			override setOutputs(int step, double time, Map<OutputPortInstance, Double> outVals) {
				if (time > 4.0){
					assertTrue(outVals.get(Tank2WaterLevel) > 0.5)
				}
			}
		}, new FmuLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.createVariantTree(src)
		generator.generateVariants(src, 1, false)
		runner.close()
	}
	
	
	@Test
	def void executeSimpleHierCosimulationWaterTankTest(){
//		val resultsDirPath = "results-gen/cosimHierarTest"
		val loader = new ModelStorage()
		val src = loader.loadCandidates("instances/execute_hier_cosim_test_watertanks.xmi")
		
		val envOut = findIded("envOut",src)
		
		val runner = new SingleCosimRunner(new IOutputProcessor{
			override initialize(RootCandidateScenario scenario, String variantID) {}
			override terminate() {}
			override setOutputs(int step, double time, Map<OutputPortInstance, Double> outVals) {
				if (time > 4.0){
					assertTrue(outVals.get(envOut) > 0.05)
				}
			}
		}, new FmuLoader)
//		val runner = new CosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.createVariantTree(src)
		generator.generateVariants(src, 1, false)
		runner.close()
	}
	
	
	@Test
	def void executeDeepHierCosimulationWaterTankTest(){
//		val resultsDirPath = "results-gen/cosimHierarTest"
		val loader = new ModelStorage()
		val src = loader.loadCandidates("instances/execute_deephier_cosim_test_watertanks.xmi")
		
		val envOut = findIded("envOut",src)
		
		val runner = new SingleCosimRunner(new IOutputProcessor{
			override initialize(RootCandidateScenario scenario, String variantID) {}
			override terminate() {}
			override setOutputs(int step, double time, Map<OutputPortInstance, Double> outVals) {
				if (time > 4.0){
					assertTrue(outVals.get(envOut) > 0.05)
				}
			}
		}, new FmuLoader)
//		val runner = new CosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.createVariantTree(src)
		generator.generateVariants(src, 1, false)
		runner.close()
	}
	
	@Test
	def void executeCosimulationTestDoubleLoopApprox(){
		Assume.assumeTrue(SystemUtils.IS_OS_WINDOWS) // Because it only works on windows.
//		System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug")
//		System.setProperty("org.slf4j.simpleLogger.logFile", "dloopbetter.log")
		val resultsDirPath = "results-gen/cosimTestDLoopApprox"
		val loader = new ModelStorage()
//		val runner = new CosimRunner(new IOutputProcessor{
//			override initialize(RootCandidateScenario scenario, String variantID) {}
//			override terminate() {}
//			override setOutputs(int step, double time, Map<OutputPortInstance, Double> outVals) {
//				if (time > 1.5){
//					assertTrue(true)
//				}
//			}
//		}, new FmuLoader)
		val runner = new SingleCosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		val src = loader.loadCandidates("instances/case_study_double_loop_approx.xmi")
		generator.createVariantTree(src)
		val numVariants = generator.generateVariants(src, 1, false)
		runner.close()
		assertEquals(1, numVariants)
	}
	
	@Test
	@Ignore // Power Bond adaptations are no longer support. Need to make them consistent with waveform relaxation techniques.
	def void executeCosimulationTestDoubleLoopPowerBond(){
		Assume.assumeTrue(SystemUtils.IS_OS_WINDOWS) // Because it only works on windows.
//		System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug")
//		System.setProperty("org.slf4j.simpleLogger.logFile", "powerbond.log")
		val resultsDirPath = "results-gen/cosimTestDLoopPBond"
		val loader = new ModelStorage()
		val src = loader.loadCandidates("instances/case_study_double_loop_pbond.hintco")
		val runner = new SingleCosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.processAdaptations(src)
		generator.createVariantTree(src)
		val numVariants = generator.generateVariants(src, 1, false)
		runner.close()
		assertEquals(1, numVariants)
	}
	
	@Test
	@Ignore // Power Bond adaptations are no longer support. Need to make them consistent with waveform relaxation techniques.
	def void executeCosimulationTestDoubleLoopPowerBondHier(){
		Assume.assumeTrue(SystemUtils.IS_OS_WINDOWS) // Because it only works on windows.
//		System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug")
//		System.setProperty("org.slf4j.simpleLogger.logFile", "cosimTestDLoopPBondHier.log")
		val resultsDirPath = "results-gen/cosimTestDLoopPBondHier"
		val loader = new ModelStorage()
		val src = loader.loadCandidates("instances/case_study_double_loop_pbond_hier.hintco")
		val runner = new SingleCosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.processAdaptations(src)
		generator.createVariantTree(src)
		val numVariants = generator.generateVariants(src, 1, false)
		runner.close()
		assertEquals(1, numVariants)
	}
	
	@Test
	def void executeCosimulationTestDoubleLoopDefault(){
		Assume.assumeTrue(SystemUtils.IS_OS_WINDOWS) // Because it only works on windows.
//		System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug")
//		System.setProperty("org.slf4j.simpleLogger.logFile", "powerbond.log")
		val resultsDirPath = "results-gen/cosimTestDLoopDefault"
		val loader = new ModelStorage()
		val src = loader.loadCandidates("instances/case_study_double_loop_default.hintco")
		val runner = new SingleCosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		generator.processAdaptations(src)
		generator.createVariantTree(src)
		val numVariants = generator.generateVariants(src, 1, false)
		runner.close()
		assertEquals(1, numVariants)
	}
	
	@Test
	def void executeDuplicateUnitTest(){
		val resultsDirPath = "results-gen/dupUnitTest"
		FileUtils.deleteDirectory(new File(resultsDirPath))
		val loader = new ModelStorage()
		val runner = new SingleCosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
		val src = loader.loadCandidates("instances/duplicate_unit_test.xmi")
		generator.createVariantTree(src)
		generator.generateVariants(src, 1, false)
		runner.close()
		val resultsWt1 = new File(resultsDirPath + "/0_1/wt1.csv")
		val resultsWt2 = new File(resultsDirPath + "/0_1/wt2.csv")
		assertTrue(resultsWt1.exists)
		assertTrue(resultsWt2.exists)
	}
	
}