package org.mitre.synthea.helpers;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.List;

import org.junit.Test;
import org.mitre.synthea.helpers.physiology.IoMapper;
import org.mitre.synthea.helpers.physiology.PhysiologyGeneratorConfig;
import org.mitre.synthea.world.agents.Person;
import org.mitre.synthea.world.concepts.VitalSign;

public class PhysiologyValueGeneratorTest {
  
  private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  
  long dateToSimTime(String dateStr) {
    try {
      return dateFormat.parse(dateStr).getTime();
    } catch (ParseException e) {
      throw new RuntimeException(e);
    }
  }
  
  @Test
  public void testValueGenerator() {
    
    Person person = new Person(0);
    
    // Person born on July 25, 1988
    person.attributes.put(Person.BIRTHDATE, dateToSimTime("1988-07-25"));
    
    // Set their BMI to a normal 22 kg/m^2
    person.setVitalSign(VitalSign.BMI, 22.0);
    
    PhysiologyGeneratorConfig config = PhysiologyValueGenerator.getConfig(
        "circulation_hemodynamics.yml");
    
    // Don't use pre generators so the model will run initially
    config.setUsePreGenerators(false);
    
    // Set the input variance threshold for R_sys to 0.1 so a small change will not
    // cause a re-run of the simulation
    for (IoMapper mapper : config.getInputs()) {
      if (mapper.getTo() == "R_sys") {
        mapper.setVariance(0.1);
      }
    }
    
    // Set our current simulation time
    long simTime = dateToSimTime("2019-09-19");
    
    // Get the generator for systolic BP
    PhysiologyValueGenerator generator = new PhysiologyValueGenerator(config,
        VitalSign.SYSTOLIC_BLOOD_PRESSURE, person, 0.0);
    
    double sys1 = generator.getValue(simTime);
    
    // Make sure it's a reasonable value
    assertTrue("sys1 is a reasonable systolic bp value", (100 < sys1) && (sys1 < 160));
    
    // Change the time by just a few seconds. The simulation should not run again,
    // which means the output value will be exactly the same.
    
    simTime += 30000;
    double sys2 = generator.getValue(simTime);
    assertEquals(sys1, sys2, 0);
    
    // Change the time by several years. Now the simulation should definitely run
    // again, which will change the output value.
    simTime = dateToSimTime("2070-09-19");
    double sys3 = generator.getValue(simTime);
    
    // Make sure it's a reasonable value
    assertTrue("sys3 is a reasonable systolic bp value", (100 < sys1) && (sys1 < 160));
    
    assertNotEquals(sys1, sys3);
    
    // Change the output variance to a small amount and verify that the result is no longer
    // exactly the same but within the variance amount.
    generator.setOutputVariance(1.0);
    double sys4 = generator.getValue(simTime);
    assertEquals(sys3, sys4, 1.0);
    assertNotEquals(sys3, sys4);
    
  }
  
  @Test
  public void testMultiLoad() {
    // Verify that we can load all ValueGenerators defined in a configuration file
    
    Person person = new Person(0);
    person.attributes.put(Person.BIRTHDATE, dateToSimTime("1988-07-25"));
    person.setVitalSign(VitalSign.BMI, 22.0);
    
    PhysiologyGeneratorConfig config = PhysiologyValueGenerator.getConfig(
        "circulation_hemodynamics.yml");
    
    List<PhysiologyValueGenerator> generators = PhysiologyValueGenerator.fromConfig(config, person);
    
    // Create a Set with each VitalSign in the config
    HashSet<VitalSign> cfgVitals = new HashSet<VitalSign>();
    for (IoMapper mapper : config.getOutputs()) {
      if (mapper.getType() == IoMapper.IoType.VITAL_SIGN) {
        cfgVitals.add(VitalSign.fromString(mapper.getTo()));
      }
    }
    
    // Create a Set with each VitalSign generator
    HashSet<VitalSign> genVitals = new HashSet<VitalSign>();
    for (PhysiologyValueGenerator generator : generators) {
      genVitals.add(generator.getVitalSign());
    }
    
    assertEquals(cfgVitals, genVitals);
  }
  
  @Test
  public void testLoadAll() {
    // Verify that we can load all ValueGenerators defined in a configuration file
    
    Person person = new Person(0);
    
    List<PhysiologyValueGenerator> generators = PhysiologyValueGenerator.loadAll(person);
    
    assertTrue("At least one generator loaded", generators.size() > 0);
  }
  
  @Test
  public void testToString() {
    Person person = new Person(0);
    person.attributes.put(Person.BIRTHDATE, dateToSimTime("1988-07-25"));
    person.setVitalSign(VitalSign.BMI, 22.0);
    
    PhysiologyGeneratorConfig config = PhysiologyValueGenerator.getConfig(
        "circulation_hemodynamics.yml");
    
    // Get the generator for systolic BP
    PhysiologyValueGenerator generator = new PhysiologyValueGenerator(config,
        VitalSign.SYSTOLIC_BLOOD_PRESSURE, person, 0.0);
    
    assertEquals("PhysiologyValueGenerator {model=circulation/Smith2004_CVS_human.xml, "
        + "VitalSigns=[LVEF, SYSTOLIC_BLOOD_PRESSURE, DIASTOLIC_BLOOD_PRESSURE], "
        + "Attributes=[Arterial Pressure Values]}", generator.toString());
  }
}
@@ -432,16 +432,71 @@
 Config;%0A
+import org.mitre.synthea.helpers.physiology.SimRunner;%0A
 import o
@@ -1782,32 +1782,91 @@
 9-09-19%22);%0A    %0A
+    SimRunner runner = new SimRunner(config, person);%0A    %0A
     // Get the g
@@ -1959,32 +1959,40 @@
 enerator(config,
+ runner,
 %0A        VitalSi
@@ -4941,32 +4941,91 @@
 ics.yml%22);%0A    %0A
+    SimRunner runner = new SimRunner(config, person);%0A    %0A
     // Get the g
@@ -5126,16 +5126,24 @@
 (config,
+ runner,
 %0A       

update tests