use crate::prelude::*;
use std::{fmt::Debug, ops::Deref};

/// Trait to model bxdf.
///
/// f(w_in, w_out) * cos(theta) / pdf
///
/// All calculations for the material are done a tangent space of the intersection.
///
/// The sample function has to sample according to the pdf function
pub trait Material<R: Rng>: Send + Sync + Debug {
    /// evaluate f(w_in, w_out)
    fn eval(&self, w_in: Dir3, w_out: Dir3, rng: &mut R) -> Color;

    /// sample w_out and return:
    /// f(w_in, w_out) * cos(theta) / pdf
    fn sample(&self, w_in: Dir3, rng: &mut R) -> SampleResult {
        let w_out = Dir3::sample_cosine_hemisphere(rng);

        SampleResult::new(w_out, self.eval(w_in, w_out, rng) * FloatConsts::PI, false)
    }

    /// returns the pdf for (w_in, w_out)
    fn pdf(&self, w_in: Dir3, w_out: Dir3) -> Float {
        let _ = w_out;
        FloatConsts::FRAC_1_PI * Float::max(w_in.y(), 0.0)
    }
}

impl<R: Rng> Material<R> for &dyn Material<R> {
    fn eval(&self, w_in: Dir3, w_out: Dir3, rng: &mut R) -> Color {
        (*self).eval(w_in, w_out, rng)
    }

    fn sample(&self, w_in: Dir3, rng: &mut R) -> SampleResult {
        (*self).sample(w_in, rng)
    }

    fn pdf(&self, w_in: Dir3, w_out: Dir3) -> Float {
        (*self).pdf(w_in, w_out)
    }
}

impl<R: Rng, M: Material<R>> Material<R> for &M {
    fn eval(&self, w_in: Dir3, w_out: Dir3, rng: &mut R) -> Color {
        (*self).eval(w_in, w_out, rng)
    }

    fn sample(&self, w_in: Dir3, rng: &mut R) -> SampleResult {
        (*self).sample(w_in, rng)
    }

    fn pdf(&self, w_in: Dir3, w_out: Dir3) -> Float {
        (*self).pdf(w_in, w_out)
    }
}

impl<R: Rng> Material<R> for std::sync::Arc<dyn Material<R>> {
    fn eval(&self, w_in: Dir3, w_out: Dir3, rng: &mut R) -> Color {
        self.deref().eval(w_in, w_out, rng)
    }

    fn sample(&self, w_in: Dir3, rng: &mut R) -> SampleResult {
        self.deref().sample(w_in, rng)
    }

    fn pdf(&self, w_in: Dir3, w_out: Dir3) -> Float {
        self.deref().pdf(w_in, w_out)
    }
}

#[derive(Debug)]
pub struct SampleResult {
    w_out: Dir3,
    color: Color,
    delta: bool,
}

impl SampleResult {
    /// In case the sample is according to a delta distribution the delta flat has to be set.
    pub fn new(w_out: Dir3, color: Color, delta: bool) -> Self {
        Self {
            w_out,
            color,
            delta,
        }
    }

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

    pub fn color(&self) -> Color {
        self.color
    }

    pub fn is_delta(&self) -> bool {
        self.delta
    }
}
