// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::rc::Rc;

use egui::Rect;
use glium::{Surface, VertexBuffer, Frame, DrawParameters, index::{NoIndices, PrimitiveType}, uniforms::SamplerWrapFunction, uniform};
use glam::{Vec2, Mat4};
use winit::{event::WindowEvent, window::Window};

use crate::app::{App, player_gfx::{PlayerGfx, Instance}};
use crate::world::{colour::Colour, player::PlayerTexture};
use crate::utils::{maths::glam_fix::Fix, blending::ALPHA_BLENDING};

pub struct PlayerPreview {
	gfx: Rc<PlayerGfx>,
	i_vbo: VertexBuffer<Instance>,
	dir: Vec2,
	cursor_pos: Option<Vec2>,
}

pub struct PreviewInfo {
	pub rect: Rect,
	pub alpha: f32,
}

impl PlayerPreview {
	pub fn new(app: &mut App) -> PlayerPreview {
		PlayerPreview {
			gfx: app.player_gfx.get(&app.display, &app.fs),
			i_vbo: VertexBuffer::empty_persistent(&app.display, 1).unwrap(),
			dir: Vec2::X,
			cursor_pos: None,
		}
	}

	pub fn event(&mut self, event: &WindowEvent) {
		if let WindowEvent::CursorMoved { position, .. } = event {
			self.cursor_pos = Some(Vec2::new(position.x as f32, position.y as f32));
		}
	}

	pub fn render(&mut self, window: &Window, frame: &mut Frame, info: PreviewInfo, colour: Colour) {
		let scale_factor = window.scale_factor() as f32;
		let lwinsize = {
			let size = window.inner_size();
			Vec2::new(size.width as f32, size.height as f32) / scale_factor
		};

		let proj = Mat4::orthographic_lh(0.0, lwinsize.x, lwinsize.y, 0.0, 0.0, 1.0);

		let rect_size: [f32; 2] = info.rect.size().into();
		let scale = Mat4::from_scale((Vec2::from(rect_size) * 0.625).extend(1.0));

		let centre = Vec2::from_array(info.rect.center().into());
		let trans = Mat4::from_translation(centre.extend(0.0));

		let matrix = proj * trans * scale;

		if let Some(mut pos) = self.cursor_pos {
			pos /= scale_factor;

			/*
			 * It could be distracting if the player moves all the time while
			 * changing colour, so just move when intended (hovering over the
			 * square that the player's rendered to).
			 *
			 * Instead of capturing winit events, I could respond to the egui hover
			 * event but the player changes direction as if the window is dragged,
			 * when it shouldn't.
			 */
			if pos.x >= info.rect.left() && pos.x <= info.rect.right() && pos.y >= info.rect.top() && pos.y <= info.rect.bottom() && let Some(dir) = (pos - centre).try_normalise() {
				self.dir = dir;
			}
		}

		self.i_vbo.write(&[Instance { i_pos: [0.0, 0.0], i_dir: self.dir.into(), i_brightness: 1.0, i_colour: colour.into(), i_layer: PlayerTexture::Human.get_layer(), i_alpha: info.alpha }]);

		let uniforms = uniform! { u_matrix: matrix.to_cols_array_2d(), u_texture: self.gfx.textures.sampled().wrap_function(SamplerWrapFunction::Clamp) };
		let params = DrawParameters {
			blend: ALPHA_BLENDING,
			.. Default::default()
		};
		frame.draw((&self.gfx.v_vbo, self.i_vbo.per_instance().unwrap()), NoIndices(PrimitiveType::TrianglesList), &self.gfx.program, &uniforms, &params).unwrap();
	}
}
