
use gl;
use std::fs;
use glm;
use cgmath::prelude::*;
    /// Shader Struct for creating and using shaders in the Context of engine
    ///
    pub struct shader {

        ID: gl::types::GLuint,
    }



    impl shader
        {
            /// Shader Constructor, will read, Compile and Create GLSL Program for use
            ///
            /// # Example
            /// `new(String::from("Example"))`
            /// if the String given was "Example" the Program expects both shaders to be in the directory "resources/shaders/Example/"
            /// with the vertex shader being called "shader.vert" and fragment "shader.frag"
            pub fn new(path: &str) -> shader
                {
                    //read file contents
                    let VERT_SHADER = fs::read_to_string(format!("resources/shaders/{path}/shader.vert")).unwrap();
                    let FRAG_SHADER = std::fs::read_to_string(format!("resources/shaders/{path}/shader.frag")).unwrap();

                    //create vertex shader
                    let vertex_shader = unsafe {gl::CreateShader(gl::VERTEX_SHADER) };
                    unsafe {
                    gl::ShaderSource(vertex_shader, 1, &VERT_SHADER.as_bytes().as_ptr().cast(), &VERT_SHADER.len().try_into().unwrap());
                    gl::CompileShader(vertex_shader);
                    }
                    //error Checking
                    let mut success = 0;
                    unsafe {
                    gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success);
                    if success == 0 {
                        let mut log_len = 0_i32;

                        let mut v: Vec<u8> = Vec::with_capacity(1024);
                        gl::GetShaderInfoLog(vertex_shader, 1024, &mut log_len, v.as_mut_ptr().cast());
                        v.set_len(log_len.try_into().unwrap());
                        panic!("Vertex Shader Compile Error: {}", String::from_utf8_lossy(&v));
                    }

                    let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
                    gl::ShaderSource(fragment_shader, 1, &FRAG_SHADER.as_bytes().as_ptr().cast(), &FRAG_SHADER.len().try_into().unwrap());
                    gl::CompileShader(fragment_shader);

                    let mut success = 0;
                    gl::GetShaderiv(fragment_shader, gl::COMPILE_STATUS, &mut success);
                    if success == 0 {
                        let mut v: Vec<u8> = Vec::with_capacity(1024);
                        let mut log_len = 0_i32;
                        gl::GetShaderInfoLog(fragment_shader, 1024, &mut log_len, v.as_mut_ptr().cast());
                        v.set_len(log_len.try_into().unwrap());
                        panic!("Fragment Shader Compile Error: {}", String::from_utf8_lossy(&v));
                    }

                    let shader_program = gl::CreateProgram();

                    gl::AttachShader(shader_program, vertex_shader);
                    gl::AttachShader(shader_program, fragment_shader);
                    gl::LinkProgram(shader_program);

                    let mut success = 0;
                    gl::GetProgramiv(shader_program, gl::LINK_STATUS, &mut success);
                    if success == 0 {
                        let mut v: Vec<u8> = Vec::with_capacity(1024);
                        let mut log_len = 0_i32;
                        gl::GetProgramInfoLog(shader_program, 1024, &mut log_len, v.as_mut_ptr().cast());
                        v.set_len(log_len.try_into().unwrap());
                        panic!("Program Link Error: {}", String::from_utf8_lossy(&v));
                    }

                    gl::DetachShader(shader_program, vertex_shader);
                    gl::DetachShader(shader_program, fragment_shader);
                    gl::DeleteShader(vertex_shader);
                    gl::DeleteShader(fragment_shader);



                        // return program
                    shader {
                        ID: shader_program,
                    }
                    }
                }
            /// uses Shader
            pub fn Use(&self)
            {
                unsafe {gl::UseProgram(self.ID);}
            }

            // BOILERPLATE JUMPSCARE
            pub fn setBool(&self, name: &str, value: bool )
                {unsafe {gl::Uniform1i(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), value as i32);}}
            pub fn setInt(&self, name: &str, value: u32 )
                {unsafe {gl::Uniform1i(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), value as i32);}}
            pub fn setFloat(&self, name: &str, value: f32 )
                {unsafe {gl::Uniform1f(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), value as f32);}}
            pub fn setVec2(&self, name: &str, value: &cgmath::Vector2<f32> )
                {unsafe {gl::Uniform2fv(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), 1, value.as_ptr());}}
            pub fn setVector2d(&self, name: &str, x: f32, y: f32)
                {unsafe {gl::Uniform2f(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), x, y);}}
            pub fn setVector3(&self, name: &str, value: cgmath::Vector3<f32> )
                {unsafe {gl::Uniform3fv(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), 1, value.as_ptr());}}
            pub fn setVector3d(&self, name: &str, x: f32, y: f32, z: f32 )
                {unsafe {gl::Uniform3f(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), x, y, z);}}
            pub fn setVector4(&self, name: &str, value: &cgmath::Vector4<f32> )
                {unsafe {gl::Uniform4fv(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), 1, value.as_ptr());}}
            pub fn setVector4d(&self, name: &str, x: f32, y: f32, z: f32, w: f32)
                {unsafe {gl::Uniform4f(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), x, y, z, w);}}
            pub fn setMat2(&self, name: &str, value: &cgmath::Matrix2<f32>)
                {unsafe {gl::UniformMatrix2fv(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), 1, gl::FALSE, value.as_ptr());}}
            pub fn setMat3(&self, name: &str, value: &cgmath::Matrix3<f32>)
                {unsafe {gl::UniformMatrix3fv(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), 1, gl::FALSE, value.as_ptr());}}
            pub fn setMat4(&self, name: &str, value: &cgmath::Matrix4<f32>)
                {unsafe {gl::UniformMatrix4fv(gl::GetUniformLocation(self.ID, name.as_ptr() as *const gl::types::GLchar), 1, gl::FALSE, value.as_ptr());}}


        }



#[cfg(test)]
  mod tests {
      use glfw;
      use glfw::Context;
      use gl;
      use super::*;

      #[test]
      fn ShaderLoading()  {

          // initialize GLFW
              let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
          // set window hints
          glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3));
          glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core));
          glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));
          glfw.window_hint(glfw::WindowHint::Resizable(false));

          //create Window
          let (mut window, events) = glfw.create_window(480, 320, "Shader Test", glfw::WindowMode::Windowed).unwrap();
          let (screen_width, screen_height) = window.get_framebuffer_size();

          // setup window
          window.make_current();
          window.set_key_polling(true);
          // initialize gl
          gl::load_with(|ptr| window.get_proc_address(ptr) as *const _);

          let Shader = shader::new("basic");
          Shader.Use();
      }
  }
