use anyhow::Result;

use opengl_rust::{
	prelude::*,
	physics::Triangle,
};

pub struct LoadedLevel {
	pub player_spawn: Vec3,
	pub camera: Camera,
	pub phys_tris: Vec <Triangle>,
	pub buffer: Vec <u8>,
	pub level: gltf::Document,
}

impl LoadedLevel {
	pub fn from_path (path: &str) -> Result <Self> {
		use gltf::{
			Semantic,
		};
		
		let (level, buffers, _) = gltf::import (path)?;
		let buffer = match buffers.get (0) {
			None => bail! ("gltf didn't load any buffers"),
			Some (x) => x.0.to_vec (),
		};
		
		let scene = match level.scenes ().next () {
			None => bail! ("No scenes in glTF file"),
			Some (x) => x,
		};
		
		let mut player_spawn = None;
		let mut camera = None;
		let mut phys_tris: Vec <opengl_rust::physics::Triangle> = Vec::new ();
		
		for node in scene.nodes () {
			if node.camera ().is_some () {
				camera = Some (Camera {
					transform: node.transform ().clone (),
				});
			}
			
			if node.name () == Some ("Player Spawn") {
				let (translation, _, _) = node.transform ().decomposed ();
				player_spawn = Some (Vec3::from (translation));
			}
			else if let Some (_camera) = node.camera () {
				
			}
			else if let Some (mesh) = node.mesh () {
				let m = gltf_node_get_mat4 (&node);
				
				for (_, prim) in mesh.primitives ().enumerate () {
					let positions = match prim.get (&Semantic::Positions) {
						None => continue,
						Some (x) => x,
					};
					let normals = match prim.get (&Semantic::Normals) {
						None => continue,
						Some (x) => x,
					};
					let indices = match prim.indices () {
						None => continue,
						Some (x) => x,
					};
					let pos_view = match positions.view () {
						None => continue,
						Some (x) => x,
					};
					let _norm_view = match normals.view () {
						None => continue,
						Some (x) => x,
					};
					let indices_view = match indices.view () {
						None => continue,
						Some (x) => x,
					};
					
					let idx_start = indices.offset () + indices_view.offset ();
					let idx_slice = &buffer [idx_start..idx_start + indices.count () * 2];
					let vert_start = positions.offset () + pos_view.offset ();
					let vert_slice = &buffer [vert_start..vert_start + positions.count () * 4 * 3];
					
					for chunk in idx_slice.chunks_exact (2 * 3) {
						let read_idx = |i: usize| u16::from_le_bytes ([chunk [i * 2 + 0], chunk [i * 2 + 1]]);
						
						let idxs = [
							read_idx (0),
							read_idx (1),
							read_idx (2),
						];
						
						let read_pos_coord = |i| f32::from_le_bytes ([
							vert_slice [i * 4 + 0],
							vert_slice [i * 4 + 1],
							vert_slice [i * 4 + 2],
							vert_slice [i * 4 + 3],
						]);
						
						let read_pos = move |i| {
							let i = usize::try_from (i).unwrap ();
							m.transform_point3 (Vec3::new (
								read_pos_coord (i * 3 + 0),
								read_pos_coord (i * 3 + 1),
								read_pos_coord (i * 3 + 2),
							))
						};
						
						// glTF triangle winding is backwards from what I expected
						// no biggie
						let verts = [
							read_pos (idxs [0]),
							read_pos (idxs [2]),
							read_pos (idxs [1]),
						];
						
						phys_tris.push (opengl_rust::physics::Triangle {
							verts,
						});
					}
				}
			}
		}
		
		let player_spawn = match player_spawn {
			None => bail! ("glTF file must have `Player Spawn` node"),
			Some (x) => x,
		};
		
		let camera = match camera {
			None => bail! ("Couldn't find camera node in glTF file"),
			Some (x) => x,
		};
		
		Ok (Self {
			buffer,
			camera,
			level,
			phys_tris,
			player_spawn,
		})
	}
}

pub struct Camera {
	pub transform: gltf::scene::Transform,
}

pub fn gltf_node_get_mat4 (node: &gltf::Node) -> Mat4 {
	Mat4::from_cols_array_2d (&node.transform ().matrix ())
}
