use crate::Float;
use crate::core::bxdf::{BxDF, BxDFFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::geometry::{Frame, Normal3f, Point2f, Vector3f, VectorLike};
use crate::spectra::SampledSpectrum;
use crate::utils::Ptr;

#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct BSDF {
    bxdf: Ptr<BxDF>,
    shading_frame: Frame,
}

impl BSDF {
    pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Ptr<BxDF>) -> Self {
        Self {
            bxdf,
            shading_frame: Frame::new(dpdus.normalize(), Vector3f::from(ns)),
        }
    }

    pub fn is_valid(&self) -> bool {
        !self.bxdf.is_null()
    }

    pub fn flags(&self) -> BxDFFlags {
        if self.bxdf.is_null() {
            // Either this, or transmissive for seethrough
            return BxDFFlags::empty();
        }
        self.bxdf.flags()
    }

    pub fn render_to_local(&self, v: Vector3f) -> Vector3f {
        self.shading_frame.to_local(v)
    }

    pub fn local_to_render(&self, v: Vector3f) -> Vector3f {
        self.shading_frame.from_local(v)
    }

    pub fn f(
        &self,
        wo_render: Vector3f,
        wi_render: Vector3f,
        mode: TransportMode,
    ) -> Option<SampledSpectrum> {
        if self.bxdf.is_null() {
            return None;
        }

        let wi = self.render_to_local(wi_render);
        let wo = self.render_to_local(wo_render);

        if wo.z() == 0.0 || wi.z() == 0.0 {
            return None;
        }

        Some(self.bxdf.f(wo, wi, mode))
    }

    pub fn sample_f(
        &self,
        wo_render: Vector3f,
        u: Float,
        u2: Point2f,
        f_args: FArgs,
    ) -> Option<BSDFSample> {
        let bxdf = unsafe { self.bxdf.as_ref() };

        let sampling_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits());
        let wo = self.render_to_local(wo_render);
        if wo.z() == 0.0 || !bxdf.flags().contains(sampling_flags) {
            return None;
        }

        let mut sample = bxdf.sample_f(wo, u, u2, f_args)?;

        if sample.pdf > 0.0 && sample.wi.z() != 0.0 {
            sample.wi = self.local_to_render(sample.wi);
            return Some(sample);
        }

        None
    }

    pub fn pdf(&self, wo_render: Vector3f, wi_render: Vector3f, f_args: FArgs) -> Float {
        if self.bxdf.is_null() {
            return 0.0;
        }
        let sample_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits());

        let wo = self.render_to_local(wo_render);
        let wi = self.render_to_local(wi_render);

        if wo.z() == 0.0 || !self.bxdf.flags().contains(sample_flags) {
            return 0.0;
        }

        self.bxdf.pdf(wo, wi, f_args)
    }

    pub fn rho_u(&self, u1: &[Point2f], uc: &[Float], u2: &[Point2f]) -> SampledSpectrum {
        if self.bxdf.is_null() {
            return SampledSpectrum::default();
        }

        self.bxdf.rho_u(u1, uc, u2)
    }

    pub fn rho_wo(&self, wo_render: Vector3f, uc: &[Float], u: &[Point2f]) -> SampledSpectrum {
        if self.bxdf.is_null() {
            return SampledSpectrum::default();
        }

        let wo = self.render_to_local(wo_render);
        self.bxdf.rho_wo(wo, uc, u)
    }

    pub fn regularize(&mut self) {
        if !self.bxdf.is_null() {
            let mut bxdf = unsafe { *self.bxdf.as_raw() };
            bxdf.regularize();
        }
    }
}

#[derive(Debug, Clone)]
pub struct BSDFSample {
    pub f: SampledSpectrum,
    pub wi: Vector3f,
    pub pdf: Float,
    pub flags: BxDFFlags,
    pub eta: Float,
    pub pdf_is_proportional: bool,
}

impl Default for BSDFSample {
    fn default() -> Self {
        Self {
            f: SampledSpectrum::default(),
            wi: Vector3f::default(),
            pdf: 0.0,
            flags: BxDFFlags::empty(),
            eta: 1.0,
            pdf_is_proportional: false,
        }
    }
}

impl BSDFSample {
    pub fn new(
        f: SampledSpectrum,
        wi: Vector3f,
        pdf: Float,
        flags: BxDFFlags,
        eta: Float,
        pdf_is_proportional: bool,
    ) -> Self {
        Self {
            f,
            wi,
            pdf,
            flags,
            eta,
            pdf_is_proportional,
        }
    }

    #[inline]
    pub fn is_reflective(&self) -> bool {
        self.flags.is_reflective()
    }
    #[inline]
    pub fn is_transmissive(&self) -> bool {
        self.flags.is_transmissive()
    }
    #[inline]
    pub fn is_diffuse(&self) -> bool {
        self.flags.is_diffuse()
    }
    #[inline]
    pub fn is_glossy(&self) -> bool {
        self.flags.is_glossy()
    }
    #[inline]
    pub fn is_specular(&self) -> bool {
        self.flags.is_specular()
    }
}
