use ray_tracing_core::{camera::Camera, prelude::*, renderer::ClassicalRenderer, scene::Scene};

pub struct MIS {
    width: u32,
    height: u32,
}

impl MIS {
    pub fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }
}

impl<S, C, R> ClassicalRenderer<R, S, C> for MIS
where
    S: Scene<R>,
    C: Camera<R>,
    R: Rng,
{
    fn render_pixel(&self, scene: &S, camera: &C, x: u32, y: u32, rng: &mut R) -> Color {
        let mut sum = Color::black();
        let mut alpha = Color::white();

        let mut r = camera.forward(x, y, rng);

        let mut count = 0;
        let mut last_bsdf_pdf = 1.0;

        while let Some(i) = scene.intersect(r, 0.001, Float::INFINITY) {
            let frame = i.tangent_frame();

            let intersect_pos = r.at(i.t());

            let w_in = frame.to_frame(-r.dir());

            if let Some(light) = i.light() {
                if count == 0 {
                    sum += alpha * light.emit(w_in, rng);
                } else {
                    let dist = i.t() / r.dir().length();

                    let path_pdf = last_bsdf_pdf;
                    let nee_pdf = if i.light_pdf() != 0.0 {
                        i.light_pdf() * dist * dist / w_in.y()
                    } else {
                        0.0
                    };

                    let b = path_pdf / (path_pdf + nee_pdf + 0.000001);
                    if b.is_nan() {
                        dbg!(b, path_pdf, nee_pdf);
                    }
                    sum += b * alpha * light.emit(w_in, rng);
                }
            }

            let w_out = if let Some(material) = i.material() {
                let sample_result = material.sample(w_in, rng);

                if let Some(l) = scene.sample_light(w_in, &i, rng) {
                    let light_frame = l.tangent_frame();

                    let light_dir = l.pos() - intersect_pos;

                    let light_ray = Ray::new(intersect_pos, light_dir.normalize(), r.time());

                    if scene
                        .intersect(light_ray, 0.001, light_dir.length() - 0.001)
                        .is_none()
                    {
                        let g = (Dir3::dot(i.normal(), light_dir.normalize())
                            * Dir3::dot(l.normal(), -light_dir.normalize()))
                            / light_dir.length_squared();

                        // calculate MIS wheight
                        let path_pdf = material.pdf(w_in, frame.to_frame(light_dir));
                        let nee_pdf = l.pdf() * light_dir.length_squared() / Float::abs(w_in.y());

                        let b = nee_pdf / (path_pdf + nee_pdf);

                        sum += b
                            * alpha
                            * g
                            * material.eval(w_in, frame.to_frame(light_dir.normalize()), rng)
                            * l.light()
                                .emit(light_frame.to_frame(-light_dir.normalize()), rng)
                            / l.pdf();
                    }
                }

                if sample_result.is_delta() {
                    last_bsdf_pdf = 100000.0;
                } else {
                    last_bsdf_pdf = material.pdf(w_in, sample_result.w_out());
                }
                alpha *= sample_result.color();
                sample_result.w_out()
            } else {
                return sum;
            };

            r = Ray::new(r.at(i.t()), frame.to_world(w_out), r.time());
            count += 1;
            if count > 4 {
                break;
            }
        }

        sum
    }

    fn width(&self) -> u32 {
        self.width
    }

    fn height(&self) -> u32 {
        self.height
    }
}
