use log::trace;
/// Implements the concepts from https://lodev.org/cgtutor/randomnoise.html
use rand;

use crate::noise::NoiseSource;
use crate::vec3::Vec3;

const NOISE_SIZE: usize = 128;
pub struct Lode {
    // Using fixed array causes stack overflow.
    noise: Vec<Vec<Vec<f32>>>, //[[[f32; NOISE_SIZE]; NOISE_SIZE]; NOISE_SIZE],
}

impl Lode {
    pub fn new<R>(rng: &mut R) -> Lode
    where
        R: rand::Rng,
    {
        let mut noise = vec![vec![vec![0.; NOISE_SIZE]; NOISE_SIZE]; NOISE_SIZE];
        // Squelch warning about only being used to index, as this way reads more consistently.
        #[allow(clippy::needless_range_loop)]
        for x in 0..NOISE_SIZE {
            for y in 0..NOISE_SIZE {
                for z in 0..NOISE_SIZE {
                    noise[x][y][z] = rng.gen();
                }
            }
        }
        Lode { noise }
    }
}

impl NoiseSource for Lode {
    fn value(&self, p: Vec3) -> f32 {
        // Fractional part of vector components
        let x = p.x - p.x.floor();
        let y = p.y - p.y.floor();
        let z = p.z - p.z.floor();

        // Wrap around the arrays
        let x1 = (p.x.floor() as usize + NOISE_SIZE) % NOISE_SIZE;
        let y1 = (p.y.floor() as usize + NOISE_SIZE) % NOISE_SIZE;
        let z1 = (p.z.floor() as usize + NOISE_SIZE) % NOISE_SIZE;

        // Neighbor values
        let x2 = (x1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;
        let y2 = (y1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;
        let z2 = (z1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;

        trace!(target: "noise", "p {}", p);
        trace!(target: "noise", "x {:.2} y {:.2} z {:.2}", x, y, z);
        trace!(target: "noise", "x1 {:.2} y1 {:.2} z1 {:.2}", x1, y1, z1);
        trace!(target: "noise", "x2 {:.2} y2 {:.2} z2 {:.2}", x2, y2, z2);
        let mut value = 0.;
        // Smooth the noise with bilinear interpolation.  Completely untested
        value += x * y * z * self.noise[x1][y1][z1];
        value += (1. - x) * y * z * self.noise[x2][y1][z1];
        value += x * (1. - y) * z * self.noise[x1][y2][z1];
        value += x * y * (1. - z) * self.noise[x1][y1][z2];
        value += (1. - x) * (1. - y) * z * self.noise[x2][y2][z1];
        value += x * (1. - y) * (1. - z) * self.noise[x1][y2][z2];
        value += (1. - x) * y * (1. - z) * self.noise[x2][y1][z2];
        value += (1. - x) * (1. - y) * (1. - z) * self.noise[x2][y2][z2];

        trace!(target: "noise", "luma {}", value);
        value
    }
}
