// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
mod fill_holes;
mod value_noise;
mod maze;
mod load_image;
mod vm;
#[cfg(test)]
mod tests;

use std::{num::NonZeroU32, path::{Path, PathBuf}};

use serde::Deserialize;
use glam::{UVec2, IVec2, Vec2};

use vm::Vm;

use crate::world::blocks::{Blocks, MAX_BLOCKS};

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BlockConfig {
	pub block_size: NonZeroU32,
	pub grid_size: UVec2,
	pub ops: Box<[Op]>,
}

pub(super) type BlockMap = Box<[f32]>;

impl BlockConfig {
	pub fn generate(&self, config_path: PathBuf) -> Result<Blocks, String> {
		let map = self.generate_map(config_path)?;
		Ok(Blocks::new(self.block_size, self.grid_size, map.into_iter().map(|x| x > 0.0).collect()))
	}

	pub fn generate_map(&self, config_path: PathBuf) -> Result<BlockMap, String> {
		let blocks = self.grid_size.x.saturating_mul(self.grid_size.y) as usize;
		if blocks > MAX_BLOCKS {
			return Err(format!("too many blocks, expected at most {MAX_BLOCKS}, got {blocks} ({}x{})", self.grid_size.x, self.grid_size.y));
		}
		Vm::new(self.grid_size, config_path).generate(&self.ops)
	}
}

#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum Op {
	// Binary operations
	Add,
	Sub,
	Mul,
	Div,
	Rem,
	Min,
	Max,

	// Generation
	Constant(f32),
	WhiteNoise { #[serde(default)] seed: u64 },
	ValueNoise {
		#[serde(default)] seed: u64,
		levels: ValueNoiseLevels,
	},
	Maze {
		#[serde(default)] seed: u64,
		#[serde(default = "nonzerou32_one")] wall_thickness: NonZeroU32,
		#[serde(default = "nonzerou32_one")] open_thickness: NonZeroU32,
		#[serde(default)] open_prob: f32,
		#[serde(default)] close_prob: f32,
	},
	Circle(Vec2),
	Ellipse(Vec2, f32),
	Rect {
		pos: IVec2,
		size: IVec2,
		#[serde(default = "f32_one")] fill: f32,
	},
	Gradient(Vec2),
	Image(Box<Path>),

	// Utilities
	FillHoles,
	Threshold,
	FlipHorizontal,
	FlipVertical,
	Shrink(usize),

	// Stack operations
	Duplicate(isize),
	Remove(isize),
}

#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum ValueNoiseLevels {
	Fractal {
		min: NonZeroU32,
		scale: NonZeroU32,
		octaves: NonZeroU32,
	},
	Sizes(Box<[NonZeroU32]>),
	WeightedSizes(Box<[(NonZeroU32, f32)]>),
}

fn nonzerou32_one() -> NonZeroU32 { const { NonZeroU32::new(1).unwrap() } }
fn f32_one() -> f32 { 1.0 }
