use std::collections::*;
use std::iter::FromIterator;

use crate::{
	renderable_model::AttrMap,
	shader::ShaderProgram,
};

pub struct ShaderClosure {
	program: ShaderProgram,
	uniforms: HashMap <u32, i32>,
	attributes: AttrMap,
}

pub struct BorrowedShaderVars <'a> {
	pub unis: &'a HashMap <u32, i32>,
	pub attrs: &'a AttrMap,
}

pub fn get_shader_attrs (
	shader: &ShaderProgram,
	attr_names: &[(usize, &str)]
) 
-> AttrMap
{
	let mut v = vec! [None; attr_names.iter ().map (|(i, _)| i).max ().unwrap () + 1];
	
	let attrs = shader.get_attribute_locations (attr_names.iter ().map (|(_, v)| v).copied ());
	
	for ((i, _), loc) in attr_names.iter ().zip (attrs.into_iter ()) {
		v [*i] = loc;
	}
	
	v
}

pub fn get_shader_uniforms (
	shader: &ShaderProgram,
	uni_names: &[(u32, &str)]
)
-> HashMap <u32, i32>
{
	let unis = shader.get_uniform_locations (uni_names.iter ().map (|(_, v)| v).copied ());
	
	let pairs = 
	uni_names.iter ().map (|(i, _)| i).copied ()
	.zip (unis.into_iter ())
	.filter (|(_, v)| *v >= 0)
	;
	
	HashMap::from_iter (pairs)
}

impl ShaderClosure {
	pub fn new (
		program: ShaderProgram, 
		uniform_names: &[(u32, &str)],
		attr_names: &[(usize, &str)]
	) -> Self
	{
		let uniforms = get_shader_uniforms (&program, uniform_names);
		let attributes = get_shader_attrs (&program, attr_names);
		Self {
			program,
			uniforms,
			attributes,
		}
	}
	
	pub fn get_id (&self) -> u32 {
		self.program.get_id ()
	}
	
	pub fn with <F> (&self, previous_id: Option <u32>, callback: F)
	where F: Fn (BorrowedShaderVars)
	{
		if previous_id != Some (self.get_id ()) {
			self.program.use_program ();
		}
		else {
			//println! ("Elided use_program ()");
		}
		
		callback (BorrowedShaderVars {
			unis: &self.uniforms,
			attrs: &self.attributes,
		});
	}
}

pub trait ShaderLookup {
	fn lookup <'a> (&'a self, id: u32) -> &'a ShaderClosure;
}
