use rand::Rng;
use ray_tracing_core::{light::AreaLight, prelude::*};
use ray_tracing_material::{diffuse::DiffuseMaterial, mirror::Mirror};
use std::cell::OnceCell;

use crate::{
    examples::ExampleSceneEnum,
    parse_obj::ObjData,
    triangle_bvh::{BVHMaterial, Triangle, TriangleBVH},
};

use super::ExampleScene;

pub struct Cornell2<R: Rng> {
    scene: OnceCell<TriangleBVH<R>>,
}

impl<R: Rng> Cornell2<R> {
    pub fn new() -> Self {
        Self {
            scene: OnceCell::new(),
        }
    }
}

impl<R: Rng> Default for Cornell2<R> {
    fn default() -> Self {
        Self::new()
    }
}

impl<R: Rng + 'static> ExampleScene<R> for Cornell2<R> {
    fn get_scene(&self) -> ExampleSceneEnum<R> {
        let s = self.scene.get_or_init(|| {
            let obj = ObjData::new("ray-tracing-scene/obj/cornell_box.obj").unwrap();

            let triangles = obj
                .triangle_faces()
                .map(|t| Triangle::new(t.v, t.mat.unwrap()))
                .collect();

            let materials = obj
                .materials()
                .iter()
                .map(|m| match m.name.as_str() {
                    "white" => BVHMaterial::new_material(DiffuseMaterial::new(Color::white())),
                    "light" => BVHMaterial::new_light(AreaLight::new(Color::white() * 30.0)),
                    "blue" => {
                        BVHMaterial::new_material(DiffuseMaterial::new(Color::new(0.0, 0.0, 1.0)))
                    }
                    "red" => {
                        BVHMaterial::new_material(DiffuseMaterial::new(Color::new(1.0, 0.0, 0.0)))
                    }
                    "green" => {
                        BVHMaterial::new_material(DiffuseMaterial::new(Color::new(0.0, 1.0, 0.0)))
                    }
                    "mirror" => BVHMaterial::new_material(Mirror::new(Color::white())),
                    _ => unreachable!(),
                })
                .collect();

            TriangleBVH::new(obj.vertices, triangles, materials)
        });

        ExampleSceneEnum::TriangleBVH(s.clone())
    }

    fn get_camera_pos(&self) -> Pos3 {
        Pos3::new(275.0, 275.0, -800.0)
    }

    fn get_camera_look_at(&self) -> Pos3 {
        Pos3::new(275.0, 275.0, 275.0)
    }

    fn get_camera_up(&self) -> Dir3 {
        Dir3::up()
    }

    fn get_horizontal_fov(&self) -> Float {
        Float::to_radians(90.0)
    }
}
