use rand::prelude::Rng;
use crate::system::{GriddedPosition, Position};
use crate::system::spaces::square_grid::Grid3D;

pub trait Walker<P: Position> {
    fn walk<R: Rng>(&self, rng: &mut R, position: &P) -> P;
}

pub struct LocalRandomWalker;

impl<P: GriddedPosition> Walker<P> for LocalRandomWalker {
    fn walk<R: Rng>(&self, rng: &mut R, position: &P) -> P {
        position.neighbour(rng.gen_range(0u32..P::NEIGHBOURS))
    }
}

pub struct DiagonalRandomWalker;

impl Walker<Grid3D> for DiagonalRandomWalker {
    fn walk<R: Rng>(&self, rng: &mut R, position: &Grid3D) -> Grid3D {
        let a: Vec<i32> = (0..3)
            .map(|r| rng.gen_range(-1..=1))
            .collect();

        position.clone() + Grid3D::from_cartesian([a[0] as f32, a[1] as f32, a[2] as f32])
    }
}

mod test {
    use rand::rngs::SmallRng;
    use rand::{SeedableRng, thread_rng};
    use crate::system::{GriddedPosition, Position};
    use crate::system::spaces::square_grid::{Grid2D, Grid3D};
    use crate::system::walker::{DiagonalRandomWalker, LocalRandomWalker, Walker};

    #[test]
    fn uniformity() {
        let walker = LocalRandomWalker;
        let mut rng = SmallRng::from_rng(thread_rng()).unwrap();
        let mut results: Vec<Grid2D> = vec![];

        let origin = &Grid2D::zero();

        let x: u32 = (1_000_000);
        for i in 0..x {
            results.push(walker.walk(&mut rng, origin));
        }

        let a = results
            .iter()
            .filter(|a| **a == Grid2D { x: 0, y: 1 })
            .count();

        let b = results
            .iter()
            .filter(|a| **a == Grid2D { x: 0, y: -1 })
            .count();

        let c = results
            .iter()
            .filter(|a| **a == Grid2D { x: 1, y: 0 })
            .count();

        let d = results
            .iter()
            .filter(|a| **a == Grid2D { x: -1, y: 0 })
            .count();

        println!("{} {} {} {}", a as f32 / x as f32, b as f32 / x as f32, c as f32 / x as f32, d as f32 / x as f32);
    }

    #[test]
    fn diagonal() {
        let drw = DiagonalRandomWalker;
        let mut rng = SmallRng::from_rng(thread_rng()).unwrap();

        for _ in 0..20 {
            println!("{:?}", drw.walk(&mut rng, &Grid3D::zero()))
        }
    }
}
