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

use std::{fs, path::Path, rc::Rc, cell::RefCell};

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

use lightning::{AnimatedLightning, LightningRenderer};

use super::smooth::forcefield::LocalForcefield;

use crate::app::{config::Config, filesystem::{Filesystem, FsBase}};
use crate::world::{player::{PlayerId, forcefield::Forcefield}, team::TeamId};
use crate::utils::glium_resize;

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

#[derive(Clone, Copy)]
struct Instance { i_pos: [f32; 2], i_rotation: [f32; 2], i_fade: f32, i_gradient_pulse: f32, i_lightning_pulse: f32 }
implement_vertex!(Instance, i_pos, i_rotation, i_fade, i_gradient_pulse, i_lightning_pulse);

pub struct ForcefieldRenderer {
	config: Rc<RefCell<Config>>,

	lightning: AnimatedLightning,
	lightning_renderer: LightningRenderer,

	v_vbo: VertexBuffer<Vertex>,
	i_vbo: VertexBuffer<Instance>,
	program: Program,
}

pub struct RenderedForcefield {
	pub inner: LocalForcefield,
	pub pos: Vec2,
	pub player_id: PlayerId,
	pub team_id: Option<TeamId>,
}

impl ForcefieldRenderer {
	pub fn new(config: Rc<RefCell<Config>>, display: &Display<WindowSurface>, fs: &Filesystem) -> ForcefieldRenderer {
		static VERTICES: [Vertex; 4] = [
			Vertex { v_pos: [-1.0, -1.0] },
			Vertex { v_pos: [ 1.0, -1.0] },
			Vertex { v_pos: [-1.0,  1.0] },
			Vertex { v_pos: [ 1.0,  1.0] },
		];

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

		ForcefieldRenderer {
			config,
			lightning: AnimatedLightning::new(),
			lightning_renderer: LightningRenderer::new(display, fs),
			v_vbo: VertexBuffer::immutable(display, &VERTICES).unwrap(),
			i_vbo: VertexBuffer::empty_persistent(display, 0).unwrap(),
			program: Program::from_source(display, &vsh, &fsh, None).unwrap(),
		}
	}

	pub fn update(&mut self, dt: f32) {
		if self.config.borrow().graphics.forcefield_lightning {
			self.lightning.update(dt);
		}
	}

	pub fn render(&mut self, forcefields: &[RenderedForcefield], display: &Display<WindowSurface>, frame: &mut Frame, params: &DrawParameters, proj_matrix: &Mat4) {
		let count = forcefields.len();
		glium_resize::vbo_persistent(&mut self.i_vbo, display, count);

		{
			let mut buf = self.i_vbo.map_write();
			for (i, ffield) in forcefields.iter().enumerate() {
				buf.set(i, Instance {
					i_pos: ffield.pos.to_array(),
					i_rotation: ffield.inner.get_rotation(),
					i_fade: ffield.inner.get_fade(),
					i_gradient_pulse: ffield.inner.get_gradient_pulse(),
					i_lightning_pulse: ffield.inner.get_lightning_pulse()
				});
			}
		}

		let uniforms = uniform! { u_matrix: proj_matrix.to_cols_array_2d(), u_radius: Forcefield::RADIUS };
		let i_vbo = self.i_vbo.slice(..count).unwrap();
		frame.draw((&self.v_vbo, i_vbo.per_instance().unwrap()), NoIndices(PrimitiveType::TriangleStrip), &self.program, &uniforms, params).unwrap();
		if self.config.borrow().graphics.forcefield_lightning {
			self.lightning_renderer.render(&self.lightning, frame, i_vbo.per_instance().unwrap(), proj_matrix, params);
		}
	}
}
