// 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, Program, Frame, DrawParameters, index::{NoIndices, PrimitiveType}, uniform, implement_vertex};
use glutin::surface::WindowSurface;
use glam::{Vec2, Mat4};

use super::World;

use crate::app::filesystem::{Filesystem, FsBase};

/*
 * Want to avoid using an if-statement in the shader as that might slow down
 * shader execution. Even though there are very few vertices, I don't know if it
 * might slow down rendering other parts.
 *
 * Source for if-statements being bad:
 * 	https://stackoverflow.com/a/66473857
 * 	https://stackoverflow.com/a/37837060
 */
#[derive(Clone, Copy)]
struct Vertex {
	world_pos: [f32; 2],
	ndc_pos: [f32; 2],
	use_ndc: [f32; 2], // If 0, use the world position, if 1 use the NDC position
}

implement_vertex!(Vertex, world_pos, ndc_pos, use_ndc);

pub(super) struct BorderRenderer {
	world_size: Vec2,
	vbo: VertexBuffer<Vertex>,
	program: Program,
}

impl BorderRenderer {
	pub fn new(display: &Display<WindowSurface>, fs: &Filesystem) -> BorderRenderer {
		let vsh = fs::read_to_string(fs.get(FsBase::Static, Path::new("shaders/border.vsh"))).unwrap();
		let fsh = fs::read_to_string(fs.get(FsBase::Static, Path::new("shaders/border.fsh"))).unwrap();

		let vertices = [
			// Bottom
			Vertex { world_pos: [0.0, 0.0], ndc_pos: [-2.0, -2.0], use_ndc: [1.0, 1.0] },
			Vertex { world_pos: [0.0, 0.0], ndc_pos: [2.0, -2.0], use_ndc: [1.0, 1.0] },
			Vertex { world_pos: [0.0, -0.5], ndc_pos: [2.0, 0.0], use_ndc: [1.0, 0.0] },

			Vertex { world_pos: [0.0, 0.0], ndc_pos: [-2.0, -2.0], use_ndc: [1.0, 1.0] },
			Vertex { world_pos: [0.0, -0.5], ndc_pos: [-2.0, 0.0], use_ndc: [1.0, 0.0] },
			Vertex { world_pos: [0.0, -0.5], ndc_pos: [2.0, 0.0], use_ndc: [1.0, 0.0] },

			// Top
			Vertex { world_pos: [0.0, 0.0], ndc_pos: [-2.0, 2.0], use_ndc: [1.0, 1.0] },
			Vertex { world_pos: [0.0, 0.0], ndc_pos: [2.0, 2.0], use_ndc: [1.0, 1.0] },
			Vertex { world_pos: [0.0, 0.5], ndc_pos: [2.0, 0.0], use_ndc: [1.0, 0.0] },

			Vertex { world_pos: [0.0, 0.0], ndc_pos: [-2.0, 2.0], use_ndc: [1.0, 1.0] },
			Vertex { world_pos: [0.0, 0.5], ndc_pos: [-2.0, 0.0], use_ndc: [1.0, 0.0] },
			Vertex { world_pos: [0.0, 0.5], ndc_pos: [2.0, 0.0], use_ndc: [1.0, 0.0] },

			// Left
			Vertex { world_pos: [0.0, -0.5], ndc_pos: [-2.0, 0.0], use_ndc: [1.0, 0.0] },
			Vertex { world_pos: [-0.5, -0.5], ndc_pos: [0.0, 0.0], use_ndc: [0.0, 0.0] },
			Vertex { world_pos: [-0.5, 0.5], ndc_pos: [0.0, 0.0], use_ndc: [0.0, 0.0] },

			Vertex { world_pos: [0.0, -0.5], ndc_pos: [-2.0, 0.0], use_ndc: [1.0, 0.0] },
			Vertex { world_pos: [0.0, 0.5], ndc_pos: [-2.0, 0.0], use_ndc: [1.0, 0.0] },
			Vertex { world_pos: [-0.5, 0.5], ndc_pos: [0.0, 0.0], use_ndc: [0.0, 0.0] },

			// Right
			Vertex { world_pos: [0.0, -0.5], ndc_pos: [2.0, 0.0], use_ndc: [1.0, 0.0] },
			Vertex { world_pos: [0.5, -0.5], ndc_pos: [0.0, 0.0], use_ndc: [0.0, 0.0] },
			Vertex { world_pos: [0.5, 0.5], ndc_pos: [0.0, 0.0], use_ndc: [0.0, 0.0] },

			Vertex { world_pos: [0.0, -0.5], ndc_pos: [2.0, 0.0], use_ndc: [1.0, 0.0] },
			Vertex { world_pos: [0.0, 0.5], ndc_pos: [2.0, 0.0], use_ndc: [1.0, 0.0] },
			Vertex { world_pos: [0.5, 0.5], ndc_pos: [0.0, 0.0], use_ndc: [0.0, 0.0] },
		];

		BorderRenderer {
			world_size: Vec2::ZERO,
			vbo: VertexBuffer::immutable(display, &vertices).unwrap(),
			program: Program::from_source(display, &vsh, &fsh, None).unwrap(),
		}
	}

	pub fn reset(&mut self, world: &World) {
		self.world_size = world.get_size();
	}

	pub fn render(&self, frame: &mut Frame, params: &DrawParameters, proj_matrix: &Mat4) {
		let mut size = self.world_size;
		for i in 0..2 {
			if size[i].is_infinite() {
				size[i] = f32::MAX;
			}
		}

		let uniforms = uniform! { u_matrix: proj_matrix.to_cols_array_2d(), u_world_size: size.to_array() };
		frame.draw(&self.vbo, NoIndices(PrimitiveType::TrianglesList), &self.program, &uniforms, params).unwrap();
	}
}
