use anyhow::anyhow;
use rand::Rng;
use crate::system::{GriddedPosition, Position, Storage};

pub trait Sticker<P: Position, S: Storage<P>> {
    fn should_stick<R: Rng>(&self, rng: &mut R, space: &S, position: &P) -> bool;
}

pub struct SimpleSticking;

pub struct ProbabilisticSticking {
    pub(crate) stick_probability: f32
}

impl ProbabilisticSticking {
    pub fn new(stick_probability: f32) -> anyhow::Result<ProbabilisticSticking> {
        return if 0f32 < stick_probability && stick_probability <= 1f32 {
            Ok(ProbabilisticSticking { stick_probability })
        } else {
            Err(anyhow!("Sticking probability outside of (0, 1] range."))
        }
    }
}

impl<P: GriddedPosition, S: Storage<P>> Sticker<P, S> for SimpleSticking {
    fn should_stick<R: Rng>(&self, _rng: &mut R, space: &S, position: &P) -> bool {
        (0..P::NEIGHBOURS)
            .map(|n| position.neighbour(n))
            .any(|neighbour| space.is_occupied(&neighbour))
    }
}

impl<P: GriddedPosition, S: Storage<P>> Sticker<P, S> for ProbabilisticSticking {
    fn should_stick<R: Rng>(&self, rng: &mut R, space: &S, position: &P) -> bool {
        (0..P::NEIGHBOURS)
            .map(|n| position.neighbour(n))
            .any(|neighbour| space.is_occupied(&neighbour) && rng.gen_range(0.0f32..=1.0) < self.stick_probability)
    }
}
