extern crate gl;

use std::collections::{HashMap, VecDeque};

use super::shader::{Shader, Program};
use crate::assets::loader::Loader;

pub type ShaderConfig = (String, gl::types::GLuint);
pub type ShaderLinkingConfig = (String, Vec<ShaderConfig>);

pub struct ShaderManager {
	gl: gl::Gl,
	fragment_shaders: HashMap<String, Shader>,
	vertex_shaders: HashMap<String, Shader>,
   programs: HashMap<String, Program>,
}

impl ShaderManager {
	pub fn new(gl: gl::Gl) -> ShaderManager {
		return ShaderManager {
			gl,
			fragment_shaders: HashMap::new(),
			vertex_shaders: HashMap::new(),
         programs: HashMap::new(),
		};
	}

	// loads the shaders from the assets folder and compiles it
	pub fn load_shaders(&mut self, shaders: Vec<ShaderConfig>, loader: Loader) {
		for shader_information in shaders {
			let shader: Shader;
			if let Ok(s) = Shader::new(
				&self.gl,
				&loader,
				&shader_information.0,
				shader_information.1,
			) {
				shader = s;
			} else {
				panic!("Shader \"{}\" not foundb", shader_information.0);
			}
			match shader_information.1 {
				gl::VERTEX_SHADER => {
					self.vertex_shaders.insert(shader_information.0, shader);
				}
				gl::FRAGMENT_SHADER => {
					self.fragment_shaders.insert(shader_information.0, shader);
				}
				_ => {}
			}
		}
	}

	pub fn link_shaders(&mut self, links: Vec<ShaderLinkingConfig>) {
		for shaders_used in links {
			let mut shaders = VecDeque::new();

			for shader_used in shaders_used.1 {
				match shader_used.1 {
					gl::VERTEX_SHADER => {
						if let Some(shader) = self.vertex_shaders.get(&shader_used.0) {
							shaders.push_back(shader);
						} else {
							panic!("Vertexshader \"{}\" not found", shader_used.0);
						}
					}
					gl::FRAGMENT_SHADER => {
						if let Some(shader) = self.fragment_shaders.get(&shader_used.0) {
							shaders.push_back(shader);
						} else {
							panic!("Fragmentshader \"{}\" not found", shader_used.0);
						}
					}
					_ => {}
				}
			}

         if let Ok(program) = Program::from_shaders(&self.gl, &shaders) {
            self.programs.insert(shaders_used.0, program);
         } else {
            panic!("Failed to link program \"{}\"", shaders_used.0);
         }

		}
	}

   pub fn get_program(&self, programe_name: String) -> Option<&Program> {
      return self.programs.get(&programe_name);
   }
}
