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

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

use crate::app::filesystem::{Filesystem, FsBase};
use crate::world::{powerup::{Powerup, PowerupType, RADIUS}, colour::Colour};
use crate::utils::{texture::Image, maths::Circle, 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_scale: f32, i_layer: u32 }
implement_vertex!(Instance, i_pos, i_scale, i_layer);

pub struct PowerupRenderer {
	v_vbo: VertexBuffer<Vertex>,
	i_vbo: VertexBuffer<Instance>,
	textures: Texture2dArray,
	average_colours: [Colour; PowerupType::COUNT],
	program: Program,
}

const TEXTURE_RADIUS: f32 = 2.0 * RADIUS;

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

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

		let image = Image::try_new(&fs.get(FsBase::Static, Path::new("textures/powerups.png")), ColorType::Rgb).unwrap();
		let average_colours: [Colour; PowerupType::COUNT] = array::from_fn(|i| {
			let pixel_count = image.pixels.len() / PowerupType::COUNT;
			let mut sum = [0u64; 3];
			for c in image.pixels[pixel_count * i..pixel_count * (i + 1)].chunks_exact(3) {
				for i in 0..3 {
					sum[i] += c[i] as u64;
				}
			}

			let sum = Vec3::new(sum[0] as f32, sum[1] as f32, sum[2] as f32);
			let max = sum.max_element();
			let average = if max != 0.0 { sum / max } else { Vec3::ONE };
			Colour::from(average.to_array().map(|x| (x * 255.0) as u8))
		});

		PowerupRenderer {
			v_vbo: VertexBuffer::immutable(display, &VERTICES).unwrap(),
			i_vbo: VertexBuffer::empty_persistent(display, 0).unwrap(),
			textures: image.into_texture_array(display, PowerupType::COUNT as u32),
			average_colours,
			program: Program::from_source(display, &vsh, &fsh, None).unwrap(),
		}
	}

	pub fn average_colour(&self, typ: PowerupType) -> Colour {
		self.average_colours[typ as usize]
	}

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

		{
			let mut buf = self.i_vbo.map_write();
			for (i, powerup) in powerups.iter().enumerate() {
				buf.set(i, Instance { i_pos: powerup.get_pos().to_array(), i_scale: Powerup::get_scale(powerup.get_time() + time_shift, powerup.get_max_time()), i_layer: powerup.get_type() as u32 });
			}
		}

		let uniforms = uniform! { u_matrix: proj_matrix.to_cols_array_2d(), u_texture: self.textures.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();
	}
}
