// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use serde::{Serialize, Deserialize};
use glam::Vec3;
use nonempty::NonEmpty;

pub const DEFAULT_STAR_DENSITY: f32 = 600.0;

pub const GRADIENT_COUNT: usize = 2;
const GRADIENT_OUTPUT_SIZE: usize = 256;

#[derive(Clone, Serialize, Deserialize)]
pub struct SkyConfig {
	pub gradients: [Gradient; GRADIENT_COUNT],
	pub cloud_brightness: f32,
	pub star_density: f32,
	pub star_intensity: f32,
	pub star_colour_brightness: f32,
	pub star_colour_offset: f32,
}

impl SkyConfig {
	pub fn get_gradient_colours(&self) -> Vec<(f32, f32, f32)> {
		let mut colours = Vec::with_capacity(GRADIENT_OUTPUT_SIZE * self.gradients.len());
		for gradient in &self.gradients {
			match gradient {
				Gradient::Bezier(bezier) => {
					for i in 0..GRADIENT_OUTPUT_SIZE {
						let t = i as f32 / (GRADIENT_OUTPUT_SIZE as f32 - 1.0);
						colours.push((bezier.get(t) * self.cloud_brightness).to_array().into());
					}
				},
			}
		}
		colours
	}
}

impl Default for SkyConfig {
	fn default() -> SkyConfig {
		let black = Gradient::Bezier(Bezier::new(NonEmpty::new(Vec3::ZERO)));
		SkyConfig {
			gradients: [black.clone(), black],
			cloud_brightness: 0.25,
			star_density: DEFAULT_STAR_DENSITY,
			star_intensity: 1.5,
			star_colour_brightness: 0.5,
			star_colour_offset: 0.5,
		}
	}
}

#[derive(Clone, Serialize, Deserialize)]
pub enum Gradient { // Useless for now but very helpful in case I add more gradients in the future
	Bezier(Bezier),
}

#[derive(Clone, Serialize, Deserialize)]
pub struct Bezier {
	pub pre_power: f32,
	pub points: NonEmpty<Vec3>,
	pub post_power: f32,
}

impl Bezier {
	pub(super) fn new(points: NonEmpty<Vec3>) -> Bezier {
		Bezier {
			pre_power: 1.0,
			points,
			post_power: 1.0,
		}
	}

	fn get(&self, t: f32) -> Vec3 {
		Bezier::bezier(&self.points, t.powf(self.pre_power)).powf(self.post_power)
	}

	fn bezier(points: &NonEmpty<Vec3>, t: f32) -> Vec3 {
		if points.tail.is_empty() {
			points.head
		} else {
			let mut next_points = NonEmpty::new(points.head.lerp(points.tail[0], t));
			for neighbours in points.tail.windows(2) {
				next_points.push(neighbours[0].lerp(neighbours[1], t));
			}
			Bezier::bezier(&next_points, t)
		}
	}
}
