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

use std::{fs, mem, array, path::Path, collections::HashMap};

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

use mesh::Rect;
use border::{Border, BorderCorner, EDGE_I_TO_POS, CORNER_I_TO_POS};

use crate::app::{filesystem::{Filesystem, FsBase}, playing::view::Camera};
use crate::world::blocks::Blocks;
use crate::utils::texture;

#[derive(Clone, Copy)]
struct BlockVertex { pos: [f32; 2], tex_coords: [f32; 2] }
implement_vertex!(BlockVertex, pos, tex_coords);

#[derive(Clone, Copy)]
struct BorderVertex { pos: [f32; 2], grad_pos: [f32; 2], grad_type: f32 }
implement_vertex!(BorderVertex, pos, grad_pos, grad_type);

pub struct BlocksRenderer {
	section_grid_size: UVec2,
	sections: HashMap<UVec2, Option<Section> /* Some if non-empty, None if empty */>,
	ibo: IndexBuffer<u32>,
	texture: Texture2d,
	block_program: Program,
	border_program: Program,
}

struct Section {
	block_vbo: VertexBuffer<BlockVertex>,
	border_vbo: VertexBuffer<BorderVertex>,
}

pub(super) const SECTION_SIZE: u32 = 32;
const BORDER_WIDTH: f32 = 0.1875;
const BORDER_MAX_ALPHA: f32 = 0.5;
const BORDER_MAX_ALPHA_PX_WIDTH: f32 = 2.5; // How wide the border must be to each the maximum alpha

impl BlocksRenderer {
	pub fn new(display: &Display<WindowSurface>, fs: &Filesystem) -> BlocksRenderer {
		let block_vsh = fs::read_to_string(fs.get(FsBase::Static, Path::new("shaders/blocks.vsh"))).unwrap();
		let block_fsh = fs::read_to_string(fs.get(FsBase::Static, Path::new("shaders/blocks.fsh"))).unwrap();
		let border_vsh = fs::read_to_string(fs.get(FsBase::Static, Path::new("shaders/block_border.vsh"))).unwrap();
		let border_fsh = fs::read_to_string(fs.get(FsBase::Static, Path::new("shaders/block_border.fsh"))).unwrap();

		/*
		 * Using an upper bound of the absolute maximum of the (impossible) case
		 * where a section consists of only blocks with each block being a 1x1
		 * rectangle, and block having eight border rects.
		 *
		 * It would be possible to mathematically prove this upper bound to be
		 * lower, but I can't be bothered and if I get it wrong and guess too low
		 * then there would be a nasty panic lurking.
		 */
		let mut indices = Vec::with_capacity((SECTION_SIZE * SECTION_SIZE * 8 * 6) as usize);
		for i in (0..SECTION_SIZE * SECTION_SIZE * 8 * 4).step_by(4) {
			indices.extend_from_slice(&[
				i    , i + 1, i + 2,
				i + 1, i + 2, i + 3,
			]);
		}

		BlocksRenderer {
			section_grid_size: UVec2::ZERO,
			sections: HashMap::new(),
			ibo: IndexBuffer::immutable(display, PrimitiveType::TrianglesList, &indices).unwrap(),
			texture: texture::load(display, &fs.get(FsBase::Static, Path::new("textures/block.png")), ColorType::Rgba),
			block_program: Program::from_source(display, &block_vsh, &block_fsh, None).unwrap(),
			border_program: Program::from_source(display, &border_vsh, &border_fsh, None).unwrap(),
		}
	}

	pub fn reset(&mut self, blocks: &Blocks) {
		let grid_size = blocks.get_grid_size();
		self.section_grid_size = UVec2::new(grid_size.x.div_ceil(SECTION_SIZE), grid_size.y.div_ceil(SECTION_SIZE));
		self.sections.clear();
		self.sections.reserve(self.section_grid_size.element_product() as usize);
	}

	pub fn render(&mut self, display: &Display<WindowSurface>, frame: &mut Frame, params: &DrawParameters, blocks: &Blocks, camera: &Camera, render_border: bool) {
		if let Some((min, max)) = self.get_visible_sections(blocks, camera) {
			let matrix = camera.get_matrix().to_cols_array_2d();
			for y in min.y..=max.y {
				for x in min.x..=max.x {
					let pos = UVec2::new(x, y);
					let section = self.sections.entry(pos).or_insert_with(|| BlocksRenderer::generate_section(pos, blocks, display));

					if let Some(section) = section {
						let uniforms = uniform! { u_matrix: matrix, u_texture: self.texture.sampled().wrap_function(SamplerWrapFunction::Repeat) };
						frame.draw(&section.block_vbo, self.ibo.slice(0..(section.block_vbo.len() / 4) * 6).unwrap(), &self.block_program, &uniforms, params).unwrap();
						if render_border {
							let px_width = camera.rel_world_to_pixel_coords(BORDER_WIDTH).min(BORDER_MAX_ALPHA_PX_WIDTH);
							let alpha = px_width.powi(2) * BORDER_MAX_ALPHA / BORDER_MAX_ALPHA_PX_WIDTH.powi(2);
							let uniforms = uniform! { u_matrix: matrix, u_alpha: alpha };
							frame.draw(&section.border_vbo, self.ibo.slice(0..(section.border_vbo.len() / 4) * 6).unwrap(), &self.border_program, &uniforms, params).unwrap();
						}
					}
				}
			}
		}
	}

	fn copy_section(blocks: &Blocks, pos: UVec2) -> (UVec2, Box<[bool]>, Box<[u8]>) {
		let (grid_size, data) = (blocks.get_grid_size(), blocks.get_data());

		let min = pos * SECTION_SIZE;
		debug_assert!(min.cmplt(grid_size).all());

		let max = (min + UVec2::splat(SECTION_SIZE)).min(grid_size);

		let size = max - min;
		let mut blocks = Vec::with_capacity(size.element_product() as usize);

		let mut i = (min.x + min.y * grid_size.x) as usize;
		for _ in 0..size.y {
			blocks.extend_from_slice(&data[i..i + size.x as usize]);
			i += grid_size.x as usize;
		}

		let mut border = Vec::with_capacity(size.element_product() as usize);
		for y in 0..size.y {
			for x in 0..size.x {
				let pos = UVec2::new(x, y) + min;
				let mut empty_adjacent = 0;
				let mut i = 0;

				if data[(pos.x + pos.y * grid_size.x) as usize] {
					for iy in -1..=1 {
						for ix in -1..=1 {
							if ix == 0 && iy == 0 {
								continue;
							}

							let pos = pos.as_ivec2() + IVec2::new(ix, iy);
							let has_no_neighbour =
								!(0..grid_size.x as i32).contains(&pos.x) ||
								!(0..grid_size.y as i32).contains(&pos.y) ||
								!data[(pos.x + pos.y * grid_size.x as i32) as usize];

							empty_adjacent |= (has_no_neighbour as u8) << i;
							i += 1;
						}
					}
				}

				border.push(Border::empty_adjacent_to_section_data(empty_adjacent));
			}
		}

		(size, blocks.into_boxed_slice(), border.into_boxed_slice())
	}

	fn generate_section(pos: UVec2, blocks: &Blocks, display: &Display<WindowSurface>) -> Option<Section> {
		let (size, section_blocks, border) = BlocksRenderer::copy_section(blocks, pos);
		let num_blocks = section_blocks.iter().filter(|&&b| b).count();
		if num_blocks == 0 { return None; }

		let block_size = blocks.get_block_size().get() as f32;
		let off = (pos * SECTION_SIZE * blocks.get_block_size().get()).as_vec2() - blocks.get_offset(block_size);

		let block_rects: Vec<Rect> = mesh::section_to_rects(size, section_blocks);
		let mut block_vertices = Vec::with_capacity(block_rects.len() * 4); // 4 vertices per rectangle (index buffer used)
		for rect in block_rects {
			let rect_size = (rect.1 - rect.0).as_vec2();
			let p0 = rect.0.as_vec2() * block_size + off;
			let p1 = p0 + rect_size * block_size;
			block_vertices.extend_from_slice(&[
				BlockVertex { pos: [p0.x, p1.y], tex_coords: [0.0, 0.0] },
				BlockVertex { pos: [p1.x, p1.y], tex_coords: [rect_size.x * block_size, 0.0] },
				BlockVertex { pos: [p0.x, p0.y], tex_coords: [0.0, rect_size.y * block_size] },
				BlockVertex { pos: [p1.x, p0.y], tex_coords: [rect_size.x * block_size, rect_size.y * block_size] },
			]);
		}

		let border_rects: Vec<(u8, Rect)> = mesh::section_to_rects(size, border);
		let mut processed_rects = Vec::new();
		let mut border_vertices = Vec::with_capacity(border_rects.len() * 8 * 4); // At most 8 borders per block, 4 vertices per border rectangle
		for (border, rect) in border_rects {
			let rect_size = (rect.1 - rect.0).as_vec2();
			let p0 = rect.0.as_vec2() * block_size + off;
			let p1 = p0 + rect_size * block_size;

			let border = Border::new(border);

			for (i, corner) in border.get_corners().iter().enumerate() {
				if let Some(corner) = corner {
					let (mut p0, mut p1) = (p0, p1);
					let pos = CORNER_I_TO_POS[i];

					for i in 0..2 {
						if pos[i] == 1 {
							p0[i] = p1[i] - BORDER_WIDTH;
						} else {
							debug_assert_eq!(pos[i], -1);
							p1[i] = p0[i] + BORDER_WIDTH;
						}
					}

					let points = [Vec2::ZERO, Vec2::X, Vec2::Y, Vec2::ONE];
					let grad_type = match corner {
						BorderCorner::External => 0.0,
						BorderCorner::Internal => 1.0,
					};

					let grad_points: [Vec2; 4] = array::from_fn(|j| points[j ^ i]);
					processed_rects.push((p0, p1, grad_points, grad_type));
				}
			}

			for (i, edge) in border.get_edges().iter().enumerate() {
				if let Some(edge) = edge {
					let (mut p0, mut p1) = (p0, p1);
					let pos = EDGE_I_TO_POS[i];

					let component = (pos.x == 0) as usize;
					if pos[component] == 1 {
						p0[component] = p1[component] - BORDER_WIDTH;
					} else {
						p1[component] = p0[component] + BORDER_WIDTH;
						debug_assert_eq!(pos[component], -1);
					}

					let other_component = component ^ 1;
					if edge.negative_corner() {
						p0[other_component] += BORDER_WIDTH;
					}
					if edge.positive_corner() {
						p1[other_component] -= BORDER_WIDTH;
					}

					let points = if component == 0 {
						[Vec2::ZERO, Vec2::X, Vec2::ZERO, Vec2::X] // Horizontal
					} else {
						[Vec2::ZERO, Vec2::ZERO, Vec2::X, Vec2::X] // Vertical
					};

					let flip = if pos[component] == 1 { 3 } else { 0 };
					let grad_points: [Vec2; 4] = array::from_fn(|j| points[j ^ flip]);
					processed_rects.push((p0, p1, grad_points, 0.0));
				}
			}

			for (p0, p1, grad_pos, grad_type) in mem::take(&mut processed_rects) {
				border_vertices.extend_from_slice(&[
					BorderVertex { pos: [p0.x, p0.y], grad_pos: grad_pos[0].to_array(), grad_type },
					BorderVertex { pos: [p1.x, p0.y], grad_pos: grad_pos[1].to_array(), grad_type },
					BorderVertex { pos: [p0.x, p1.y], grad_pos: grad_pos[2].to_array(), grad_type },
					BorderVertex { pos: [p1.x, p1.y], grad_pos: grad_pos[3].to_array(), grad_type },
				]);
			}
		}

		Some(Section {
			block_vbo: VertexBuffer::immutable(display, &block_vertices).unwrap(),
			border_vbo: VertexBuffer::immutable(display, &border_vertices).unwrap(),
		})
	}

	fn get_visible_sections(&self, blocks: &Blocks, camera: &Camera) -> Option<(UVec2, UVec2)> {
		let block_size = blocks.get_block_size().get() as f32;
		let pos = camera.get_pos() + block_size * blocks.get_grid_size().as_vec2() / 2.0;
		let size = camera.get_size();

		let mut min = ((pos - size / 2.0) / (block_size * SECTION_SIZE as f32)).floor().as_ivec2();
		let mut max = ((pos + size / 2.0) / (block_size * SECTION_SIZE as f32)).floor().as_ivec2();

		for i in 0..2 {
			min[i] = min[i].max(0);
			max[i] = max[i].min(self.section_grid_size[i] as i32 - 1);

			if min[i] as u32 >= self.section_grid_size[i] || max[i] < 0 { return None; }
		}

		Some((min.as_uvec2(), max.as_uvec2()))
	}
}
