use nom::{
	IResult,
	bytes::complete::{tag},
	number::complete::{le_u32},
};

use std::convert::TryInto;

pub mod consts {
use iota::iota;
iota! {
	pub const VERSION: usize = iota;
			, FILESIZE
			, FLAGS
			, NUM_TEXT
			, OFS_TEXT
			, NUM_MESHES
			, OFS_MESHES
			, NUM_VERTEXARRAYS
			, NUM_VERTEXES
			, OFS_VERTEXARRAYS
			, NUM_TRIANGLES
			, OFS_TRIANGLES
			, OFS_ADJACENCY
			, NUM_JOINTS
			, OFS_JOINTS
			, NUM_POSES
			, OFS_POSES
			, NUM_ANIMS
			, OFS_ANIMS
			, NUM_FRAMES
			, NUM_FRAMECHANNELS
			, OFS_FRAMES
			, OFS_BOUNDS
			, NUM_COMMENT
			, OFS_COMMENT
			, NUM_EXTENSIONS
			, OFS_EXTENSIONS
}
}

pub mod types {
iota! {
	pub const POSITION: usize = iota;
			, TEXCOORD
			, NORMAL
			, TANGENT
			, BLENDINDEXES
			, BLENDWEIGHTS
			, COLOR
}
	pub const CUSTOM: usize       = 0x10;
}

pub mod formats {
iota! {
	pub const BYTE: u32 = iota;
			, UBYTE
			, SHORT
			, USHORT
			, INT
			, UINT
			, HALF
			, FLOAT
			, DOUBLE
}
}

#[derive (Debug)]
pub struct Mesh {
	pub name: u32,
	pub material: u32,
	pub first_vertex: u32,
	pub num_vertexes: u32,
	pub first_triangle: u32,
	pub num_triangles: u32,
}

#[derive (Debug)]
pub struct Header {
	pub fields: [u32; 27],
}

#[derive (Debug)]
pub struct VertexArray {
	va_type: u32,
	va_flags: u32,
	va_format: u32,
	va_size: u32,
	va_offset: u32,
}

#[derive (Debug)]
pub struct Model <'a> {
	data: &'a [u8],
	
	pub header: Header,
	text: Vec <u8>,
	pub meshes: Vec <Mesh>,
	vertexarrays: Vec <VertexArray>,
}

impl Header {
	pub fn from_slice (input: &[u8]) -> IResult <&[u8], Header> {
		let (input, _) = tag (b"INTERQUAKEMODEL\0")(input)?;
		let (input, version) = le_u32 (input)?;
		
		// I only know how to parse version 2
		assert_eq! (version, 2);
		
		let mut input = input;
		let mut fields = [0; 27];
		fields [0] = version;
		
		for field in fields.iter_mut ().skip (1) {
			let (i, h) = le_u32 (input)?;
			input = i;
			*field = h;
		}
		
		Ok ((input, Header {
			fields,
		}))
	}
}

impl Mesh {
	pub fn from_slice (input: &[u8]) -> IResult <&[u8], Mesh> {
		let mut result = Mesh {
			name: 0,
			material: 0,
			first_vertex: 0,
			num_vertexes: 0,
			first_triangle: 0,
			num_triangles: 0,
		};
		
		let mut input = input;
		for field in [
			&mut result.name,
			&mut result.material,
			&mut result.first_vertex,
			&mut result.num_vertexes,
			&mut result.first_triangle,
			&mut result.num_triangles,
		].iter_mut () {
			let (i, f) = le_u32 (input)?;
			input = i;
			**field = f;
		}
		
		Ok ((input, result))
	}
}

impl VertexArray {
	pub fn from_slice (input: &[u8]) -> IResult <&[u8], VertexArray> {
		let mut result = VertexArray {
			va_type: 0,
			va_flags: 0,
			va_format: 0,
			va_size: 0,
			va_offset: 0,
		};
		
		let mut input = input;
		for field in [
			&mut result.va_type,
			&mut result.va_flags,
			&mut result.va_format,
			&mut result.va_size,
			&mut result.va_offset,
		].iter_mut () {
			let (i, f) = le_u32 (input)?;
			input = i;
			**field = f;
		}
		
		Ok ((input, result))
	}
}

#[derive (Debug)]
pub enum ModelLoadErr {
	ParseHeaderFailed,
	ParseMeshFailed (usize),
	ParseVertexArrayFailed (usize),
}

impl <'a> Model <'a> {
	pub fn from_slice (data: &'a [u8]) -> Result <Model <'a>, ModelLoadErr> {
		let header = match Header::from_slice (data) {
			Ok ((_, h)) => h,
			Err (_) => return Err (ModelLoadErr::ParseHeaderFailed),
		};
		
		let text = {
			let offset: usize = header.fields [consts::OFS_TEXT].try_into ().unwrap ();
			let num: usize = header.fields [consts::NUM_TEXT].try_into ().unwrap ();
			Vec::from (&data [offset..offset + num])
		};
		
		let meshes = {
			let num: usize = header.fields [consts::NUM_MESHES].try_into ().unwrap ();
			let mut meshes = Vec::with_capacity (num);
			let mesh_size = 6 * 4;
			let meshes_offset: usize = header.fields [consts::OFS_MESHES].try_into ().unwrap ();
			
			for i in 0..num {
				let offset = meshes_offset + i * mesh_size;
				let mesh_slice = &data [offset..offset + mesh_size];
				
				let mesh = match Mesh::from_slice (mesh_slice) {
					Ok ((_, m)) => m,
					Err (_) => return Err (ModelLoadErr::ParseMeshFailed (i)),
				};
				
				meshes.push (mesh);
			}
			meshes
		};
		
		let vertexarrays = {
			let num: usize = header.fields [consts::NUM_VERTEXARRAYS].try_into ().unwrap ();
			let mut vertexarrays = Vec::with_capacity (num);
			let vertexarray_size = 5 * 4;
			let vertexarrays_offset: usize = header.fields [consts::OFS_VERTEXARRAYS].try_into ().unwrap ();
			
			for i in 0..num {
				let offset = vertexarrays_offset + i * vertexarray_size;
				let vertexarray_slice = &data [offset..offset + vertexarray_size];
				
				let vertexarray = match VertexArray::from_slice (vertexarray_slice) {
					Ok ((_, va)) => va,
					Err (_) => return Err (ModelLoadErr::ParseVertexArrayFailed (i)),
				};
				
				vertexarrays.push (vertexarray);
			}
			
			vertexarrays
		};
		
		Ok (Model {
			data,
			
			header,
			text,
			meshes,
			vertexarrays,
		})
	}
	
	pub fn get_vertex_slice (&self, 
		vertexarray_index: usize
	) -> &[u8]
	{
		let vertexarray = &self.vertexarrays [vertexarray_index];
		let bytes_per_float = 4;
		let stride = bytes_per_float * vertexarray.va_size;
		//assert_eq! (stride, 12);
		
		let offset: usize = (vertexarray.va_offset).try_into ().unwrap ();
		let num_bytes: usize = (stride * self.header.fields [consts::NUM_VERTEXES]).try_into ().unwrap ();
		
		&self.data [offset..offset + num_bytes]
	}
	
	pub fn get_index_slice (&self, mesh_index: usize) -> &[u8] {
		let mesh = &self.meshes [mesh_index];
		let bytes_per_u32 = 4;
		let indexes_per_tri = 3;
		let stride = bytes_per_u32 * indexes_per_tri;
		
		let offset: usize = (self.header.fields [consts::OFS_TRIANGLES] + stride * mesh.first_triangle).try_into ().unwrap ();
		
		let num_bytes: usize = (stride * mesh.num_triangles).try_into ().unwrap ();
		
		&self.data [offset..offset + num_bytes]
	}
	
	pub fn get_all_indexes (&self) -> &[u8] {
		let bytes_per_u32 = 4;
		let indexes_per_tri = 3;
		let stride = bytes_per_u32 * indexes_per_tri;
		
		let offset: usize = self.header.fields [consts::OFS_TRIANGLES].try_into ().unwrap ();
		
		let num_bytes: usize = (stride * self.header.fields [consts::NUM_TRIANGLES]).try_into ().unwrap ();
		
		&self.data [offset..offset + num_bytes]
	}
	
	// I don't think IQM makes any guarantees about UTF-8
	// so I will only say that the slice has no NULs
	
	pub fn get_mesh_name (&self, index: usize) -> &[u8] {
		let mesh = &self.meshes [index];
		
		let ofs: usize = (self.header.fields [consts::OFS_TEXT] + mesh.name).try_into ().unwrap ();
		
		// There should be an easy way to do this with CString?
		let mut nul_index = None;
		for (j, c) in self.data [ofs..].iter ().enumerate () {
			if *c == 0 {
				nul_index = Some (j);
				break;
			}
		}
		let nul_index = nul_index.unwrap ();
		
		&self.data [ofs..ofs + nul_index]
	}
}
