use alloc::vec::Vec;
use core::marker::PhantomData;
use necsim_core_bond::{NonNegativeF64, PositiveF64};
use necsim_core::{
    cogs::{
        coalescence_sampler::CoalescenceRngSample, Backup, EmigrationExit, Habitat,
        LocallyCoherentLineageStore, MathsCore, RngCore,
    },
    landscape::{IndexedLocation, Location},
    lineage::{GlobalLineageReference, MigratingLineage, TieBreaker},
    simulation::partial::emigration_exit::PartialSimulation,
};
use crate::decomposition::Decomposition;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct DomainEmigrationExit<M: MathsCore, H: Habitat<M>, C: Decomposition<M, H>> {
    decomposition: C,
    emigrants: Vec<(u32, MigratingLineage)>,
    _marker: PhantomData<(M, H)>,
}
#[contract_trait]
impl<M: MathsCore, H: Habitat<M>, C: Decomposition<M, H>> Backup for DomainEmigrationExit<M, H, C> {
    unsafe fn backup_unchecked(&self) -> Self {
        Self {
            decomposition: self.decomposition.backup_unchecked(),
            emigrants: self
                .emigrants
                .iter()
                .map(|(partition, migrating_lineage)| {
                    (*partition, migrating_lineage.backup_unchecked())
                })
                .collect(),
            _marker: PhantomData::<(M, H)>,
        }
    }
}
#[contract_trait]
impl<
        M: MathsCore,
        H: Habitat<M>,
        C: Decomposition<M, H>,
        G: RngCore<M>,
        S: LocallyCoherentLineageStore<M, H>,
    > EmigrationExit<M, H, G, S> for DomainEmigrationExit<M, H, C>
{
    #[must_use]
    #[debug_ensures(ret.is_some() == (
        old(self.decomposition.map_location_to_subdomain_rank(
            &dispersal_target, &simulation.habitat
        )) == self.decomposition.get_subdomain().rank()
    ), "lineage only emigrates to other subdomains")]
    fn optionally_emigrate(
        &mut self,
        global_reference: GlobalLineageReference,
        dispersal_origin: IndexedLocation,
        dispersal_target: Location,
        prior_time: NonNegativeF64,
        event_time: PositiveF64,
        simulation: &mut PartialSimulation<M, H, G, S>,
        rng: &mut G,
    ) -> Option<(
        GlobalLineageReference,
        IndexedLocation,
        Location,
        NonNegativeF64,
        PositiveF64,
    )> {
        let target_subdomain = self
            .decomposition
            .map_location_to_subdomain_rank(&dispersal_target, &simulation.habitat);
        if target_subdomain == self.decomposition.get_subdomain().rank() {
            return Some((
                global_reference,
                dispersal_origin,
                dispersal_target,
                prior_time,
                event_time,
            ));
        }
        self.emigrants.push((
            target_subdomain,
            MigratingLineage {
                global_reference,
                dispersal_origin,
                dispersal_target,
                prior_time,
                event_time,
                coalescence_rng_sample: CoalescenceRngSample::new(rng),
                tie_breaker: if self.decomposition.get_subdomain().rank() < target_subdomain {
                    TieBreaker::PreferImmigrant
                } else {
                    TieBreaker::PreferLocal
                },
            },
        ));
        None
    }
}
impl<M: MathsCore, H: Habitat<M>, C: Decomposition<M, H>> DomainEmigrationExit<M, H, C> {
    #[must_use]
    pub fn new(decomposition: C) -> Self {
        Self {
            decomposition,
            emigrants: Vec::new(),
            _marker: PhantomData::<(M, H)>,
        }
    }
    pub fn len(&self) -> usize {
        self.emigrants.len()
    }
    pub fn is_empty(&self) -> bool {
        self.emigrants.is_empty()
    }
}
impl<M: MathsCore, H: Habitat<M>, C: Decomposition<M, H>> Iterator
    for DomainEmigrationExit<M, H, C>
{
    type Item = (u32, MigratingLineage);
    fn next(&mut self) -> Option<Self::Item> {
        self.emigrants.pop()
    }
}