use crate::core::bsdf::BSDFSample;
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::geometry::{
    Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere, spherical_direction,
    spherical_theta,
};
use crate::core::scattering::reflect;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::square;
use crate::utils::ptr::Ptr;
use crate::utils::sampling::{PiecewiseLinear2D, cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI, PI, PI_OVER_2};
use core::any::Any;
use num_traits::Float as NumFloat;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MeasuredBxDFData {
    pub isotropic: bool,
    pub wavelengths: Ptr<Float>,
    pub spectra: Ptr<PiecewiseLinear2D<3>>,
    pub ndf: Ptr<PiecewiseLinear2D<0>>,
    pub vndf: Ptr<PiecewiseLinear2D<2>>,
    pub sigma: Ptr<PiecewiseLinear2D<0>>,
    pub luminance: Ptr<PiecewiseLinear2D<2>>,
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MeasuredBxDF {
    pub brdf: Ptr<MeasuredBxDFData>,
    pub lambda: SampledWavelengths,
}

unsafe impl Send for MeasuredBxDF {}
unsafe impl Sync for MeasuredBxDF {}

impl MeasuredBxDF {
    pub fn new(brdf: &MeasuredBxDFData, lambda: &SampledWavelengths) -> Self {
        Self {
            brdf: Ptr::from(brdf),
            lambda: *lambda,
        }
    }

    pub fn theta2u(theta: Float) -> Float {
        (theta * (2. / PI)).sqrt()
    }

    pub fn phi2u(phi: Float) -> Float {
        phi * 1. / (2. * PI) + 0.5
    }

    pub fn u2theta(u: Float) -> Float {
        square(u) * PI_OVER_2
    }

    pub fn u2phi(u: Float) -> Float {
        (2. * u - 1.) * PI
    }
}

impl BxDFTrait for MeasuredBxDF {
    fn flags(&self) -> BxDFFlags {
        BxDFFlags::REFLECTION | BxDFFlags::GLOSSY
    }

    fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
        if !same_hemisphere(wo, wi) {
            return SampledSpectrum::new(0.);
        }

        let mut wo_curr = wo;
        let mut wi_curr = wi;
        if wo.z() < 0. {
            wo_curr = -wo_curr;
            wi_curr = -wi_curr;
        }

        // Get half direction vector
        let wm_curr = wi_curr + wo_curr;
        if wm_curr.norm_squared() == 0. {
            return SampledSpectrum::new(0.);
        }
        let wm = wm_curr.normalize();

        // Map vectors to unit square
        let theta_o = spherical_theta(wo_curr);
        let phi_o = wo_curr.y().atan2(wo_curr.x());
        let theta_m = spherical_theta(wm);
        let phi_m = wm.y().atan2(wm.x());
        let u_wo = Point2f::new(MeasuredBxDF::theta2u(theta_o), MeasuredBxDF::phi2u(phi_o));
        let u_wm_phi = if self.brdf.isotropic {
            phi_m - phi_o
        } else {
            phi_m
        };
        let mut u_wm = Point2f::new(
            MeasuredBxDF::theta2u(theta_m),
            MeasuredBxDF::phi2u(u_wm_phi),
        );
        u_wm[1] -= u_wm[1].floor();

        // Inverse parametrization
        let ui = self.brdf.vndf.invert(u_wm, [phi_o, theta_o]);
        let fr = SampledSpectrum::from_fn(|i| {
            self.brdf
                .spectra
                .evaluate(ui.p, [phi_o, theta_o, self.lambda[i]])
                .max(0.0)
        });

        fr * self.brdf.ndf.evaluate(u_wm, [])
            / (4. * self.brdf.sigma.evaluate(u_wo, []) * cos_theta(wi))
    }

    fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
        let reflection_flags =
            BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
        if !f_args.sample_flags.contains(reflection_flags) {
            return 0.;
        }
        if !same_hemisphere(wo, wi) {
            return 0.;
        }

        let mut wo_curr = wo;
        let mut wi_curr = wi;
        if wo.z() < 0. {
            wo_curr = -wo_curr;
            wi_curr = -wi_curr;
        }

        let wm_curr = wi_curr + wo_curr;
        if wm_curr.norm_squared() == 0. {
            return 0.;
        }
        let wm = wm_curr.normalize();
        let theta_o = spherical_theta(wo_curr);
        let phi_o = wo_curr.y().atan2(wo_curr.x());
        let theta_m = spherical_theta(wm);
        let phi_m = wm.y().atan2(wm.x());

        let u_wm_phi = if self.brdf.isotropic {
            phi_m - phi_o
        } else {
            phi_m
        };

        let mut u_wm = Point2f::new(
            MeasuredBxDF::theta2u(theta_m),
            MeasuredBxDF::phi2u(u_wm_phi),
        );
        u_wm[1] = u_wm[1] - u_wm[1].floor();

        let ui = self.brdf.vndf.invert(u_wm, [phi_o, theta_o]);
        let sample = ui.p;
        let vndf_pdf = ui.pdf;

        let pdf = self.brdf.luminance.evaluate(sample, [phi_o, theta_o]);
        let sin_theta_m = (square(wm.x()) + square(wm.y())).sqrt();
        let jacobian = 4. * wm.dot(wo) * f32::max(2. * square(PI) * u_wm.x() * sin_theta_m, 1e-6);
        vndf_pdf * pdf / jacobian
    }

    fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
        let reflection_flags =
            BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
        if !f_args.sample_flags.contains(reflection_flags) {
            return None;
        }

        let mut flip_w = false;
        let mut wo_curr = wo;
        if wo.z() <= 0. {
            wo_curr = -wo_curr;
            flip_w = true;
        }

        let theta_o = spherical_theta(wo_curr);
        let phi_o = wo_curr.y().atan2(wo_curr.x());
        // Warp sample using luminance distribution
        let mut s = self.brdf.luminance.sample(u, [phi_o, theta_o]);
        let u = s.p;
        let lum_pdf = s.pdf;

        // Sample visible normal distribution of measured BRDF
        s = self.brdf.vndf.sample(u, [phi_o, theta_o]);
        let u_wm = s.p;
        let mut pdf = s.pdf;

        // Map from microfacet normal to incident direction
        let mut phi_m = MeasuredBxDF::u2phi(u_wm.y());
        let theta_m = MeasuredBxDF::u2theta(u_wm.x());
        if self.brdf.isotropic {
            phi_m += phi_o;
        }
        let sin_theta_m = theta_m.sin();
        let cos_theta_m = theta_m.cos();
        let wm = spherical_direction(sin_theta_m, cos_theta_m, phi_m);
        let mut wi = reflect(wo_curr, wm.into());
        if wi.z() <= 0. {
            return None;
        }

        // Interpolate spectral BRDF
        let mut f = SampledSpectrum::from_fn(|i| {
            self.brdf
                .spectra
                .evaluate(u, [phi_o, theta_o, self.lambda[i]])
                .max(0.0)
        });

        let u_wo = Point2f::new(MeasuredBxDF::theta2u(theta_o), MeasuredBxDF::phi2u(phi_o));
        f *= self.brdf.ndf.evaluate(u_wm, [])
            / (4. * self.brdf.sigma.evaluate(u_wo, []) * abs_cos_theta(wi));
        pdf /= 4. * wm.dot(wo_curr) * f32::max(2. * square(PI) * u_wm.x(), 1e-6);

        if flip_w {
            wi = -wi;
        }

        let bsdf = BSDFSample {
            f,
            wi,
            pdf: pdf * lum_pdf,
            flags: BxDFFlags::GLOSSY_REFLECTION,
            ..Default::default()
        };

        Some(bsdf)
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn regularize(&mut self) {
        return;
    }
}
