use crate::{
    examples::ExampleSceneEnum,
    parse_obj::ObjData,
    triangle_bvh::{BVHMaterial, Triangle, TriangleBVH},
};
use rand::Rng;
use ray_tracing_core::{
    color::Color,
    light::AreaLight,
    math::{Dir3, Pos3},
    prelude::Float,
};
use ray_tracing_material::{iridescent::Iridescent, oren_nayar::OrenNayar};
use std::cell::OnceCell;

use super::ExampleScene;

#[derive(Default)]
pub struct Presentation<R: Rng> {
    scene: OnceCell<TriangleBVH<R>>,
}

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

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

            let mut triangles = obj
                .triangle_faces()
                .map(|t| Triangle::new(t.v, 4))
                .collect::<Vec<_>>();

            let materials = vec![
                BVHMaterial::new_material(OrenNayar::new(0.5, Color::new(0.8, 0.8, 0.8))),
                BVHMaterial::new_material(OrenNayar::new(0.5, Color::new(0.8, 0.8, 0.8))),
                BVHMaterial::new_material(OrenNayar::new(0.5, Color::new(0.8, 0.8, 0.8))),
                // BVHMaterial::new_material(OrenNayar::new(0.5, Color::new(0.9, 0.2, 0.2))),
                // BVHMaterial::new_material(OrenNayar::new(0.5, Color::new(0.2, 0.9, 0.2))),
                BVHMaterial::new_light(AreaLight::new(Color::white() * 30.0)),
                BVHMaterial::new_material(Iridescent::new(250.0, 250.0, 1.0, 1.5, 20)),
                BVHMaterial::new_material(Iridescent::new(0.7 * 988.0, 0.3 * 988.0, 1.0, 1.5, 20)),
                BVHMaterial::new_material(Iridescent::new(0.9 * 500.0, 0.1 * 500.0, 1.0, 1.5, 20)),
            ];

            let dragon_vertices = obj.vertices.len();
            let dragon_triangles = triangles.len();

            let mut vertices = obj.vertices;
            for v in &mut vertices {
                *v = Pos3::new(v.x() * 0.3, (v.z() + 60.0) * 0.3, -v.y() * 0.3);
            }

            // second dragon
            let offset = vertices.len() as u32;
            for i in 0..dragon_vertices {
                vertices.push(vertices[i] + Dir3::new(50.0, 0.0, 25.0));
            }

            for i in 0..dragon_triangles {
                triangles.push(Triangle::new(
                    [
                        triangles[i].vertices[0] + offset,
                        triangles[i].vertices[1] + offset,
                        triangles[i].vertices[2] + offset,
                    ],
                    5,
                ));
            }

            // third dragon
            let offset = vertices.len() as u32;
            for i in 0..dragon_vertices {
                vertices.push(vertices[i] + Dir3::new(-50.0, 0.0, -25.0));
            }

            for i in 0..dragon_triangles {
                triangles.push(Triangle::new(
                    [
                        triangles[i].vertices[0] + offset,
                        triangles[i].vertices[1] + offset,
                        triangles[i].vertices[2] + offset,
                    ],
                    6,
                ));
            }

            // cornel box
            let side_length = 400.0;
            let light_size = 150.0;
            let light_offset = 0.01;
            let offset = vertices.len() as u32;
            vertices.extend_from_slice(&[
                Pos3::new(side_length, 2.0 * side_length, side_length),
                Pos3::new(side_length, 2.0 * side_length, -side_length),
                Pos3::new(side_length, 0.0, side_length),
                Pos3::new(side_length, 0.0, -side_length),
                Pos3::new(-side_length, 2.0 * side_length, side_length),
                Pos3::new(-side_length, 2.0 * side_length, -side_length),
                Pos3::new(-side_length, 0.0, side_length),
                Pos3::new(-side_length, 0.0, -side_length),
                Pos3::new(light_size, 2.0 * side_length - light_offset, light_size),
                Pos3::new(light_size, 2.0 * side_length - light_offset, -light_size),
                Pos3::new(-light_size, 2.0 * side_length - light_offset, light_size),
                Pos3::new(-light_size, 2.0 * side_length - light_offset, -light_size),
            ]);

            triangles.extend_from_slice(&[
                Triangle::new([offset, 1 + offset, 2 + offset], 0),
                Triangle::new([1 + offset, 3 + offset, 2 + offset], 0),
                Triangle::new([offset, 4 + offset, 1 + offset], 0),
                Triangle::new([1 + offset, 4 + offset, 5 + offset], 0),
                Triangle::new([2 + offset, 3 + offset, 6 + offset], 0),
                Triangle::new([3 + offset, 7 + offset, 6 + offset], 0),
                Triangle::new([offset, 2 + offset, 4 + offset], 1),
                Triangle::new([6 + offset, 4 + offset, 2 + offset], 1),
                Triangle::new([1 + offset, 5 + offset, 3 + offset], 2),
                Triangle::new([7 + offset, 3 + offset, 5 + offset], 2),
                Triangle::new([8 + offset, 10 + offset, 9 + offset], 3),
                Triangle::new([11 + offset, 9 + offset, 10 + offset], 3),
            ]);

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

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

    fn get_camera_pos(&self) -> Pos3 {
        Pos3::new(-150.0, 160.0, 250.0)
    }

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

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

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