use sge_vectors::Mat4;
use glium::{DrawParameters, Surface};
use sge_camera::get_camera_3d;
use sge_config::get_dithering;
use sge_debugging::*;
use sge_math::transform::Transform3D;
use sge_rng::rand;
use sge_time::delta_time;
use sge_time::time;
use sge_window::window_size;

use crate::materials::Material;
use crate::object_3d::Object3D;
use crate::object_3d::ObjectToDraw;

pub struct DrawQueue3D {
    pub(crate) objects: Vec<ObjectToDraw>,
}

impl DrawQueue3D {
    pub fn empty() -> Self {
        Self { objects: vec![] }
    }

    pub fn draw<T: Surface>(&mut self, frame: &mut T, view_proj: &Mat4) {
        let params = DrawParameters {
            blend: glium::Blend::alpha_blending(),
            depth: glium::Depth {
                test: glium::DepthTest::IfLess,
                write: true,
                ..Default::default()
            },
            dithering: get_dithering(),
            ..Default::default()
        };

        let delta_time = delta_time();
        let time = time();
        let random_number: f32 = rand();
        let screen_size = window_size();

        let set_common_uniforms = |material: &mut Material, mut transform: Transform3D| {
            material.set_mat4("view_proj_matrix", *view_proj);
            material.set_mat4("model_matrix", transform.matrix());
            material.set_mat3("normal_matrix", transform.into_normal_matrix());
            material.set_float("time", time);
            material.set_float("delta_time", delta_time);
            material.set_float("random", random_number);
            material.set_vec2("screen_size", screen_size);
            material.set_vec3("camera_pos", get_camera_3d().eye());
        };

        let draw_object = |frame: &mut T, object: &mut Object3D, transform: Transform3D| {
            let material = object.material.get_mut();
            set_common_uniforms(material, transform);
            let program = material.program.get();

            let default_params = DrawParameters {
                backface_culling: object.transform.desired_culling_mode(object.flip_normals),
                ..params.clone()
            };
            let params = object
                .material
                .draw_param_overrides
                .as_ref()
                .unwrap_or(&default_params);

            debugger_add_vertices(object.mesh.vertices.len());
            debugger_add_indices(object.mesh.indices.len());
            debugger_add_drawn_objects(1);
            debugger_add_draw_calls(1);

            frame
                .draw(
                    &object.mesh.vertices,
                    &object.mesh.indices,
                    program,
                    material,
                    params,
                )
                .unwrap();
        };

        for object in std::mem::take(&mut self.objects).iter_mut() {
            match object {
                ObjectToDraw::Many { object, transforms } => {
                    let object = object.get_mut();
                    for transform in transforms.iter_mut() {
                        draw_object(frame, object, *transform);
                    }
                }
                ObjectToDraw::Single(object) => {
                    let object = object.get_mut();
                    let transform = object.transform;
                    draw_object(frame, object, transform);
                }
                ObjectToDraw::WithTransform(object, transform) => {
                    let object = object.get_mut();
                    draw_object(frame, object, *transform);
                }
            }
        }
    }
}
