// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::fs::File;
use std::io::BufWriter;
use std::f32::consts::TAU;

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

use super::{image::Image, layer::Layer, powerup::line::{Line, LightConfig}};

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

const IMAGE_PATH: &str = "../data/textures/stars.png";
const RADII_PATH: &str = "../data/textures/star_radii";
const COUNT: usize = 256;
const SIZE: usize = 31;

// Inspired by GIMP's sparkle plugin on single pixels
fn generate_star(seed: u64) -> (Vec<u8>, Vec2) {
	let mut rng = ChaCha20Rng::seed_from_u64(seed);
	let mut layer = Layer::<f32>::new(SIZE, SIZE);
	let angle = rng.next_f32() * TAU;
	let intensity = rng.next_f32() * rng.next_f32();
	let minor_scale_mul = rng.next_f32() * 0.4 + 0.4;

	let lines: Vec<(Line, LightConfig)> = (0..4).map(|i| {
		let angle = angle + i as f32 * TAU / 8.0;
		let scale = if i % 2 == 0 { 1.0 } else { minor_scale_mul };

		let (radius, att_a) = (intensity * scale, 8000.0 / scale / scale);

		let (sin, cos) = angle.sin_cos();
		let p = Vec2::new(cos, sin) * radius;
		(Line(-p, p), LightConfig { att_a, att_b: 1.0 })
	}).collect();

	for (line, light) in lines {
		for y in 0..SIZE {
			for x in 0..SIZE {
				const SAMPLES: usize = 8;

				let mut sum = 0.0;
				for sy in 0..SAMPLES {
					for sx in 0..SAMPLES {
						let (dx, dy) = ((sx as f32 + 0.5) / SAMPLES as f32 - 0.5, (sy as f32 + 0.5) / SAMPLES as f32 - 0.5);
						sum += line.intensity(Vec2::new(x as f32 + dx, y as f32 + dy) / (SIZE as f32 - 1.0) * 2.0 - 1.0, &light);
					}
				}

				layer.pixel(x, y, sum);
			}
		}
	}

	layer.darken();
	layer.normalise();
	layer.pow(1.5);

	let image = Image::from(layer);
	let mut radii = Vec2::ZERO;

	let centre = (Vec2::splat(SIZE as f32) - 1.0) / 2.0;

	let mut i = 0;
	for y in 0..image.height {
		for x in 0..image.width {
			if image.pixels[i] > 0 {
				let point_radii = (Vec2::new(x as f32, y as f32) - centre).abs() + 0.5;
				radii = radii.max(point_radii);
			}

			i += 1;
		}
	}

	(image.flip_vertical(ColorType::Grayscale).pixels.into(), radii)
}

pub fn generate() {
	if utils::exists(IMAGE_PATH) { return; }

	let mut results = (0..COUNT).into_par_iter().map(|i| {
		let (pixels, radii) = generate_star(i as u64);
		(i, pixels, radii)
	}).collect::<Vec<_>>();
	results.sort_unstable_by(|a, b| a.0.cmp(&b.0));

	let all_radii = results.iter().map(|(_, _, radii)| *radii).collect::<Vec<_>>();
	DefaultOptions::new().with_varint_encoding().serialize_into(BufWriter::new(File::create(RADII_PATH).unwrap()), &all_radii).unwrap();

	Image {
		width: SIZE,
		height: SIZE * COUNT,
		pixels: results.into_iter().flat_map(|(_, pixels, _)| pixels).collect(),
	}.save(IMAGE_PATH, ColorType::Grayscale);
}
