use crate::renderer::pipelines::triangle_pipeline::shaders::vs;
use glam::{Mat3, Mat4, Vec3};
use std::error::Error;
use std::sync::Arc;
use std::time::Instant;
use vulkano::buffer::Subbuffer;
use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer};
use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint};

use crate::renderer::{pipelines::triangle_pipeline::create_triangle_pipeline, App, Vertex2D};

const VERTICES: [Vertex2D; 12] = [
    // Triangle en haut à gauche
    Vertex2D {
        position: [-0.5, -0.75],
        color: [1.0, 0.0, 0.0],
    },
    Vertex2D {
        position: [-0.75, -0.25],
        color: [0.0, 1.0, 0.0],
    },
    Vertex2D {
        position: [-0.25, -0.25],
        color: [0.0, 0.0, 1.0],
    },
    // Triangle en bas à gauche
    Vertex2D {
        position: [-0.5, 0.25],
        color: [0.5, 0.5, 0.5],
    },
    Vertex2D {
        position: [-0.75, 0.75],
        color: [0.2, 0.8, 0.2],
    },
    Vertex2D {
        position: [-0.25, 0.75],
        color: [0.8, 0.2, 0.2],
    },
    // Triangle en haut à droite
    Vertex2D {
        position: [0.5, -0.75],
        color: [1.0, 1.0, 0.0],
    },
    Vertex2D {
        position: [0.25, -0.25],
        color: [0.0, 1.0, 1.0],
    },
    Vertex2D {
        position: [0.75, -0.25],
        color: [1.0, 0.0, 1.0],
    },
    // Triangle en bas à droite
    Vertex2D {
        position: [0.5, 0.25],
        color: [0.1, 0.5, 0.8],
    },
    Vertex2D {
        position: [0.25, 0.75],
        color: [0.8, 0.6, 0.1],
    },
    Vertex2D {
        position: [0.75, 0.75],
        color: [0.3, 0.4, 0.6],
    },
];

pub struct Scene {
    pipeline: Arc<GraphicsPipeline>,
    vertex_buffer: Subbuffer<[Vertex2D]>,

    rotation_start: Instant,
}

impl Scene {
    pub fn load(app: &App) -> Result<Self, Box<dyn Error>> {
        let pipeline = create_triangle_pipeline(&app.device, &app.rcx.as_ref().unwrap().swapchain)?;
        let vertex_buffer =
            Vertex2D::create_buffer(Vec::from_iter(VERTICES), &app.memory_allocator)?;

        Ok(Scene {
            pipeline,
            vertex_buffer,
            rotation_start: Instant::now(),
        })
    }

    pub fn render(
        &self,
        app: &App,
        builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
    ) -> Result<(), Box<dyn Error>> {
        let vertex_count = self.vertex_buffer.len() as u32;
        let instance_count = vertex_count / 3;

        let uniform_buffer = self.get_uniform_buffer(app);
        let layout = &self.pipeline.layout().set_layouts()[0];
        let descriptor_set = DescriptorSet::new(
            app.descriptor_set_allocator.clone(),
            layout.clone(),
            [WriteDescriptorSet::buffer(0, uniform_buffer)],
            [],
        )
        .unwrap();

        unsafe {
            builder
                .bind_pipeline_graphics(self.pipeline.clone())?
                .bind_descriptor_sets(
                    PipelineBindPoint::Graphics,
                    self.pipeline.layout().clone(),
                    0,
                    descriptor_set,
                )?
                .bind_vertex_buffers(0, self.vertex_buffer.clone())?
                .draw(vertex_count, instance_count, 0, 0)?;
        }

        Ok(())
    }

    fn get_uniform_buffer(&self, app: &App) -> Subbuffer<vs::MVPData> {
        let swapchain = &app.rcx.as_ref().unwrap().swapchain;
        let elapsed = self.rotation_start.elapsed();
        let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0;
        let rotation = Mat3::from_rotation_y(rotation as f32);

        // NOTE: This teapot was meant for OpenGL where the origin is at the lower left
        // instead the origin is at the upper left in Vulkan, so we reverse the Y axis.
        let aspect_ratio = swapchain.image_extent()[0] as f32 / swapchain.image_extent()[1] as f32;

        let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0);
        let view = Mat4::look_at_rh(
            Vec3::new(0.3, 0.3, 1.0),
            Vec3::new(0.0, 0.0, 0.0),
            Vec3::new(0.0, -1.0, 0.0),
        );
        let scale = Mat4::from_scale(Vec3::splat(1.0));

        let uniform_data = vs::MVPData {
            world: Mat4::from_mat3(rotation).to_cols_array_2d(),
            view: (view * scale).to_cols_array_2d(),
            projection: proj.to_cols_array_2d(),
        };

        let buffer = app.uniform_buffer_allocator.allocate_sized().unwrap();
        *buffer.write().unwrap() = uniform_data;

        buffer
    }
}