use std;

use rand;
use rand::Rng;

use crate::aabb::AABB;
use crate::hitable::Hit;
use crate::hitable::HitRecord;
use crate::material::Isotropic;
use crate::ray::Ray;
use crate::texture::Texture;
use crate::vec3::Vec3;

pub struct ConstantMedium<H, T>
where
    H: Hit,
    T: Texture,
{
    density: f32,
    material: Isotropic<T>,
    hitable: H,
}

impl<H, T> ConstantMedium<H, T>
where
    H: Hit,
    T: Texture,
{
    pub fn new(hitable: H, density: f32, texture: T) -> ConstantMedium<H, T>
    where
        T: Texture,
    {
        ConstantMedium {
            density,
            material: Isotropic::new(texture),
            hitable,
        }
    }
}

impl<H, T> Hit for ConstantMedium<H, T>
where
    H: Hit,
    T: Texture,
{
    fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
        let mut rng = rand::thread_rng();
        if let Some(mut rec1) = self.hitable.hit(r, std::f32::MIN, std::f32::MAX) {
            if let Some(mut rec2) = self.hitable.hit(r, rec1.t + 0.001, std::f32::MAX) {
                if rec1.t < t_min {
                    rec1.t = t_min;
                }
                if rec2.t > t_max {
                    rec2.t = t_max;
                }
                if rec1.t >= rec2.t {
                    return None;
                }
                if rec1.t < 0. {
                    rec1.t = 0.;
                }
                let distance_inside_boundary = (rec2.t - rec1.t) * r.direction.length();
                let hit_distance: f32 = -(1. / self.density) * rng.gen::<f32>().ln();
                if hit_distance < distance_inside_boundary {
                    let t = rec1.t + hit_distance / r.direction.length();
                    let normal = Vec3::new(rng.gen(), rng.gen(), rng.gen()).unit_vector();
                    return Some(HitRecord {
                        t,
                        p: r.point_at_parameter(t),
                        normal,
                        material: &self.material,
                        uv: (0., 0.),
                    });
                }
            }
        }
        None
    }

    fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
        self.hitable.bounding_box(t_min, t_max)
    }
}
