// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
mod polygon;
mod edge;
mod line;

use std::fs::File;
use std::io::BufWriter;
use std::collections::{BTreeSet, BTreeMap};
use std::f32::consts::TAU;

use bincode::{Options, DefaultOptions};
use rand::{distributions::{Distribution, Uniform}, SeedableRng};
use rand_chacha::ChaCha20Rng;
use glam::Vec2;

use edge::Edge;
use line::Line;
use polygon::{Polygon, Split, ConcaveSplit};
use assets::Polygon as OutPolygon;

use crate::utils;

pub struct Explosion {
	polygons: Vec<Polygon>,
	new_polygons: Vec<Polygon>,
	edges: BTreeSet<Edge>,
	int_edges: BTreeMap<Edge, Vec2>,
	pos_dist: Uniform<f32>,
	angle_dist: Uniform<f32>,
	rng: ChaCha20Rng,
}

impl Explosion {
	fn new() -> Explosion {
		let poly = Polygon::new_root();
		let edges = poly.edges().collect::<BTreeSet<_>>();

		Explosion {
			polygons: Vec::new(),
			new_polygons: Vec::new(),
			edges,
			int_edges: BTreeMap::new(),
			pos_dist: Uniform::new(-0.5, 0.5),
			angle_dist: Uniform::new(0.0, TAU),
			rng: ChaCha20Rng::seed_from_u64(0),
		}
	}

	fn generate(&mut self) -> Result<Vec<OutPolygon>, ()> {
		self.edges.clear();

		let poly = Polygon::new_root();
		self.edges.extend(poly.edges());
		self.polygons.push(poly);

		while self.polygons.len() < 35 {
			self.step();
		}

		let mut ret = Vec::with_capacity(self.polygons.len() + 1);
		for poly in self.polygons.drain(..) {
			match poly.split_if_concave(&mut self.rng) {
				ConcaveSplit::Convex(p) => ret.push(p.into_output()),
				ConcaveSplit::Concave([a, b]) => { ret.push(a.into_output()); ret.push(b.into_output()) },
				ConcaveSplit::Fail => return Err(()),
			}
		}
		Ok(ret)
	}

	fn step(&mut self) {
		let line = Line::new(self.pos_dist.sample(&mut self.rng), self.pos_dist.sample(&mut self.rng), self.angle_dist.sample(&mut self.rng));
		if self.get_int_edges(&line).is_err() { return; }
		self.polygons.retain(|poly| {
			match poly.split(&self.int_edges) {
				Split::None => true,
				Split::Some(new) => {
					for poly in new {
						self.edges.extend(poly.edges());
						self.new_polygons.push(poly);
					}
					false
				}
				Split::TooMany(int) => {
					/*
					 * Need to remove the edges from the intersected set to avoid
					 * them from being removed from self.edges when they weren't
					 * removed from the polygons, resulting in self.edges and the
					 * edges in self.polygons being out of sync.
					 */
					for edge in int {
						self.int_edges.remove(edge);
					}
					true
				}
			}
		});
		self.polygons.append(&mut self.new_polygons);

		// Can remove all interesecting edges as they would have all been split up, none remaining in any polygon
		for int_edge in self.int_edges.keys() {
			self.edges.remove(int_edge);
		}
	}

	fn get_int_edges(&mut self, line: &Line) -> Result<(), ()> {
		self.int_edges.clear();
		for edge in &self.edges {
			if let Some(point) = line.intersecting(edge)? {
				self.int_edges.insert(edge.clone(), point);
			}
		}
		Ok(())
	}
}

pub fn generate() {
	const PATH: &str = "../data/explosions";
	if !utils::exists(PATH) {
		const COUNT: usize = 8;

		let mut explosion = Explosion::new();
		let mut all_polygons = Vec::with_capacity(COUNT);
		while all_polygons.len() < COUNT {
			if let Ok(polygons) = explosion.generate() {
				all_polygons.push(polygons);
			}
		}

		DefaultOptions::new().with_varint_encoding().serialize_into(BufWriter::new(File::create(PATH).unwrap()), &all_polygons).unwrap();
	}
}
