// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{borrow::Cow, num::NonZeroU32};

use glam::UVec2;
use rand::SeedableRng;

use super::{Vm, ValueNoiseLevels};

use crate::world::WorldRng;
use crate::utils::{maths, rng::Float};

impl Vm {
	pub(super) fn value_noise<'a>(&mut self, seed: u64, levels: &'a ValueNoiseLevels) -> Result<(), String> {
		let levels: Cow<'a, [(NonZeroU32, f32)]> = match *levels {
			ValueNoiseLevels::Fractal { min, scale, ref octaves } => {
				if octaves.get() > 100 { return Err(format!("too many octaves, got {octaves}, need 0 < octaves ≤ 100")) }

				let mut size = min.get();
				let mut ret = Vec::with_capacity(octaves.get() as usize);
				for _ in 0..octaves.get() {
					ret.push((NonZeroU32::new(size).unwrap(), size as f32));

					// Error not needed if the final iteration but I don't care
					size = size.checked_mul(scale.get()).ok_or_else(|| format!("overflow due to large octaves {octaves} and scale {scale}"))?;
				}

				Cow::Owned(ret)
			}
			ValueNoiseLevels::Sizes(ref sizes) => Cow::Owned(sizes.iter().map(|&x| (x, x.get() as f32)).collect()),
			ValueNoiseLevels::WeightedSizes(ref data) => Cow::Borrowed(data),
		};

		let noise_size = UVec2::splat(levels.iter().max_by_key(|(size, _)| size.get()).ok_or_else(|| String::from("no levels provided"))?.0.get())
			.max(self.grid_size + UVec2::splat(1)) // Adding one to avoid wrapping around
			.min(UVec2::splat(4096));

		let noise = {
			let mut rng = WorldRng::seed_from_u64(seed);
			(0..noise_size.element_product()).map(|_| rng.next_f32()).collect::<Vec<f32>>()
		};

		let mut bmap = vec![0.0; self.block_count];

		for (size, weight) in levels.iter() {
			let size = size.get();
			let mut i = 0;
			for y in 0..self.grid_size.y {
				for x in 0..self.grid_size.x {
					let pos = UVec2::new(x, y);
					let p0 = (pos / size) * size;
					let p1 = p0 + UVec2::splat(size);
					let t = (pos - p0).as_vec2() / size as f32;

					bmap[i] += weight * maths::bilerp(
						noise[((p0.x % noise_size.x) + (p0.y % noise_size.y) * noise_size.x) as usize],
						noise[((p1.x % noise_size.x) + (p0.y % noise_size.y) * noise_size.x) as usize],
						noise[((p0.x % noise_size.x) + (p1.y % noise_size.y) * noise_size.x) as usize],
						noise[((p1.x % noise_size.x) + (p1.y % noise_size.y) * noise_size.x) as usize],
						t.x, t.y,
					);

					i += 1;
				}
			}
		}

		let max = levels.iter().map(|(_, weight)| weight).sum::<f32>();
		for x in &mut bmap { *x /= max; }

		self.stack.push(bmap.into_boxed_slice());
		Ok(())
	}
}
