// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
pub(super) mod line;
mod lightning;

use rand_chacha::ChaCha20Rng;
use rand::SeedableRng;
use glam::{Vec2, Vec3};
use png::ColorType;
use rayon::prelude::*;

use line::{Line, LightConfig};
use lightning::LightningConfig;

use super::image::Image;
use super::layer::Layer;

use crate::utils::{self, Float};

const SIZE: usize = 171;

fn random_point_with(rng: &mut ChaCha20Rng, predicate: impl Fn(Vec2) -> bool) -> Vec2 {
	loop {
		let pos = Vec2::new(rng.next_f32(), rng.next_f32()) * 2.0 - 1.0;
		if predicate(pos) { return pos; }
	}
}

fn random_point_in_circle(rng: &mut ChaCha20Rng) -> Vec2 {
	random_point_with(rng, |pos| pos.length_squared() <= 1.0)
}

fn random_point_in_outer_circle(rng: &mut ChaCha20Rng) -> Vec2 {
	random_point_with(rng, |pos| (0.5..=1.0).contains(&pos.length_squared()))
}

fn bezier(points: &[Vec3], t: f32) -> Vec3 {
	match points.len() {
		0 => panic!("This shouldn't happen"),
		1 => points[0],
		_ => bezier(&points.windows(2).map(|points| points[0].lerp(points[1], t)).collect::<Vec<_>>(), t),
	}
}

struct Job {
	seed: u64,
	gradient: fn(f32) -> Vec3,
	line_generator: fn(&mut ChaCha20Rng) -> Line,
	lightning_count: u32,
	lightning: LightningConfig,
	light: LightConfig,
}

impl Default for Job {
	fn default() -> Job {
		Job {
			seed: 0,
			gradient: Vec3::splat,
			line_generator: |rng| Line(random_point_in_circle(rng), random_point_in_circle(rng)),
			lightning_count: 50,
			lightning: LightningConfig::default(),
			light: LightConfig::default(),
		}
	}
}

fn job(j: Job) -> Vec<u8> {
	const ZOOM: f32 = 0.5;

	let mut layer = Layer::<f32>::new(SIZE, SIZE);
	let mut rng = ChaCha20Rng::seed_from_u64(j.seed);

	let mut lines = Vec::new();
	for _ in 0..j.lightning_count {
		lightning::lightning((j.line_generator)(&mut rng), &mut rng, &j.lightning, &mut lines);
	}

	for y in 0..SIZE {
		for x in 0..SIZE {
			for line in &lines {
				layer.pixel(x, y, line.intensity((Vec2::new(x as f32, y as f32) / (SIZE as f32 - 1.0) * 2.0 - 1.0) / ZOOM, &j.light));
			}
		}
	}

	layer.darken();
	layer.normalise();
	layer.flip_vertical();

	layer
		.into_pixels().into_iter()
		.flat_map(|v| (j.gradient)(v).to_array().into_iter())
		.map(|v| u8::try_from((255.0 * v).round() as i32).expect("Gradient return value must be in [0, 1] for input in [0, 1]"))
		.collect()
}

pub fn generate() {
	const OUTPUT: &str = "../data/textures/powerups.png";

	if utils::exists(OUTPUT) { return; }

	let jobs = [
		Job { // Speed
			seed: 0,
			gradient: |t| bezier(&[
				Vec3::ZERO,
				Vec3::new(0.25, 1.0, 0.125),
				Vec3::new(0.75, 1.0, 0.375),
				Vec3::ONE,
			], t),
			lightning: LightningConfig {
				pos_mul: 0.35,
				t_range: 0.25,
				.. LightningConfig::default()
			},
			lightning_count: 65,
			.. Job::default()
		},
		Job { // Reload
			seed: 1,
			gradient: |t| {
				let mut colour = bezier(&[
					Vec3::ZERO,
					Vec3::new(1.0, 0.875, 0.0),
					Vec3::ONE,
				], t.powf(0.875));
				colour.y *= colour.y.powf(0.375);
				colour.z *= colour.z.powf(0.5);
				colour
			},
			lightning_count: 100,
			.. Job::default()
		},
		Job { // Health and regeneration
			seed: 2,
			gradient: |t| bezier(&[
				Vec3::ZERO,
				Vec3::new(0.5, 0.0625, 0.1875),
				Vec3::new(1.0, 0.25, 0.875),
				Vec3::ONE,
			], t),
			lightning: LightningConfig {
				min_split_line_length: 0.25,
				.. LightningConfig::default()
			},
			lightning_count: 100,
			light: LightConfig {
				att_a: 750.0,
				att_b: 0.75,
			},
			.. Job::default()
		},
		Job { // Bullet damage
			seed: 3,
			gradient: |t| bezier(&[
				Vec3::ZERO,
				Vec3::new(0.75, 0.0, 0.0),
				Vec3::new(1.0, 0.5, 0.0),
				Vec3::ONE,
			], t),
			line_generator: |rng| Line(random_point_in_outer_circle(rng), random_point_in_circle(rng) * 0.5),
			lightning: LightningConfig {
				pos_mul: 0.375,
				t_range: 0.6,
				.. LightningConfig::default()
			},
			light: LightConfig {
				att_a: 4000.0,
				att_b: 1.0,
			},
			lightning_count: 100,
		},
		Job { // Forcefield
			seed: 4,
			gradient: |t| bezier(&[
				Vec3::ZERO,
				Vec3::new(0.0, 0.25, 0.75),
				Vec3::new(0.0, 1.0, 1.0),
				Vec3::ONE,
			], t),
			line_generator: |rng| Line(random_point_in_outer_circle(rng), random_point_in_outer_circle(rng) * 0.75),
			lightning: LightningConfig {
				pos_mul: 0.4,
				.. LightningConfig::default()
			},
			light: LightConfig {
				att_a: 750.0,
				att_b: 1.0,
			},
			lightning_count: 60,
		},
		Job { // Teleportation
			seed: 5,
			gradient: |t| bezier(&[
				Vec3::ZERO,
				Vec3::new(0.09375, 0.0625, 0.375),
				Vec3::new(0.5, 0.125, 1.0),
				Vec3::new(1.0, 0.25, 1.0),
				Vec3::ONE,
			], t * (1.0 + t * (2.0 + t * (-3.0 + t)))),
			lightning_count: 75,
			lightning: LightningConfig {
				pos_mul: 0.25,
				min_split_line_length: 0.15,
				t_range: 0.125,
			},
			light: LightConfig {
				att_a: 1200.0,
				att_b: 1.0,
			},
			.. Job::default()
		},
	];

	let job_count = jobs.len();

	let mut results = jobs.into_par_iter().enumerate().map(|(i, j)| (i, job(j))).collect::<Vec<_>>();
	results.sort_unstable_by(|a, b| a.0.cmp(&b.0));

	Image {
		width: SIZE,
		height: SIZE * job_count,
		pixels: results.into_iter().flat_map(|j| j.1).collect(),
	}.save(OUTPUT, ColorType::Rgb);
}
