use std::ops::Deref;

use crate::prelude::*;

pub trait Scene<R: Rng> {
    type Mat<'a>: Material<R>
    where
        Self: 'a,
        R: 'a;

    type Light<'b>: Light<R>
    where
        Self: 'b,
        R: 'b;

    fn intersect(
        &self,
        ray: Ray,
        min: Float,
        max: Float,
    ) -> Option<Intersection<R, Self::Mat<'_>, Self::Light<'_>>>;

    fn sample_light<'b>(
        &self,
        w_in: Dir3,
        intersection: &Intersection<R, Self::Mat<'b>, Self::Light<'b>>,
        rng: &mut R,
    ) -> Option<LightSample<'_, R>>
    where
        Self: 'b;
}

pub struct Intersection<R: Rng, M: Material<R>, L: Light<R>> {
    t: Float,
    normal: Dir3,
    material: Option<M>,
    light: Option<L>,
    light_pdf: Float,
    r: std::marker::PhantomData<R>,
}

impl<R: Rng, M: Material<R>, L: Light<R>> Intersection<R, M, L> {
    pub fn new(
        t: Float,
        normal: Dir3,
        material: Option<M>,
        light: Option<L>,
        light_pdf: Float,
    ) -> Self {
        Self {
            t,
            normal,
            material,
            light,
            light_pdf,
            r: std::marker::PhantomData,
        }
    }

    pub fn t(&self) -> Float {
        self.t
    }

    pub fn normal(&self) -> Dir3 {
        self.normal
    }

    pub fn material(&self) -> Option<&M> {
        self.material.as_ref()
    }

    pub fn light(&self) -> Option<&L> {
        self.light.as_ref()
    }

    pub fn light_pdf(&self) -> Float {
        self.light_pdf
    }

    pub fn tangent_frame(&self) -> Frame {
        Frame::from_normal(self.normal)
    }
}

pub struct LightSample<'sc, R: Rng> {
    pos: Pos3,
    pdf: Float,
    normal: Dir3,
    light: &'sc dyn Light<R>,
}

impl<'sc, R: Rng> LightSample<'sc, R> {
    pub fn new(pos: Pos3, pdf: Float, normal: Dir3, light: &'sc dyn Light<R>) -> Self {
        Self {
            pos,
            pdf,
            normal,
            light,
        }
    }

    pub fn pdf(&self) -> Float {
        self.pdf
    }

    pub fn pos(&self) -> Pos3 {
        self.pos
    }

    pub fn light(&self) -> &'sc dyn Light<R> {
        self.light
    }

    pub fn normal(&self) -> Dir3 {
        self.normal
    }

    pub fn tangent_frame(&self) -> Frame {
        Frame::from_normal(self.normal)
    }
}

impl<T: Scene<R> + ?Sized, R: Rng> Scene<R> for Box<T> {
    type Mat<'a>
        = T::Mat<'a>
    where
        T: 'a,
        R: 'a;

    type Light<'b>
        = T::Light<'b>
    where
        T: 'b,
        R: 'b;

    fn intersect(
        &self,
        ray: Ray,
        min: Float,
        max: Float,
    ) -> Option<Intersection<R, Self::Mat<'_>, Self::Light<'_>>> {
        self.deref().intersect(ray, min, max)
    }

    fn sample_light<'b>(
        &self,
        w_in: Dir3,
        intersection: &Intersection<R, Self::Mat<'b>, Self::Light<'b>>,
        rng: &mut R,
    ) -> Option<LightSample<'_, R>>
    where
        Self: 'b,
    {
        self.deref().sample_light(w_in, intersection, rng)
    }
}
