// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{fs, path::Path};

use glium::{Surface, VertexBuffer, Display, Texture2d, Program, Frame, DrawParameters, index::{NoIndices, PrimitiveType}, uniforms::SamplerWrapFunction, uniform, implement_vertex};
use glutin::surface::WindowSurface;
use glam::{Vec2, Vec3, Mat4};
use png::ColorType;

use super::forcefield::RenderedForcefield;

use crate::app::filesystem::{Filesystem, FsBase};
use crate::world::{bullet::{Bullet, RADIUS}, player::forcefield::Forcefield};
use crate::utils::{texture, glium_resize};

#[derive(Clone, Copy)]
struct Vertex { v_pos: [f32; 2], v_tex_coords: [f32; 2] }
implement_vertex!(Vertex, v_pos, v_tex_coords);

#[derive(Clone, Copy)]
struct Instance { i_pos: [f32; 2], i_tint: [f32; 3] }
implement_vertex!(Instance, i_pos, i_tint);

pub struct BulletRenderer {
	v_vbo: VertexBuffer<Vertex>,
	i_vbo: VertexBuffer<Instance>,
	texture: Texture2d,
	program: Program,
}

impl BulletRenderer {
	pub fn new(display: &Display<WindowSurface>, fs: &Filesystem) -> BulletRenderer {
		static VERTICES: [Vertex; 4] = [
			Vertex { v_pos: [-RADIUS,  RADIUS], v_tex_coords: [0.0, 0.0] },
			Vertex { v_pos: [ RADIUS,  RADIUS], v_tex_coords: [1.0, 0.0] },
			Vertex { v_pos: [-RADIUS, -RADIUS], v_tex_coords: [0.0, 1.0] },
			Vertex { v_pos: [ RADIUS, -RADIUS], v_tex_coords: [1.0, 1.0] },
		];

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

		BulletRenderer {
			v_vbo: VertexBuffer::immutable(display, &VERTICES).unwrap(),
			i_vbo: VertexBuffer::empty_persistent(display, 0).unwrap(),
			texture: texture::load(display, &fs.get(FsBase::Static, Path::new("textures/bullet.png")), ColorType::Rgba),
			program: Program::from_source(display, &vsh, &fsh, None).unwrap(),
		}
	}

	#[allow(clippy::too_many_arguments)]
	pub fn render(&mut self, bullets: &[Bullet], forcefields: &[RenderedForcefield], display: &Display<WindowSurface>, frame: &mut Frame, params: &DrawParameters, proj_matrix: &Mat4, time_diff: f32) {
		glium_resize::vbo_persistent(&mut self.i_vbo, display, bullets.len());

		let mut count = 0;
		{
			let mut buf = self.i_vbo.map_write();
			for bullet in bullets {
				if bullet.get_time() >= 0.0 {
					let pos = bullet.get_smooth_pos(time_diff);
					buf.set(count, Instance { i_pos: pos.to_array(), i_tint: BulletRenderer::get_tint(bullet, forcefields, pos) });
					count += 1;
				}
			}
		}

		let uniforms = uniform! { u_matrix: proj_matrix.to_cols_array_2d(), u_texture: self.texture.sampled().wrap_function(SamplerWrapFunction::Clamp) };
		frame.draw((&self.v_vbo, self.i_vbo.slice(..count).unwrap().per_instance().unwrap()), NoIndices(PrimitiveType::TriangleStrip), &self.program, &uniforms, params).unwrap();
	}

	fn get_tint(bullet: &Bullet, forcefields: &[RenderedForcefield], bullet_pos: Vec2) -> [f32; 3] {
		let mut tint = if bullet.get_damage_mul() > 1.0 { Vec3::new(1.5, 0.75, 0.75) } else { Vec3::ONE };
		let mut ffield_int = 0.0;
		for ffield in forcefields.iter().filter(|ffield| bullet.should_deflect(ffield.player_id, ffield.team_id)) {
			if let Some(strength) = Forcefield::strength(bullet_pos, ffield.pos) {
				ffield_int += strength * ffield.inner.get_fade();
			}
		}

		let ffield_tint = Vec3::ONE + Vec3::new(-0.25, 0.21484375, 0.625) * ffield_int.min(1.0);
		tint *= ffield_tint;
		tint.to_array()
	}
}
