use std::{ffi::c_void, mem::size_of, sync::Arc};

use glam::{Mat4, Vec3, Vec4};
use rand::{thread_rng, Rng};

use metal::{Buffer, Device, NSRange, NSUInteger};

use super::{camera::Camera, geometry::*};

pub struct Scene {
    pub device: Device,
    pub camera: Camera,
    pub geometries: Vec<Arc<dyn Geometry>>,
    pub geometry_instances: Vec<Arc<GeometryInstance>>,
    pub lights: Vec<AreaLight>,
    pub lights_buffer: Buffer,
}

impl Scene {
    pub fn new(device: Device) -> Self {
        let mut geometries = Vec::<Arc<dyn Geometry>>::new();
        let mut light_mesh = TriangleGeometry::new(device.clone(), "light".to_string());
        let transform = Mat4::from_translation(Vec3::new(0.0, 1.0, 0.0))
            * Mat4::from_scale(Vec3::new(0.5, 1.98, 0.5));
        light_mesh.add_cube_with_faces(
            FACE_MASK_POSITIVE_Y,
            Vec3::new(1.0, 1.0, 1.0),
            transform,
            true,
        );
        light_mesh.upload_to_buffers();
        let light_mesh = Arc::new(light_mesh);
        geometries.push(light_mesh.clone());

        let mut geometry_mesh = TriangleGeometry::new(device.clone(), "geometry".to_string());
        let transform = Mat4::from_translation(Vec3::new(0.0, 1.0, 0.0))
            * Mat4::from_scale(Vec3::new(2.0, 2.0, 2.0));
        geometry_mesh.add_cube_with_faces(
            FACE_MASK_NEGATIVE_Y | FACE_MASK_POSITIVE_Y | FACE_MASK_NEGATIVE_Z,
            Vec3::new(0.725, 0.71, 0.68),
            transform,
            true,
        );
        geometry_mesh.add_cube_with_faces(
            FACE_MASK_NEGATIVE_X,
            Vec3::new(0.63, 0.065, 0.05),
            transform,
            true,
        );
        geometry_mesh.add_cube_with_faces(
            FACE_MASK_POSITIVE_X,
            Vec3::new(0.14, 0.45, 0.091),
            transform,
            true,
        );
        let transform = Mat4::from_translation(Vec3::new(-0.335, 0.6, -0.29))
            * Mat4::from_rotation_y(0.3)
            * Mat4::from_scale(Vec3::new(0.6, 1.2, 0.6));
        geometry_mesh.add_cube_with_faces(
            FACE_MASK_ALL,
            Vec3::new(0.725, 0.71, 0.68),
            transform,
            false,
        );
        geometry_mesh.upload_to_buffers();
        let geometry_mesh = Arc::new(geometry_mesh);
        geometries.push(geometry_mesh.clone());

        let mut sphere_geometry = SphereGeometry::new(device.clone());
        sphere_geometry.add_sphere_with_origin(
            Vec3::new(0.3275, 0.3, 0.3725),
            0.3,
            Vec3::new(0.725, 0.71, 0.68),
        );
        sphere_geometry.upload_to_buffers();
        let sphere_geometry = Arc::new(sphere_geometry);
        geometries.push(sphere_geometry.clone());

        let mut rng = thread_rng();
        let mut geometry_instances = Vec::new();
        let mut lights = Vec::new();
        for y in -1..2 {
            for x in -1..2 {
                let transform =
                    Mat4::from_translation(Vec3::new(x as f32 * 2.5, y as f32 * 2.5, 0.0));
                geometry_instances.push(Arc::new(GeometryInstance {
                    geometry: light_mesh.clone(),
                    transform,
                    mask: GEOMETRY_MASK_LIGHT,
                    index_in_scene: 0,
                }));
                geometry_instances.push(Arc::new(GeometryInstance {
                    geometry: geometry_mesh.clone(),
                    transform,
                    mask: GEOMETRY_MASK_TRIANGLE,
                    index_in_scene: 1,
                }));
                geometry_instances.push(Arc::new(GeometryInstance {
                    geometry: sphere_geometry.clone(),
                    transform,
                    mask: GEOMETRY_MASK_SPHERE,
                    index_in_scene: 2,
                }));
                lights.push(AreaLight {
                    position: Vec4::new(x as f32 * 2.5, y as f32 * 2.5 + 1.98, 0.0, 0.0),
                    forward: Vec4::new(0.0, -1.0, 0.0, 0.0),
                    right: Vec4::new(0.25, 0.0, 0.0, 0.0),
                    up: Vec4::new(0.0, 0.0, 0.25, 0.0),
                    colour: Vec4::new(
                        rng.gen_range(0f32..=1.0),
                        rng.gen_range(0f32..=1.0),
                        rng.gen_range(0f32..=1.0),
                        0.0,
                    ),
                });
            }
        }
        let lights_buffer = device.new_buffer_with_data(
            lights.as_ptr() as *const c_void,
            (lights.len() * size_of::<AreaLight>()) as NSUInteger,
            get_managed_buffer_storage_mode(),
        );
        lights_buffer.did_modify_range(NSRange::new(0, lights_buffer.length()));
        lights_buffer.set_label("lights buffer");

        Self {
            device,
            camera: Camera::new(),
            geometries,
            geometry_instances,
            lights,
            lights_buffer,
        }
    }
}
