use super::{policy::*, state::*, texture::TextureBinding};
use crate::uses::*;

#[macro_export]
macro_rules! Uniform {
	($b: ident, ($n: expr, $v: expr)) => {{
		const _ID: (u32, &str) = shader_use::id($n);
		let mut b = $b;
		b.Uniform(_ID, $v);
		b
	}};
}

#[macro_export]
macro_rules! Uniforms {
($s: ident, $(($n: expr, $v: expr)),+) => {{
	let b = $s.Bind();
	$(let b = Uniform!(b, ($n, $v));)+
	b
}};
}

pub trait UniformImpl {
	fn apply(&self, _: i32);
}
impl UniformImpl for [u32] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform1uiv, name, self);
	}
}
impl UniformImpl for [uVec2] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform2uiv, name, self);
	}
}
impl UniformImpl for [uVec3] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform3uiv, name, self);
	}
}
impl UniformImpl for [uVec4] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform4uiv, name, self);
	}
}
impl UniformImpl for [i32] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform1iv, name, self);
	}
}
impl UniformImpl for [iVec2] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform2iv, name, self);
	}
}
impl UniformImpl for [iVec3] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform3iv, name, self);
	}
}
impl UniformImpl for [iVec4] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform4iv, name, self);
	}
}
impl UniformImpl for [f32] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform1fv, name, self);
	}
}
impl UniformImpl for [Vec2] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform2fv, name, self);
	}
}
impl UniformImpl for [Vec3] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform3fv, name, self);
	}
}
impl UniformImpl for [Vec4] {
	fn apply(&self, name: i32) {
		uni(gl::Uniform4fv, name, self);
	}
}
impl UniformImpl for [Mat2] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix2fv, name, self);
	}
}
impl UniformImpl for [Mat3] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix3fv, name, self);
	}
}
impl UniformImpl for [Mat4] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix4fv, name, self);
	}
}
impl UniformImpl for [Mat2x3] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix2x3fv, name, self);
	}
}
impl UniformImpl for [Mat3x2] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix3x2fv, name, self);
	}
}
impl UniformImpl for [Mat2x4] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix2x4fv, name, self);
	}
}
impl UniformImpl for [Mat4x2] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix4x2fv, name, self);
	}
}
impl UniformImpl for [Mat3x4] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix3x4fv, name, self);
	}
}
impl UniformImpl for [Mat4x3] {
	fn apply(&self, name: i32) {
		uni_mat(gl::UniformMatrix4x3fv, name, self);
	}
}

fn uni<T, S>(f: unsafe fn(i32, i32, *const T), name: i32, slice: &[S]) {
	GLCheck!(f(name, i32::to(slice.len()), slice.as_ptr() as *const T));
}

fn uni_mat<S>(f: unsafe fn(i32, i32, GLbool, *const f32), name: i32, slice: &[S]) {
	GLCheck!(f(name, i32::to(slice.len()), gl::FALSE, slice.as_ptr() as *const f32));
}

pub trait UniformArgs {
	fn get(self, _: i32, _: &mut HashMap<i32, i32>);
}
impl<'l, T: TexType> UniformArgs for &TextureBinding<'l, T> {
	fn get(self, name: i32, tex_cache: &mut HashMap<i32, i32>) {
		let u = i32::to(self.u);
		let ent = tex_cache.entry(name).or_insert(-1);
		if *ent != u {
			DEBUG!("Updating GL tex {} to {} in shader {}", *ent, u, ShdProg::bound_obj());
			GLCheck!(gl::Uniform1i(name, u));
			*ent = u;
		}
	}
}
impl<T> UniformArgs for T
where
	[T]: UniformImpl,
{
	fn get(self, name: i32, _: &mut HashMap<i32, i32>) {
		[self].apply(name);
	}
}
impl<T> UniformArgs for &[T]
where
	[T]: UniformImpl,
{
	fn get(self, name: i32, _: &mut HashMap<i32, i32>) {
		self.apply(name);
	}
}
impl<T> UniformArgs for &Vec<T>
where
	[T]: UniformImpl,
{
	fn get(self, name: i32, _: &mut HashMap<i32, i32>) {
		self[..].apply(name);
	}
}
impl<T> UniformArgs for Vec<T>
where
	[T]: UniformImpl,
{
	fn get(self, name: i32, _: &mut HashMap<i32, i32>) {
		self[..].apply(name);
	}
}
//TODO Const Generics

pub mod uniforms_use {
	pub const fn id(s: &str) -> (u32, &str) {
		(super::chksum::const_fnv1(s.as_bytes()), s)
	}
}
