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

use std::rc::Rc;
use std::fs::{self, File};
use std::path::Path;
use std::io::BufReader;

use bincode::{Options, DefaultOptions};
use glium::{Surface, VertexBuffer, IndexBuffer, Display, Program, Frame, DrawParameters, index::PrimitiveType, uniform, uniforms::SamplerWrapFunction, implement_vertex};
use glutin::surface::WindowSurface;
use glam::Mat4;

use assets::Polygon;
use fragment::Fragment;

use super::particle::{ParticleSystem, explosion::Explosion};

use crate::app::{filesystem::{Filesystem, FsBase}, player_gfx::PlayerGfx, playing::model::changes::Hit};
use crate::utils::{glium_resize, GliumWriter};

#[derive(Clone, Copy)]
struct Vertex { v_pos: [f32; 2], v_tex_coords: [f32; 2], v_layer: u32, v_colour: [f32; 3], v_alpha: f32, v_temp: [f32; 3] }
implement_vertex!(Vertex, v_pos, v_tex_coords, v_layer, v_colour, v_alpha, v_temp);

pub struct DeathEffect {
	all_polygons: Vec<Vec<Polygon>>,
	fragments: Vec<Fragment>,
	vbo: VertexBuffer<Vertex>,
	ibo: IndexBuffer<u32>,
	program: Program,
	player_gfx: Rc<PlayerGfx>,
}

impl DeathEffect {
	pub fn new(display: &Display<WindowSurface>, fs: &Filesystem, player_gfx: Rc<PlayerGfx>) -> DeathEffect {
		let all_polygons = DefaultOptions::new().with_varint_encoding().deserialize_from(BufReader::new(File::open(fs.get(FsBase::Static, Path::new("explosions"))).unwrap())).unwrap();

		let vsh = fs::read_to_string(fs.get(FsBase::Static, Path::new("shaders/death_effect.vsh"))).unwrap();
		let fsh = fs::read_to_string(fs.get(FsBase::Static, Path::new("shaders/death_effect.fsh"))).unwrap();

		DeathEffect {
			all_polygons,
			fragments: Vec::new(),
			vbo: VertexBuffer::empty_persistent(display, 0).unwrap(),
			ibo: IndexBuffer::empty_persistent(display, PrimitiveType::TrianglesList, 0).unwrap(),
			program: Program::from_source(display, &vsh, &fsh, None).unwrap(),
			player_gfx,
		}
	}

	pub fn reset(&mut self) {
		self.fragments.clear();
	}

	pub fn add(&mut self, hit: &Hit, particles: &mut ParticleSystem) {
		let polygons = &self.all_polygons[rand::random::<usize>() % self.all_polygons.len()];
		self.fragments.extend(polygons.iter().map(|poly| Fragment::new(poly, hit)));
		particles.add(|| Explosion::new(hit), 125);
	}

	pub fn update(&mut self, dt: f32) {
		self.fragments.retain_mut(|frag| frag.update(dt));
	}

	pub fn render(&mut self, display: &Display<WindowSurface>, frame: &mut Frame, params: &DrawParameters, proj_matrix: &Mat4) {
		let (v_count, i_count) = self.fragments.iter().map(Fragment::vertex_and_index_count).fold((0, 0), |a, b| (a.0 + b.0, a.1 + b.1));
		glium_resize::vbo_persistent(&mut self.vbo, display, v_count);
		glium_resize::ibo_persistent(&mut self.ibo, display, PrimitiveType::TrianglesList, i_count);

		{
			let (mut v_writer, mut i_writer) = (GliumWriter::new(&mut self.vbo), GliumWriter::new(&mut self.ibo));
			for frag in &self.fragments {
				frag.push_vertices(&mut v_writer, &mut i_writer);
			}
		}

		let mut sampler = self.player_gfx.textures.sampled();
		(sampler.1.wrap_function.0, sampler.1.wrap_function.1) = (SamplerWrapFunction::Clamp, SamplerWrapFunction::Mirror);
		let uniforms = uniform! { u_matrix: proj_matrix.to_cols_array_2d(), u_texture: sampler };
		frame.draw(self.vbo.slice(..v_count).unwrap(), self.ibo.slice(..i_count).unwrap(), &self.program, &uniforms, params).unwrap();
	}
}
