use super::ExampleScene;
use crate::{
    examples::ExampleSceneEnum,
    triangle_bvh::{BVHMaterial, Triangle, TriangleBVH},
};
use ray_tracing_core::{light::AreaLight, prelude::*};
use ray_tracing_material::{diffuse::DiffuseMaterial, mirror::Mirror};
use std::cell::OnceCell;

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

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

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

impl<R: Rng + 'static> ExampleScene<R> for BasicCornell<R> {
    fn get_scene(&self) -> ExampleSceneEnum<R> {
        let s = self.scene.get_or_init(|| {
            let side_length = 1.5;
            let light_size = 0.5;
            let light_offset = 0.01;
            let box_size = 0.3;
            TriangleBVH::new(
                vec![
                    Pos3::new(side_length, side_length, side_length),
                    Pos3::new(side_length, side_length, -side_length),
                    Pos3::new(side_length, -side_length, side_length),
                    Pos3::new(side_length, -side_length, -side_length),
                    Pos3::new(-side_length, side_length, side_length),
                    Pos3::new(-side_length, side_length, -side_length),
                    Pos3::new(-side_length, -side_length, side_length),
                    Pos3::new(-side_length, -side_length, -side_length),
                    Pos3::new(light_size, side_length - light_offset, light_size),
                    Pos3::new(light_size, side_length - light_offset, -light_size),
                    Pos3::new(-light_size, side_length - light_offset, light_size),
                    Pos3::new(-light_size, side_length - light_offset, -light_size),
                    Pos3::new(box_size, -side_length, 0.0),
                    Pos3::new(0.0, -side_length, -box_size),
                    Pos3::new(-box_size, -side_length, 0.0),
                    Pos3::new(0.0, -side_length, box_size),
                    Pos3::new(0.0, box_size - side_length, 0.0),
                ],
                vec![
                    Triangle::new([0, 1, 2], 0),
                    Triangle::new([1, 3, 2], 0),
                    Triangle::new([0, 4, 1], 0),
                    Triangle::new([1, 4, 5], 0),
                    Triangle::new([2, 3, 6], 0),
                    Triangle::new([3, 7, 6], 0),
                    Triangle::new([0, 2, 4], 1),
                    Triangle::new([6, 4, 2], 1),
                    Triangle::new([1, 5, 3], 2),
                    Triangle::new([7, 3, 5], 2),
                    Triangle::new([8, 10, 9], 3),
                    Triangle::new([11, 9, 10], 3),
                    Triangle::new([12, 13, 16], 4),
                    Triangle::new([13, 14, 16], 4),
                    Triangle::new([14, 15, 16], 4),
                    Triangle::new([15, 12, 16], 4),
                ],
                vec![
                    BVHMaterial {
                        material: Some(Box::new(DiffuseMaterial::new(Color::new(0.8, 0.8, 0.8)))),
                        light: None,
                    },
                    BVHMaterial {
                        material: Some(Box::new(DiffuseMaterial::new(Color::new(0.9, 0.0, 0.0)))),
                        light: None,
                    },
                    BVHMaterial {
                        material: Some(Box::new(DiffuseMaterial::new(Color::new(0.0, 0.9, 0.0)))),
                        light: None,
                    },
                    BVHMaterial {
                        material: None,
                        light: Some(Box::new(AreaLight::new(Color::white() * 18.0))),
                    },
                    BVHMaterial {
                        material: Some(Box::new(Mirror::new(Color::new(1.0, 1.0, 1.0)))),
                        light: None,
                    },
                ],
            )
        });

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

    fn get_camera_pos(&self) -> Pos3 {
        Pos3::new(-6.0, 0.0, 0.0)
    }

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

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

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