use crate::core::geometry::{
    Normal3f, Point2f, Vector2f, Vector3f, VectorLike, abs_cos_theta, cos_phi, cos2_theta, sin_phi,
    tan2_theta,
};
use crate::core::pbrt::{Float, PI};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::math::{clamp, lerp, safe_sqrt, square};
use crate::utils::sampling::sample_uniform_disk_polar;
use num_traits::Float as NumFloat;

use crate::utils::complex::Complex;

#[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct TrowbridgeReitzDistribution {
    alpha_x: Float,
    alpha_y: Float,
}

impl TrowbridgeReitzDistribution {
    pub fn new(alpha_x: Float, alpha_y: Float) -> Self {
        Self { alpha_x, alpha_y }
    }

    pub fn d(&self, wm: Vector3f) -> Float {
        let tan2_theta = tan2_theta(wm);
        if tan2_theta.is_infinite() {
            return 0.;
        }
        let cos4_theta = square(cos2_theta(wm));
        let e =
            tan2_theta * (square(cos_phi(wm) / self.alpha_x) + square(sin_phi(wm) / self.alpha_y));
        1.0 / (PI * self.alpha_x * self.alpha_y * cos4_theta * square(1. + e))
    }

    pub fn effectively_smooth(&self) -> bool {
        self.alpha_x.max(self.alpha_y) < 1e-3
    }

    pub fn lambda(&self, w: Vector3f) -> Float {
        let tan2_theta = tan2_theta(w);
        if tan2_theta.is_infinite() {
            return 0.;
        }
        let alpha2 = square(cos_phi(w) * self.alpha_x) + square(sin_phi(w) * self.alpha_y);
        ((1. + alpha2 * tan2_theta).sqrt() - 1.) / 2.
    }

    pub fn g(&self, wo: Vector3f, wi: Vector3f) -> Float {
        1. / (1. + self.lambda(wo) + self.lambda(wi))
    }

    pub fn g1(&self, w: Vector3f) -> Float {
        1. / (1. / self.lambda(w))
    }

    pub fn d_from_w(&self, w: Vector3f, wm: Vector3f) -> Float {
        self.g1(w) / abs_cos_theta(w) * self.d(wm) * w.dot(wm).abs()
    }

    pub fn pdf(&self, w: Vector3f, wm: Vector3f) -> Float {
        self.d_from_w(w, wm)
    }

    pub fn sample_wm(&self, w: Vector3f, u: Point2f) -> Vector3f {
        let mut wh = Vector3f::new(self.alpha_x * w.x(), self.alpha_y * w.y(), w.z()).normalize();
        if wh.z() < 0. {
            wh = -wh;
        }
        let t1 = if wh.z() < 0.99999 {
            Vector3f::new(0., 0., 1.).cross(wh).normalize()
        } else {
            Vector3f::new(1., 0., 0.)
        };
        let t2 = wh.cross(t1);
        let mut p = sample_uniform_disk_polar(u);
        let h = (1. - square(p.x())).sqrt();
        p[1] = lerp((1. + wh.z()) / 2., h, p.y());
        let pz = 0_f32.max(1. - Vector2f::from(p).norm_squared());
        let nh = p.x() * t1 + p.y() * t2 + pz * wh;
        Vector3f::new(
            self.alpha_x * nh.x(),
            self.alpha_y * nh.y(),
            nh.z().max(1e-6),
        )
        .normalize()
    }

    pub fn roughness_to_alpha(roughness: Float) -> Float {
        roughness.sqrt()
    }

    pub fn regularize(&mut self) {
        if self.alpha_x < 0.3 {
            self.alpha_x = clamp(2. * self.alpha_x, 0.1, 0.3);
        }
        if self.alpha_y < 0.3 {
            self.alpha_y = clamp(2. * self.alpha_y, 0.1, 0.3);
        }
    }
}

pub fn refract(wi: Vector3f, n: Normal3f, eta_ratio: Float) -> Option<(Vector3f, Float)> {
    let mut n_interface = n;
    let mut eta = eta_ratio;

    let mut cos_theta_i = Vector3f::from(n_interface).dot(wi);

    if cos_theta_i < 0.0 {
        eta = 1.0 / eta;
        cos_theta_i = -cos_theta_i;
        n_interface = -n_interface;
    }

    let sin2_theta_i = (1.0 - square(cos_theta_i)).max(0.0_f32);
    let sin2_theta_t = sin2_theta_i / square(eta);

    // Handle total internal reflection
    if sin2_theta_t >= 1.0 {
        return None;
    }

    let cos_theta_t = (1.0 - sin2_theta_t).sqrt();

    let wt = -wi / eta + (cos_theta_i / eta - cos_theta_t) * Vector3f::from(n_interface);
    Some((wt, eta))
}

pub fn reflect(wo: Vector3f, n: Normal3f) -> Vector3f {
    -wo + Vector3f::from(2. * wo.dot(n.into()) * n)
}

pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float {
    let mut cos_safe = clamp(cos_theta_i, -1., 1.);
    let mut eta_corr = eta;
    if cos_theta_i < 0. {
        eta_corr = 1. / eta_corr;
        cos_safe = -cos_safe;
    }
    let sin2_theta_i = 1. - square(cos_safe);
    let sin2_theta_t = sin2_theta_i / square(eta_corr);
    if sin2_theta_t >= 1. {
        return 1.;
    }
    let cos_theta_t = safe_sqrt(1. - sin2_theta_t);
    let r_parl = (eta_corr * cos_safe - cos_theta_t) / (eta_corr * cos_safe + cos_theta_t);
    let r_perp = (cos_safe - eta_corr * cos_theta_t) / (cos_safe + eta_corr * cos_theta_t);

    (square(r_parl) + square(r_perp)) / 2.
}

pub fn fr_complex(cos_theta_i: Float, eta: Complex) -> Float {
    let cos_corr = clamp(cos_theta_i, 0., 1.);
    let sin2_theta_i = 1. - square(cos_corr);
    let sin2_theta_t: Complex = sin2_theta_i / square(eta);
    let cos2_theta_t: Complex = (1. - sin2_theta_t).sqrt();

    let r_parl = (eta * cos_corr - cos2_theta_t) / (eta * cos_corr + cos2_theta_t);
    let r_perp = (cos_corr - eta * cos2_theta_t) / (cos_corr + eta * cos2_theta_t);

    (r_parl.norm() + r_perp.norm()) / 2.
}

pub fn fr_complex_from_spectrum(
    cos_theta_i: Float,
    eta: SampledSpectrum,
    k: SampledSpectrum,
) -> SampledSpectrum {
    let mut result = SampledSpectrum::default();
    for i in 0..N_SPECTRUM_SAMPLES {
        result[i] = fr_complex(cos_theta_i, Complex::new(eta[i], k[i]));
    }
    result
}

pub fn fresnel_moment1(eta: Float) -> Float {
    let eta2 = eta * eta;
    let eta3 = eta2 * eta;
    let eta4 = eta3 * eta;
    let eta5 = eta4 * eta;
    if eta < 1. {
        return 0.45966 - 1.73965 * eta + 3.37668 * eta2 - 3.904945 * eta3 + 2.49277 * eta4
            - 0.68441 * eta5;
    } else {
        return -4.61686 + 11.1136 * eta - 10.4646 * eta2 + 5.11455 * eta3 - 1.27198 * eta4
            + 0.12746 * eta5;
    }
}

pub fn fresnel_moment2(eta: Float) -> Float {
    let eta2 = eta * eta;
    let eta3 = eta2 * eta;
    let eta4 = eta3 * eta;
    let eta5 = eta4 * eta;

    if eta < 1. {
        return 0.27614 - 0.87350 * eta + 1.12077 * eta2 - 0.65095 * eta3
            + 0.07883 * eta4
            + 0.04860 * eta5;
    } else {
        let r_eta = 1. / eta;
        let r_eta2 = r_eta * r_eta;
        let r_eta3 = r_eta2 * r_eta;

        return -547.033 + 45.3087 * r_eta3 - 218.725 * r_eta2 + 458.843 * r_eta + 404.557 * eta
            - 189.519 * eta2
            + 54.9327 * eta3
            - 9.00603 * eta4
            + 0.63942 * eta5;
    }
}
