use super::meshes;
use crate::{
    glm, Camera, GBuf, IndexBuf, ShaderBuilder, ShaderProgram, Texture, Transform, VertArray,
    VertBasic, VertBuf, VertTrans,
};
use gl;
use rayon::prelude::*;
#[allow(dead_code)]
pub struct Renderer {
    cube_shader: ShaderProgram,
    cube_vao: VertArray,
    cube_vbo: VertBuf<VertBasic>,
    cube_trans_vbo: VertBuf<VertTrans>,
    cube_tex: Texture,
    light_shader: ShaderProgram,
    light_vao: VertArray,
    light_vbo: VertBuf<VertBasic>,
    light_trans_vbo: VertBuf<VertTrans>,
    g_buf: GBuf,
    lit_def_geo: ShaderProgram,
    lit_def_light: ShaderProgram,
    ndc_quad_vbo: VertBuf<VertBasic>,
    ndc_quad_vao: VertArray,
    deferred: bool,
}
impl Renderer {
    pub fn new(resolution: (u32, u32), max_cubes: usize, max_lights: usize) -> Self {
        gl_call!(gl::Enable(gl::DEPTH_TEST));
        let cube_shader = ShaderBuilder::new(
            include_str!("shaders/lit_for.vert"),
            include_str!("shaders/lit_for.frag"),
        )
        .with_float4("u_color", glm::vec4(1.0, 1.0, 1.0, 1.0))
        .build();
        let img_path = crate::assets_path().join("tile_bookcaseFull.png");
        let cube_tex = Texture::new(&img_path);
        
        let cube_vbo = VertBuf::<VertBasic>::new(meshes::cube::tex_cube_verts());
        let cube_trans_vbo = VertBuf::<VertTrans>::new(Vec::with_capacity(max_cubes));
        let ibo = IndexBuf::new(meshes::cube::tex_cube_inds());
        let cube_vao = VertArray::new(&[&cube_vbo, &cube_trans_vbo], ibo);
        let light_shader = ShaderBuilder::new(
            include_str!("shaders/unlit_for.vert"),
            include_str!("shaders/unlit_for.frag"),
        )
        .with_float4("u_color", glm::vec4(1.0, 1.0, 1.0, 1.0))
        .build();
        
        let light_vbo = VertBuf::<VertBasic>::new(meshes::cube::tex_cube_verts());
        let light_trans_vbo = VertBuf::<VertTrans>::new(Vec::with_capacity(max_lights));
        let ibo = IndexBuf::new(meshes::cube::tex_cube_inds());
        let light_vao = VertArray::new(&[&light_vbo, &light_trans_vbo], ibo);
        let lit_def_geo = ShaderBuilder::new(
            include_str!("shaders/lit_def_geo.vert"),
            include_str!("shaders/lit_def_geo.frag"),
        )
        .with_float4("u_color", glm::vec4(1.0, 1.0, 1.0, 1.0))
        .build();
        let lit_def_light = ShaderBuilder::new(
            include_str!("shaders/lit_def_light.vert"),
            include_str!("shaders/lit_def_light.frag"),
        )
        .build();
        lit_def_light.set_int("u_tex_pos", 0);
        lit_def_light.set_int("u_tex_norm", 1);
        lit_def_light.set_int("u_tex_alb_spec", 2);
        let ndc_quad_vbo = VertBuf::new(meshes::quad::ndc_quad_verts());
        let ndc_quad_vao = VertArray::new(&[&ndc_quad_vbo], IndexBuf::new(vec![]));
        Renderer {
            cube_shader,
            cube_vao,
            cube_vbo,
            cube_trans_vbo,
            cube_tex,
            light_shader,
            light_vao,
            light_vbo,
            light_trans_vbo,
            g_buf: GBuf::new(resolution.0, resolution.1),
            lit_def_geo,
            lit_def_light,
            ndc_quad_vbo,
            ndc_quad_vao,
            deferred: false,
        }
    }
    pub fn deferred(&self) -> bool {
        self.deferred
    }
    pub fn set_deferred(&mut self, val: bool) {
        self.deferred = val;
    }
    pub fn cube_shader(&self) -> &ShaderProgram {
        &self.cube_shader
    }
    pub fn clear(&self) {
        
        
        
        
        
        
        gl_call!(gl::ClearColor(0.0, 0.0, 0.0, 1.0));
        gl_call!(gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT));
    }
    pub fn resize(&mut self, width: u32, height: u32) {
        self.g_buf.resize(width, height)
    }
    pub fn handle_event(&mut self, event: &glutin::event::Event<()>) {
        match event {
            glutin::event::Event::WindowEvent {
                event: glutin::event::WindowEvent::Resized(physical_size),
                ..
            } => self.resize(physical_size.width, physical_size.height),
            _ => (),
        }
    }
    pub fn begin_draw(&self, camera: &Camera) {
        let vp_mat = camera.view_projection_matrix();
        self.light_shader.set_mat4("u_view_projection", &vp_mat);
        self.cube_shader.set_mat4("u_view_projection", &vp_mat);
        self.cube_shader.set_float3("u_view_pos", &camera.position);
        self.lit_def_geo.set_mat4("u_view_projection", &vp_mat);
        self.lit_def_light
            .set_float3("u_view_pos", &camera.position);
    }
    pub fn end_draw(&mut self) {
        self.clear();
        if self.deferred {
            self.draw_cubes_def();
        } else {
            self.draw_cubes();
        }
        self.draw_lights();
    }
    pub fn set_vert_trans(vertices: &mut Vec<VertTrans>, transforms: &[Transform]) {
        vertices.resize_with(transforms.len(), std::default::Default::default);
        vertices
            .par_iter_mut()
            .zip(transforms.par_iter())
            .for_each(|(v, t)| v.set(t));
    }
    
    #[deprecated]
    pub fn set_vert_trans2(vertices: &mut Vec<VertTrans>, transforms: &[Transform]) {
        vertices.clear();
        vertices.append(
            &mut transforms
                .par_iter()
                .map(VertTrans::from_transform)
                .collect(),
        );
    }
    pub fn set_cubes(&mut self, transforms: &[Transform]) {
        let vertices = self.cube_trans_vbo.vertices_mut();
        Renderer::set_vert_trans(vertices, transforms);
    }
    pub fn set_lights(&mut self, transforms: &[Transform]) {
        let vertices = self.light_trans_vbo.vertices_mut();
        Renderer::set_vert_trans(vertices, transforms);
        transforms.iter().enumerate().for_each(|(i, t)| {
            let name = format!("u_point_lights[{}].position", i);
            self.cube_shader.set_float3(&name, &t.position);
            self.lit_def_light.set_float3(&name, &t.position);
        });
        self.cube_shader
            .set_int("u_point_lights_count", transforms.len() as i32);
        self.lit_def_light
            .set_int("u_point_lights_count", transforms.len() as i32);
    }
    fn draw_cubes(&self) {
        self.cube_trans_vbo.set_data();
        self.cube_shader.bind();
        self.cube_tex.bind();
        self.cube_vao.bind();
        gl_call!(gl::DrawElementsInstanced(
            gl::TRIANGLES,
            self.cube_vao.index_buf().len() as i32,
            gl::UNSIGNED_INT,
            std::ptr::null(),
            self.cube_trans_vbo.vertices().len() as i32,
        ));
        self.cube_vao.unbind();
        self.cube_tex.unbind();
        self.cube_shader.unbind();
    }
    fn draw_lights(&self) {
        self.light_trans_vbo.set_data();
        self.light_shader.bind();
        self.light_vao.bind();
        gl_call!(gl::DrawElementsInstanced(
            gl::TRIANGLES,
            self.light_vao.index_buf().len() as i32,
            gl::UNSIGNED_INT,
            std::ptr::null(),
            self.light_trans_vbo.vertices().len() as i32,
        ));
        self.light_vao.unbind();
        self.light_shader.unbind();
    }
    fn draw_cubes_def(&mut self) {
        self.cube_trans_vbo.set_data();
        
        self.g_buf.bind();
        {
            gl_call!(gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT));
            self.lit_def_geo.bind();
            self.cube_tex.bind();
            self.cube_vao.bind();
            gl_call!(gl::DrawElementsInstanced(
                gl::TRIANGLES,
                self.cube_vao.index_buf().len() as i32,
                gl::UNSIGNED_INT,
                std::ptr::null(),
                self.cube_trans_vbo.vertices().len() as i32,
            ));
            self.cube_vao.unbind();
            self.cube_tex.unbind();
            self.lit_def_geo.unbind();
        }
        self.g_buf.unbind();
        
        gl_call!(gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT));
        {
            self.lit_def_light.bind();
            self.g_buf.bind_bufs();
            self.ndc_quad_vao.bind();
            gl_call!(gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4));
            self.ndc_quad_vao.unbind();
            self.g_buf.unbind_bufs();
            self.lit_def_light.unbind();
        }
        
        self.g_buf.blit_depth();
    }
    pub fn draw_quad(&mut self, _transform: &Transform) {
        todo!()
    }
    pub fn draw_triangle(&mut self, _transform: &Transform) {
        todo!()
    }
}