use demes::{AsymmetricMigration, MigrationRate, SizeFunction, Time};

#[derive(Eq, PartialEq)]
struct ExpectedMigration {
    source: String,
    dest: String,
    rate: MigrationRate,
    start_time: Time,
    end_time: Time,
}

impl ExpectedMigration {
    fn new<
        N: ToString,
        M: TryInto<MigrationRate, Error = demes::DemesError>,
        S: TryInto<Time, Error = demes::DemesError>,
        E: TryInto<Time, Error = demes::DemesError>,
    >(
        source: N,
        dest: N,
        rate: M,
        start_time: S,
        end_time: E,
    ) -> Result<Self, demes::DemesError> {
        let rate = rate.try_into()?;
        let start_time = start_time.try_into()?;
        let end_time = end_time.try_into()?;
        Ok(Self {
            source: source.to_string(),
            dest: dest.to_string(),
            rate,
            start_time,
            end_time,
        })
    }
}

impl From<AsymmetricMigration> for ExpectedMigration {
    fn from(value: AsymmetricMigration) -> Self {
        Self {
            source: value.source().to_string(),
            dest: value.dest().to_string(),
            rate: value.rate(),
            start_time: value.start_time(),
            end_time: value.end_time(),
        }
    }
}

fn test_graph_equality_after_round_trip(
    graph: &demes::Graph,
) -> Result<bool, Box<dyn std::error::Error>> {
    let yaml = serde_yaml::to_string(graph)?;
    let round_trip = demes::loads(&yaml)?;
    Ok(*graph == round_trip)
}

macro_rules! assert_graph_equality_after_round_trip {
    ($graph: ident) => {
        match test_graph_equality_after_round_trip(&$graph) {
            Ok(b) => assert!(b),
            Err(e) => panic!("{}", e.to_string()),
        }
    };
}

#[test]
fn test_tutorial_example_01() {
    let yaml = "
# Comments start with a hash.
description:
  Asymmetric migration between two extant demes.
time_units: generations
defaults:
  epoch:
    start_size: 5000
demes:
  - name: X
    epochs:
      - end_time: 1000
  - name: A
    ancestors: [X]
  - name: B
    ancestors: [X]
    epochs:
      - start_size: 2000
        end_time: 500
      - start_size: 400
        end_size: 10000
migrations:
  - source: A
    dest: B
    rate: 1e-4
";
    let g = demes::loads(yaml).unwrap();
    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn tutorial_example_03() {
    let yaml = "
time_units: generations
demes:
  - name: A
    epochs:
      - start_size: 1000
";
    let g = demes::loads(yaml).unwrap();
    assert_eq!(g.num_demes(), 1);
    assert_eq!(
        f64::from(g.get_deme("A").unwrap().start_time()),
        f64::INFINITY,
    );
    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn replacement_with_size_change() {
    let yaml = "
time_units: generations
demes:
  - name: A
    epochs:
      - start_size: 1000
        end_time: 1000
  - name: B
    ancestors: [A]
    epochs:
      - start_size: 2000
";
    let g = demes::loads(yaml).unwrap();
    assert_eq!(g.num_demes(), 2);
    assert_eq!(g.demes().len(), 2);

    for d in g.demes() {
        if d.name() == "A" {
            assert_eq!(d.num_ancestors(), 0);
        } else {
            assert_eq!(d.num_ancestors(), 1);

            // iterate over ancestor HashMap of {ancestor name => ancestor Deme index}
            for (name, index) in d.ancestor_names().iter().zip(d.ancestor_indexes().iter()) {
                let deme = g.deme(*index);
                assert_eq!(name, "A");
                assert_eq!(*deme.name(), *name);
                assert_eq!(deme.num_ancestors(), 0);
            }

            // Iterate over just the names
            assert!(d.ancestor_names().iter().all(|ancestor| *ancestor == "A"));

            // With only 1 ancestor, there is exactly 1 proportion
            // represeting 100% ancestry
            assert_eq!(d.proportions().len(), 1);
            assert_eq!(f64::from(d.proportions()[0]), 1.0);
        }
    }
    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn default_epoch_sizes() {
    let yaml = "
time_units: generations
defaults:
  epoch:
    start_size: 1000
demes:
  - name: A
";
    let g = demes::loads(yaml).unwrap();
    assert_eq!(g.num_demes(), 1);

    // .deme(at) returns a strong reference,
    // thus increasing the reference count
    assert_eq!(g.deme(0).name(), "A");
    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn tutorial_example_21() {
    let yaml = "
description: The Gutenkunst et al. (2009) OOA model.
doi:
- https://doi.org/10.1371/journal.pgen.1000695
time_units: years
generation_time: 25

demes:
- name: ancestral
  description: Equilibrium/root population
  epochs:
  - {end_time: 220e3, start_size: 7300}
- name: AMH
  description: Anatomically modern humans
  ancestors: [ancestral]
  epochs:
  - {end_time: 140e3, start_size: 12300}
- name: OOA
  description: Bottleneck out-of-Africa population
  ancestors: [AMH]
  epochs:
  - {end_time: 21.2e3, start_size: 2100}
- name: YRI
  description: Yoruba in Ibadan, Nigeria
  ancestors: [AMH]
  epochs:
  - start_size: 12300
- name: CEU
  description: Utah Residents (CEPH) with Northern and Western European Ancestry
  ancestors: [OOA]
  epochs:
  - {start_size: 1000, end_size: 29725}
- name: CHB
  description: Han Chinese in Beijing, China
  ancestors: [OOA]
  epochs:
  - {start_size: 510, end_size: 54090}

migrations:
- {demes: [YRI, OOA], rate: 25e-5}
- {demes: [YRI, CEU], rate: 3e-5}
- {demes: [YRI, CHB], rate: 1.9e-5}
- {demes: [CEU, CHB], rate: 9.6e-5}
";

    let g = demes::loads(yaml).unwrap();
    let generation_time: f64 = g.generation_time().into();
    assert_eq!(generation_time, 25.0);
    assert!(matches!(g.time_units(), demes::TimeUnits::Years,));
    assert_eq!(g.time_units().to_string(), "years".to_string());
    assert_eq!(g.migrations().len(), 8);

    let expected_resolved_migrations = vec![
        ExpectedMigration::new("YRI", "OOA", 25e-5, 140e3, 21.2e3).unwrap(),
        ExpectedMigration::new("OOA", "YRI", 25e-5, 140e3, 21.2e3).unwrap(),
        ExpectedMigration::new("YRI", "CEU", 3e-5, 21.2e3, 0.0).unwrap(),
        ExpectedMigration::new("CEU", "YRI", 3e-5, 21.2e3, 0.0).unwrap(),
        ExpectedMigration::new("YRI", "CHB", 1.9e-5, 21.2e3, 0.0).unwrap(),
        ExpectedMigration::new("CHB", "YRI", 1.9e-5, 21.2e3, 0.0).unwrap(),
        ExpectedMigration::new("CEU", "CHB", 9.6e-5, 21.2e3, 0.0).unwrap(),
        ExpectedMigration::new("CHB", "CEU", 9.6e-5, 21.2e3, 0.0).unwrap(),
    ];

    assert!(g
        .migrations()
        .iter()
        .all(|m| expected_resolved_migrations.contains(&ExpectedMigration::from(m.clone()))));

    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn test_size_propagation() {
    let yaml = "
time_units: generations
demes:
  - name: A
    epochs:
      - start_size: 1000
        end_time: 1000
  - name: B
    epochs:
      - start_size: 2000
  - name: C
    ancestors: [A, B]
    proportions: [0.5, 0.5]
    start_time: 1000
    epochs:
      - start_size: 503
";
    let g = demes::loads(yaml).unwrap();
    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn test_start_time_handling() {
    let yaml = "
time_units: generations
demes:
  - name: A
    epochs:
      - start_size: 1000
  - name: B
    ancestors: [A]
    start_time: 100
    epochs:
      - start_size: 1000
";
    let g = demes::loads(yaml).unwrap();
    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn tutorial_example_17() {
    let yaml = "
time_units: generations
demes:
  - name: X
    epochs:
      - end_time: 1000
        start_size: 2000
  - name: A
    ancestors: [X]
    epochs:
      - start_size: 2000
  - name: B
    ancestors: [X]
    epochs:
      - start_size: 2000
pulses:
  - sources: [A]
    dest: B
    proportions: [0.05]
    time: 500
";
    let g = demes::loads(yaml).unwrap();
    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn jacobs_el_al_2019_from_gallery() {
    let yaml = "
description: |
  A ten population model of out-of-Africa, including two pulses of
  Denisovan admixture into Papuans, and several pulses of Neandertal
  admixture into non-Africans.
  Most parameters are from Jacobs et al. (2019), Table S5 and Figure S5.
  This model is an extension of one from Malaspinas et al. (2016), thus
  some parameters are inherited from there.
time_units: generations
doi:
- https://doi.org/10.1016/j.cell.2019.02.035
- https://doi.org/10.1038/nature18299

demes:
- name: YRI
  epochs:
  - {end_time: 20225.0, start_size: 32671.0}
  - {end_time: 2218.0, start_size: 41563.0}
  - {end_time: 0, start_size: 48433.0}
- name: DenA
  ancestors: [YRI]
  start_time: 20225.0
  epochs:
  - {end_time: 15090.0, start_size: 13249.0}
  - {end_time: 12500.0, start_size: 100.0}
  - {end_time: 9750.0, start_size: 100.0}
  - {end_time: 0, start_size: 5083.0}
- name: NeaA
  ancestors: [DenA]
  start_time: 15090.0
  epochs:
  - {end_time: 3375.0, start_size: 13249.0}
  - {end_time: 0, start_size: 826.0}
- name: Den2
  ancestors: [DenA]
  start_time: 12500.0
  epochs:
  - start_size: 13249.0
- name: Den1
  ancestors: [DenA]
  start_time: 9750.0
  epochs:
  - start_size: 13249.0
- name: Nea1
  ancestors: [NeaA]
  start_time: 3375.0
  epochs:
  - start_size: 13249.0
- name: Ghost
  ancestors: [YRI]
  start_time: 2218.0
  epochs:
  - {end_time: 2119.0, start_size: 1394.0}
  - {end_time: 0, start_size: 8516.0}
- name: Papuan
  ancestors: [Ghost]
  start_time: 1784.0
  epochs:
  - {end_time: 1685.0, start_size: 243.0}
  - {end_time: 0, start_size: 8834.0}
- name: CHB
  ancestors: [Ghost]
  start_time: 1758.0
  epochs:
  - {end_time: 1659.0, start_size: 2231.0}
  - {end_time: 1293.0, start_size: 12971.0}
  - {end_time: 0, start_size: 9025.0}
- name: CEU
  ancestors: [CHB]
  start_time: 1293.0
  epochs:
  - start_size: 6962.0

migrations:
- {demes: [YRI, Ghost], rate: 0.000179, start_time: 1659.0}
- {demes: [CHB, Papuan], rate: 0.000572, start_time: 1659.0, end_time: 1293.0}
- {demes: [CHB, Papuan], rate: 5.72e-05, start_time: 1293.0}
- {demes: [CHB, Ghost], rate: 0.000442, start_time: 1659.0, end_time: 1293.0}
- {demes: [CEU, CHB], rate: 3.14e-05}
- {demes: [CEU, Ghost], rate: 0.000442}

pulses:
- {sources: [Nea1], dest: Ghost, time: 1853.0, proportions: [0.024]}
- {sources: [Den2], dest: Papuan, time: 1575.8620689655172, proportions: [0.018]}
- {sources: [Nea1], dest: CHB, time: 1566.0, proportions: [0.011]}
- {sources: [Nea1], dest: Papuan, time: 1412.0, proportions: [0.002]}
- {sources: [Den1], dest: Papuan, time: 1027.5862068965516, proportions: [0.022]}
- {sources: [Nea1], dest: CHB, time: 883.0, proportions: [0.002]}
";

    let g = demes::loads(yaml).unwrap();
    assert_graph_equality_after_round_trip!(g);
}

#[test]
fn test_pulses_are_stably_sorted() {
    let yaml1 = "
time_units: generations
demes:
  - name: X
    epochs:
      - end_time: 1000
        start_size: 2000
  - name: A
    ancestors: [X]
    epochs:
      - start_size: 2000
  - name: B
    ancestors: [X]
    epochs:
      - start_size: 2000
pulses:
  - sources: [A]
    dest: B
    proportions: [0.05]
    time: 500
  - sources: [A]
    dest: B
    proportions: [0.05]
    time: 501
  - sources: [B]
    dest: A
    proportions: [0.05]
    time: 501
";

    let yaml2 = "
time_units: generations
description: the pulses at 501 are in a different order
demes:
  - name: X
    epochs:
      - end_time: 1000
        start_size: 2000
  - name: A
    ancestors: [X]
    epochs:
      - start_size: 2000
  - name: B
    ancestors: [X]
    epochs:
      - start_size: 2000
pulses:
  - sources: [A]
    dest: B
    proportions: [0.05]
    time: 500
  - sources: [B]
    dest: A
    proportions: [0.05]
    time: 501
  - sources: [A]
    dest: B
    proportions: [0.05]
    time: 501
";

    let g = demes::loads(yaml1).unwrap();
    let expected_pulse_times = vec![
        demes::Time::try_from(501.).unwrap(),
        demes::Time::try_from(501.).unwrap(),
        demes::Time::try_from(500.).unwrap(),
    ];
    let pulse_times = g
        .pulses()
        .iter()
        .map(|pulse| pulse.time())
        .collect::<Vec<Time>>();
    assert_eq!(pulse_times, expected_pulse_times);
    assert_eq!(g.pulses()[0].sources(), &["A".to_string()]);
    assert_eq!(g.pulses()[0].dest(), "B");
    assert_eq!(g.pulses()[1].sources(), &["B".to_string()]);
    assert_eq!(g.pulses()[1].dest(), "A");
    assert_graph_equality_after_round_trip!(g);

    let g2 = demes::loads(yaml2).unwrap();
    let pulse_times = g
        .pulses()
        .iter()
        .map(|pulse| pulse.time())
        .collect::<Vec<Time>>();
    assert_eq!(pulse_times, expected_pulse_times);
    assert_eq!(g2.pulses()[0].sources(), &["B".to_string()]);
    assert_eq!(g2.pulses()[0].dest(), "A");
    assert_eq!(g2.pulses()[1].sources(), &["A".to_string()]);
    assert_eq!(g2.pulses()[1].dest(), "B");

    // The two graphs are not equal b/c the pulses
    // are sorted stable w.r.to time.
    assert_ne!(g, g2);
}

#[test]
fn linear_size_function_default() {
    let yaml = "
time_units: generations
defaults:
  epoch:
    start_size: 5000
    size_function: linear
demes:
  - name: X
    epochs:
      - end_time: 1000
      - end_size: 100

";
    let graph = demes::loads(yaml).unwrap();
    let sf = graph.deme(0).epochs()[0].size_function();
    assert!(matches!(sf, demes::SizeFunction::Constant));
    let sf = graph.deme(0).epochs()[1].size_function();
    assert!(matches!(sf, demes::SizeFunction::Linear));
}

#[test]
fn selfing_rate_default() {
    let yaml = "
time_units: generations
defaults:
  epoch:
    selfing_rate: 0.25
demes:
  - name: X
    epochs:
     - start_size: 5000
";
    let graph = demes::loads(yaml).unwrap();
    let selfing_rate = graph.deme(0).epochs()[0].selfing_rate();
    assert_eq!(0.25, f64::from(selfing_rate));
}

#[test]
fn cloning_rate_default() {
    let yaml = "
time_units: generations
defaults:
  epoch:
    cloning_rate: 0.25
demes:
  - name: X
    epochs:
     - start_size: 5000
";
    let graph = demes::loads(yaml).unwrap();
    let cloning_rate = graph.deme(0).epochs()[0].cloning_rate();
    assert_eq!(0.25, f64::from(cloning_rate));
}

#[test]
fn end_time_default() {
    let yaml = "
time_units: generations
defaults:
  epoch:
    end_time: 100
demes:
  - name: X
    epochs:
     - start_size: 5000
";
    let graph = demes::loads(yaml).unwrap();
    let end_time = graph.deme(0).epochs()[0].end_time();
    assert_eq!(100.0, f64::from(end_time));
    assert_eq!(graph.most_recent_deme_end_time(), 100.0);
}

// from demes-spec/test-cases/valid
#[test]
fn defaults_deme_many_epochs_local() {
    let yaml = "
description: Set epoch defaults using deme-local values.
time_units: generations

demes:
- name: deme0
  defaults:
    epoch: {cloning_rate: 0.5, end_size: 2, selfing_rate: 0.1, start_size: 1}
  epochs:
  - {end_time: 100, start_size: 1, end_size: 1}
  - {end_time: 3}
  - {end_time: 2}
  - {end_time: 1}
  - {end_time: 0}
";
    let g = demes::loads(yaml).unwrap();

    for deme in g.demes().iter() {
        assert_eq!(f64::from(deme.start_size()), 1.0);
    }

    for deme in g.demes().iter() {
        for epoch in deme.epochs().iter() {
            assert_eq!(f64::from(epoch.cloning_rate()), 0.5);
            assert_eq!(f64::from(epoch.selfing_rate()), 0.1);
        }
    }
    let expected_start_sizes = vec![1.; 5];
    let start_sizes = g.deme(0).start_sizes().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(start_sizes, expected_start_sizes);

    let expected_end_sizes = vec![1.0, 2.0, 2.0, 2.0, 2.0];
    let end_sizes = g.deme(0).end_sizes();
    let end_sizes = end_sizes.map(f64::from).collect::<Vec<f64>>();
    assert_eq!(end_sizes, expected_end_sizes);
    assert_eq!(g.deme(0).end_size(), 2.0);
    let expected_start_times = vec![f64::INFINITY, 100., 3., 2., 1.];
    let start_times = g.deme(0).start_times().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(start_times, expected_start_times);
    for (i, epoch) in g.deme(0).epochs().iter().enumerate() {
        assert_eq!(epoch.start_time(), start_times[i]);
    }
    let expected_end_times = vec![100., 3., 2., 1., 0.];
    let end_times = g.deme(0).end_times();
    let end_times = end_times.map(f64::from).collect::<Vec<f64>>();
    assert_eq!(end_times, expected_end_times);
}

#[test]
fn defaults_deme_many_epochs_local_with_size_function_default() {
    let yaml = "
description: Modified from above to include default size_function
time_units: generations

demes:
- name: deme0
  defaults:
    epoch: {cloning_rate: 0.5, end_size: 2, selfing_rate: 0.1, start_size: 1, size_function: linear}
  epochs:
  - {end_time: 100, start_size: 1, end_size: 1}
  - {end_time: 3, size_function: exponential}
  - {end_time: 2}
  - {end_time: 1}
  - {end_time: 0}
";
    let g = demes::loads(yaml).unwrap();
    let size_functions = g
        .deme(0)
        .epochs()
        .iter()
        .map(|epoch| epoch.size_function())
        .collect::<Vec<SizeFunction>>();
    let expected_size_functions = vec![
        SizeFunction::Constant,
        SizeFunction::Exponential,
        SizeFunction::Linear,
        SizeFunction::Linear,
        SizeFunction::Linear,
    ];
    assert_eq!(size_functions, expected_size_functions);
}

// copied from demes-spec repo
#[test]
fn demelevel_defaults_epoch_03() {
    let yaml = "
time_units: generations
demes:
- name: a
  epochs:
  - {start_size: 1}
  defaults:
    epoch: {end_time: 10}
- name: b
  epochs:
  - {start_size: 1, end_time: 90}
  - {start_size: 2, end_time: 50}
  - {start_size: 3}
  defaults:
    epoch: {end_time: 10}
";
    let g = demes::loads(yaml).unwrap();
    let deme = g.deme(0);
    assert_eq!(f64::from(deme.end_time()), 10.0);
    assert_eq!(f64::from(deme.start_time()), f64::INFINITY);
    let deme = g.deme(1);
    assert_eq!(f64::from(deme.end_time()), 10.0);
    let end_times = deme.end_times().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(end_times, vec![90., 50., 10.]);
}

// copied from demes-spec repo
#[test]
fn infinity_03() {
    let yaml = "
time_units: generations
defaults:
  deme: {start_time: .inf}
demes:
  - name: A
    epochs:
      - start_size: 100
";
    let g = demes::loads(yaml).unwrap();
    let deme = g.deme(0);
    assert_eq!(f64::from(deme.start_time()), f64::INFINITY);
    assert_eq!(f64::from(deme.start_size()), 100.0);
    assert_eq!(f64::from(deme.end_size()), 100.0);
    assert_eq!(f64::from(deme.end_time()), 0.0);
}

// copied from demes-spec repo
#[test]
fn toplevel_defaults_deme_01() {
    let yaml = "
time_units: generations
defaults:
  deme:
    ancestors: [a, b, c]
    proportions: [0.1, 0.7, 0.2]
demes:
- name: a
  ancestors: []
  proportions: []
  epochs:
  - {start_size: 1}
- name: b
  ancestors: []
  proportions: []
  epochs:
  - {start_size: 1}
- name: c
  ancestors: []
  proportions: []
  epochs:
  - {start_size: 1}
- name: x
  start_time: 100
  epochs:
  - {start_size: 1}
- name: y
  start_time: 100
  epochs:
  - {start_size: 1}
- name: z
  start_time: 100
  epochs:
  - {start_size: 1}
";
    let g = demes::loads(yaml).unwrap();

    for deme in 0..3 {
        assert!(g.deme(deme).ancestor_names().is_empty());
        assert!(g.deme(deme).ancestor_indexes().is_empty());
        assert!(g.deme(deme).proportions().is_empty());
    }

    for deme in 3..6 {
        assert_eq!(g.deme(deme).ancestor_names().len(), 3);
        assert_eq!(
            g.deme(deme).ancestor_indexes().len(),
            3,
            "{:?}",
            g.deme(deme).ancestor_indexes()
        );
        assert_eq!(g.deme(deme).proportions().len(), 3);
    }
}

// from demes-spec repo
#[test]
fn demlevel_defaults_epoch_01() {
    let yaml = "
time_units: generations
demes:
- name: a
  defaults:
    epoch: {start_size: 1}
- name: b
  epochs:
  - {end_time: 90}
  - {end_size: 100, end_time: 50}
  - {start_size: 100, end_size: 50}
  defaults:
    epoch: {start_size: 1}
";
    let g = demes::loads(yaml).unwrap();
    let start_sizes = g.deme(0).start_sizes().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(start_sizes, vec![1.0]);
    let start_sizes = g.deme(1).start_sizes().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(start_sizes, vec![1.0, 1.0, 100.0]);
}

// from demes-spec repo
#[test]
fn demlevel_defaults_epoch_06() {
    let yaml = "
time_units: generations
demes:
- name: a
  defaults:
    epoch: {end_size: 1}
- name: b
  epochs:
  - {end_time: 90}
  - {start_size: 100, end_time: 50}
  - {start_size: 1, end_size: 100}
  defaults:
    epoch: {end_size: 1}
";
    let g = demes::loads(yaml).unwrap();
    let start_sizes = g.deme(0).start_sizes().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(start_sizes, vec![1.0]);
    let start_sizes = g.deme(1).start_sizes().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(start_sizes, vec![1.0, 100.0, 1.0]);
}

#[test]
fn toplevel_defaults_epoch_03() {
    let yaml = "
time_units: generations
defaults:
  epoch: {end_time: 100}
demes:
- name: a
  epochs:
  - {start_size: 1}
- name: b
  epochs:
  - {start_size: 1}
- name: c
  epochs:
  - {start_size: 1}
- name: d
  ancestors: [a, b, c]
  proportions: [0.2, 0.3, 0.5]
  start_time: 100
  epochs:
  - {start_size: 1, end_time: 50}
  - {start_size: 2, end_time: 0}
- name: e
  ancestors: [a, b, c]
  proportions: [0.2, 0.3, 0.5]
  start_time: 100
  epochs:
  - {start_size: 1}
  - {start_size: 2, end_time: 10}
  defaults:
    epoch: {end_time: 50}
";
    let g = demes::loads(yaml).unwrap();

    for i in 0..3 {
        let end_times = g.deme(i).end_times().map(f64::from).collect::<Vec<f64>>();
        assert_eq!(end_times, vec![100.]);
    }
    // deme d
    let end_times = g.deme(3).end_times().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(end_times, vec![50., 0.]);
    // deme e
    let end_times = g.deme(4).end_times().map(f64::from).collect::<Vec<f64>>();
    assert_eq!(end_times, vec![50., 10.]);
}

#[test]
#[cfg(feature = "json")]
fn toplevel_metadata_01() {
    let yaml = r#"
time_units: generations
metadata:
  one: 1
  two: "two"
  three: [3, 3, 3]
  not_sure: null
  nested:
    nested:
      nested:
        now_im_done: "nested!"
demes:
  - name: a
    epochs:
    - start_size: 100
"#;
    let g = demes::loads(yaml).unwrap();
    assert!(g.metadata().is_some());
    let yaml_from_graph = serde_yaml::to_string(&g).unwrap();
    let g_from_yaml = demes::loads(&yaml_from_graph).unwrap();
    assert_eq!(g, g_from_yaml);
    let json = g.as_json_string().unwrap();
    let g_from_json = demes::loads_json(&json).unwrap();
    assert_eq!(g, g_from_json);
}

#[test]
fn pulse_edge_case_02() {
    let yaml = "
time_units: generations
demes:
- name: deme1
  epochs:
  - {start_size: 1}
- name: deme2
  epochs:
  - {start_size: 1, end_time: 50}
- name: deme3
  ancestors: [deme2]
  epochs:
  - {start_size: 1}
pulses:
- {sources: [deme1], dest: deme3, proportions: [0.9], time: 50}
";
    let _ = demes::loads(yaml).unwrap();
}

#[test]
fn test_deme_size_at() {
    let yaml = "
description: Multiple epochs with different size functions
time_units: generations
demes:
- name: deme0
  epochs:
  - {end_time: 300, start_size: 200}
  - {end_time: 200, start_size: 100}
  - {end_time: 100, size_function: linear, end_size: 200}
  - {end_time: 0, end_size: 1000}
- name: deme1
  ancestors: [deme0]
  start_time: 50
  epochs:
  - {start_size: 10}
";
    let g = demes::loads(yaml).unwrap();

    let epoch = g.deme(0).epochs()[3];
    let dt = f64::from(epoch.start_time()) - 50_f64;
    let r = (f64::from(epoch.end_size()) / f64::from(epoch.start_size())).ln()
        / (f64::from(epoch.start_time()) - f64::from(epoch.end_time()));
    let size_50 = f64::from(epoch.start_size()) * (r * dt).exp();

    assert_eq!(
        200_f64, // size at Infinity
        f64::from(g.deme(0).size_at(f64::INFINITY).unwrap().unwrap())
    );
    assert_eq!(
        200_f64, // size at transition between two epochs, [end_time, start_time)
        f64::from(g.deme(0).size_at(300).unwrap().unwrap())
    );
    assert_eq!(
        100_f64, // middle constant epoch
        f64::from(g.deme(0).size_at(250).unwrap().unwrap())
    );
    assert_eq!(
        150_f64, // middle linear increase epoch
        f64::from(g.deme(0).size_at(150).unwrap().unwrap())
    );
    assert_eq!(
        size_50, // middle exponential increase
        f64::from(g.deme(0).size_at(50).unwrap().unwrap())
    );
    assert!(g.deme(0).size_at(-10).is_err());
    assert_eq!(None, g.deme(1).size_at(100).unwrap()); // epoch that does not yet exist
}

#[test]
fn test_epoch_size_at() {
    let yaml = "
description: Multiple epochs with different size functions
time_units: generations
demes:
- name: deme0
  epochs:
  - {end_time: 300, start_size: 200}
  - {end_time: 200, start_size: 100}
  - {end_time: 100, size_function: linear, end_size: 200}
  - {end_time: 0, end_size: 1000}
- name: deme1
  ancestors: [deme0]
  start_time: 50
  epochs:
  - {start_size: 10}
";
    let g = demes::loads(yaml).unwrap();

    let epoch = g.deme(0).epochs()[3];
    let dt = f64::from(epoch.start_time()) - 50_f64;
    let r = (f64::from(epoch.end_size()) / f64::from(epoch.start_size())).ln()
        / (f64::from(epoch.start_time()) - f64::from(epoch.end_time()));
    let size_50 = f64::from(epoch.start_size()) * (r * dt).exp();

    assert_eq!(
        200_f64, // size at Infinity
        f64::from(
            g.deme(0).epochs()[0]
                .size_at(f64::INFINITY)
                .unwrap()
                .unwrap()
        )
    );
    assert_eq!(
        200_f64, // size at transition between two epochs, [end_time, start_time)
        f64::from(g.deme(0).epochs()[0].size_at(300).unwrap().unwrap())
    );
    assert_eq!(
        100_f64, // middle constant epoch
        f64::from(g.deme(0).epochs()[1].size_at(250).unwrap().unwrap())
    );
    assert_eq!(
        150_f64, // middle linear increase epoch
        f64::from(g.deme(0).epochs()[2].size_at(150).unwrap().unwrap())
    );
    assert_eq!(
        size_50, // middle exponential increase
        f64::from(g.deme(0).epochs()[3].size_at(50).unwrap().unwrap())
    );
    assert!(g.deme(0).epochs()[0].size_at(-10).is_err());
    assert!(g.deme(0).epochs()[0].size_at(10).unwrap().is_none()); // time outside of epoch
}