use byteorder::{LittleEndian, ReadBytesExt};

use std::cmp;
use std::convert::TryInto;
use std::ffi::c_void;
use std::io::Cursor;

// Only contains f32 floats

pub struct VertexBuffer {
	id: u32,
	// Not bytes. 
	len: usize,
}

const FLOAT_SIZE: usize = 4;

impl VertexBuffer {
	// len is the number of floats
	
	fn allocate_buffer (len: usize) -> u32 {
		let mut id = 0;
		unsafe {
			gl::GenBuffers (1, &mut id);
			gl::BindBuffer (gl::ARRAY_BUFFER, id);
			
			// Khronos docs say that null is not derefed here
			gl::BufferData (
				gl::ARRAY_BUFFER, 
				(len * FLOAT_SIZE).try_into ().unwrap (), 
				std::ptr::null (), 
				gl::STATIC_DRAW
			);
		}
		assert! (id != 0);
		id
	}
	
	pub fn from_slices (slices: &[&[f32]]) -> Self {
		let len = slices.iter ()
		.map (|slice| slice.len ())
		.sum ();
		
		let id = Self::allocate_buffer (len);
		
		let mut offset = 0;
		for slice in slices.iter () {
			unsafe {
				gl::BufferSubData (
					gl::ARRAY_BUFFER, 
					(offset * FLOAT_SIZE).try_into ().unwrap (), 
					(len * FLOAT_SIZE).try_into ().unwrap (), 
					&slice [0] as *const f32 as *const c_void
				);
			}
			
			offset += slice.len ();
		}
		
		Self {
			id,
			len,
		}
	}
	
	pub fn from_slice (slice: &[f32]) -> Self {
		Self::from_slices (&[slice])
	}
	
	pub fn bind (&self) {
		unsafe {
			gl::BindBuffer (gl::ARRAY_BUFFER, self.id);
		}
	}
}
impl Drop for VertexBuffer {
	fn drop (&mut self) {
		if self.id == 0 {
			return;
		}
		
		unsafe {
			gl::DeleteBuffers (1, &self.id);
		}
		
		self.id = 0;
	}
}

pub struct IndexBuffer {
	id: u32,
	// Not bytes. Number of indexes.
	len: usize,
	max: u32,
}

impl IndexBuffer {
	pub fn from_slice (slice: &[u8]) -> Self {
		let mut rdr = Cursor::new (slice);
		
		let mut max = None;
		
		const IDX_SIZE: usize = 4;
		
		assert_eq! (slice.len () % IDX_SIZE, 0);
		
		for _ in 0..slice.len () / IDX_SIZE {
			let idx = rdr.read_u32::<LittleEndian> ().unwrap ();
			
			max = match max {
				None => Some (idx),
				Some (max) => Some (cmp::max (max, idx)),
			};
		}
		
		let id = {
			let mut id = 0;
			unsafe {
				gl::GenBuffers (1, &mut id);
				gl::BindBuffer (gl::ELEMENT_ARRAY_BUFFER, id);
				
				gl::BufferData (gl::ELEMENT_ARRAY_BUFFER, slice.len ().try_into ().unwrap (), &slice [0] as *const u8 as *const c_void, gl::STATIC_DRAW);
			}
			assert! (id != 0);
			id
		};
		
		Self {
			id,
			len: slice.len () / IDX_SIZE,
			max: max.unwrap (),
		}
	}
	
	pub fn bind (&self) {
		unsafe {
			gl::BindBuffer (gl::ELEMENT_ARRAY_BUFFER, self.id);
		}
	}
	
	pub fn max (&self) -> u32 {
		self.max
	}
}

impl Drop for IndexBuffer {
	fn drop (&mut self) {
		if self.id == 0 {
			return;
		}
		
		unsafe {
			gl::DeleteBuffers (1, &self.id);
		}
		
		self.id = 0;
	}
}
