use std::sync::Arc;

use crate::{
    aabb::AABB,
    cuboid::Cuboid,
    hitable::{Hit, HitRecord},
    material::Material,
    ray::Ray,
    vec3::Vec3,
};

#[derive(Debug)]
pub struct Glowybox {
    p_min: Vec3,
    p_max: Vec3,
    main: Cuboid,
    edges: [Cuboid; 12],
}

impl Glowybox {
    // This clippy doesn't work right with Arc.
    #[allow(clippy::needless_pass_by_value)]
    pub fn new(
        p_min: Vec3,
        p_max: Vec3,
        edge_thickness: f32,
        main_material: Arc<dyn Material>,
        edge_material: Arc<dyn Material>,
    ) -> Glowybox {
        assert!(p_min.x < p_max.x);
        assert!(p_min.y < p_max.y);
        assert!(p_min.z < p_max.z);
        let main = Cuboid::new(p_min, p_max, main_material);
        // Top edges
        let ht = edge_thickness / 2.;
        let edges = [
            // Top edges
            Cuboid::new(
                [p_min.x - ht, p_max.y - ht, p_min.z - ht].into(),
                [p_min.x + ht, p_max.y + ht, p_max.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_min.x - ht, p_max.y - ht, p_min.z - ht].into(),
                [p_max.x + ht, p_max.y + ht, p_min.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_max.x - ht, p_max.y - ht, p_min.z - ht].into(),
                [p_max.x + ht, p_max.y + ht, p_max.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_min.x - ht, p_max.y - ht, p_max.z - ht].into(),
                [p_max.x + ht, p_max.y + ht, p_max.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            // Bottom edges
            Cuboid::new(
                [p_min.x - ht, p_min.y - ht, p_min.z - ht].into(),
                [p_min.x + ht, p_min.y + ht, p_max.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_min.x - ht, p_min.y - ht, p_min.z - ht].into(),
                [p_max.x + ht, p_min.y + ht, p_min.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_max.x - ht, p_min.y - ht, p_min.z - ht].into(),
                [p_max.x + ht, p_min.y + ht, p_max.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_min.x - ht, p_min.y - ht, p_max.z - ht].into(),
                [p_max.x + ht, p_min.y + ht, p_max.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            // Middle edges
            Cuboid::new(
                [p_min.x - ht, p_min.y - ht, p_min.z - ht].into(),
                [p_min.x + ht, p_max.y + ht, p_min.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_min.x - ht, p_min.y - ht, p_max.z - ht].into(),
                [p_min.x + ht, p_max.y + ht, p_max.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_max.x - ht, p_min.y - ht, p_min.z - ht].into(),
                [p_max.x + ht, p_max.y + ht, p_min.z + ht].into(),
                Arc::clone(&edge_material),
            ),
            Cuboid::new(
                [p_max.x - ht, p_min.y - ht, p_max.z - ht].into(),
                [p_max.x + ht, p_max.y + ht, p_max.z + ht].into(),
                Arc::clone(&edge_material),
            ),
        ];
        let p_min = [p_min.x - ht, p_min.y - ht, p_min.z - ht].into();
        let p_max = [p_max.x + ht, p_max.y + ht, p_max.z + ht].into();
        Glowybox {
            p_min,
            p_max,
            main,
            edges,
        }
    }
}

impl Hit for Glowybox {
    fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
        let mut edge_hit = None;
        for edge in &self.edges {
            if let Some(hit) = edge.hit(r, t_min, t_max) {
                edge_hit = Some(hit);
                break;
            }
        }
        let main_hit = self.main.hit(r, t_min, t_max);
        match (edge_hit, main_hit) {
            (Some(ehit), Some(mhit)) => {
                if mhit.t < ehit.t {
                    Some(mhit)
                } else {
                    Some(ehit)
                }
            }
            (Some(ehit), None) => Some(ehit),
            (None, Some(mhit)) => Some(mhit),
            _ => None,
        }
    }

    fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
        Some(AABB::new(self.p_min, self.p_max))
    }
}
