use core::{marker::PhantomData, num::NonZeroU64};
use necsim_core::{
    cogs::{DispersalSampler, Habitat, MathsCore, RngCore, SeparableDispersalSampler},
    landscape::Location,
};
use necsim_core_bond::ClosedUnitF64;
use crate::cogs::habitat::non_spatial::NonSpatialHabitat;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
#[cfg_attr(feature = "cuda", derive(rust_cuda::lend::LendRustToCuda))]
#[cfg_attr(feature = "cuda", cuda(free = "M", free = "G"))]
pub struct NonSpatialDispersalSampler<M: MathsCore, G: RngCore<M>> {
    marker: PhantomData<(M, G)>,
}
impl<M: MathsCore, G: RngCore<M>> Default for NonSpatialDispersalSampler<M, G> {
    #[must_use]
    fn default() -> Self {
        Self {
            marker: PhantomData::<(M, G)>,
        }
    }
}
impl<M: MathsCore, G: RngCore<M>> Clone for NonSpatialDispersalSampler<M, G> {
    fn clone(&self) -> Self {
        Self {
            marker: PhantomData::<(M, G)>,
        }
    }
}
#[contract_trait]
impl<M: MathsCore, G: RngCore<M>> DispersalSampler<M, NonSpatialHabitat<M>, G>
    for NonSpatialDispersalSampler<M, G>
{
    #[must_use]
    #[inline]
    fn sample_dispersal_from_location(
        &self,
        _location: &Location,
        habitat: &NonSpatialHabitat<M>,
        rng: &mut G,
    ) -> Location {
        use necsim_core::cogs::RngSampler;
        let habitat_index_max =
            habitat.get_extent().width().get() * habitat.get_extent().height().get();
        let dispersal_target_index =
            rng.sample_index_u64(unsafe { NonZeroU64::new_unchecked(habitat_index_max) });
        #[allow(clippy::cast_possible_truncation)]
        Location::new(
            habitat
                .get_extent()
                .origin()
                .x()
                .wrapping_add((dispersal_target_index % habitat.get_extent().width().get()) as u32),
            habitat
                .get_extent()
                .origin()
                .y()
                .wrapping_add((dispersal_target_index / habitat.get_extent().width().get()) as u32),
        )
    }
}
#[contract_trait]
impl<M: MathsCore, G: RngCore<M>> SeparableDispersalSampler<M, NonSpatialHabitat<M>, G>
    for NonSpatialDispersalSampler<M, G>
{
    #[must_use]
    #[debug_requires((
        u64::from(habitat.get_extent().width()) * u64::from(habitat.get_extent().height())
    ) > 1_u64, "a different, non-self dispersal, target location exists")]
    fn sample_non_self_dispersal_from_location(
        &self,
        location: &Location,
        habitat: &NonSpatialHabitat<M>,
        rng: &mut G,
    ) -> Location {
        use necsim_core::cogs::RngSampler;
        let habitat_index_max =
            habitat.get_extent().width().get() * habitat.get_extent().height().get();
        let current_location_index =
            u64::from(location.y()) * habitat.get_extent().width().get() + u64::from(location.x());
        let dispersal_target_index = {
            let dispersal_target_index =
                rng.sample_index_u64(unsafe { NonZeroU64::new_unchecked(habitat_index_max - 1) });
            if dispersal_target_index >= current_location_index {
                dispersal_target_index + 1
            } else {
                dispersal_target_index
            }
        };
        #[allow(clippy::cast_possible_truncation)]
        Location::new(
            habitat
                .get_extent()
                .origin()
                .x()
                .wrapping_add((dispersal_target_index % habitat.get_extent().width().get()) as u32),
            habitat
                .get_extent()
                .origin()
                .y()
                .wrapping_add((dispersal_target_index / habitat.get_extent().width().get()) as u32),
        )
    }
    #[must_use]
    fn get_self_dispersal_probability_at_location(
        &self,
        _location: &Location,
        habitat: &NonSpatialHabitat<M>,
    ) -> ClosedUnitF64 {
        let self_dispersal = 1.0_f64
            / (f64::from(habitat.get_extent().width()) * f64::from(habitat.get_extent().height()));
        unsafe { ClosedUnitF64::new_unchecked(self_dispersal) }
    }
}