use crate::core::geometry::{
    Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
    Vector3fi, VectorLike,
};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::shape::{
    QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
};
use crate::utils::Transform;
use crate::utils::math::square;
use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::{Float, PI};
use num_traits::Float as NumFloat;

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DiskShape {
    pub radius: Float,
    pub inner_radius: Float,
    pub height: Float,
    pub phi_max: Float,
    pub render_from_object: Transform,
    pub object_from_render: Transform,
    pub reverse_orientation: bool,
    pub transform_swap_handedness: bool,
}

impl DiskShape {
    pub fn new(
        radius: Float,
        inner_radius: Float,
        height: Float,
        phi_max: Float,
        render_from_object: Transform,
        object_from_render: Transform,
        reverse_orientation: bool,
    ) -> Self {
        Self {
            radius,
            inner_radius,
            height,
            phi_max,
            render_from_object: render_from_object.clone(),
            object_from_render,
            reverse_orientation,
            transform_swap_handedness: render_from_object.swaps_handedness(),
        }
    }

    fn basic_intersect(&self, r: &Ray, t_max: Float) -> Option<QuadricIntersection> {
        let oi = self.object_from_render.apply_to_point(r.o);
        let di = self.object_from_render.apply_to_vector(r.d);
        // Reject disk intersections for rays parallel to the disk’s plane
        if di.z() == 0. {
            return None;
        }

        let t_shape_hit = (self.height - oi.z()) / di.z();
        if t_shape_hit == 0. || t_shape_hit >= t_max {
            return None;
        }

        // See if hit point is inside disk radii and phi_max
        let p_hit: Point3f = oi + t_shape_hit * di;
        let dist2 = square(p_hit.x()) + square(p_hit.y());
        if dist2 > square(self.radius) || dist2 < square(self.inner_radius) {
            return None;
        }

        let mut phi = p_hit.y().atan2(p_hit.x());
        if phi < 0. {
            phi += 2. * PI;
        }
        if phi > self.phi_max {
            return None;
        }

        Some(QuadricIntersection {
            t_hit: t_shape_hit,
            p_obj: p_hit,
            phi,
        })
    }

    fn interaction_from_intersection(
        &self,
        isect: QuadricIntersection,
        wo: Vector3f,
        time: Float,
    ) -> SurfaceInteraction {
        let mut p_hit = isect.p_obj;
        let phi = isect.phi;
        // Find parametric representation of disk hit
        let u = phi / self.phi_max;
        let r_hit = (square(p_hit.x()) + square(p_hit.y())).sqrt();
        let v = (self.radius - r_hit) / (self.radius - self.inner_radius);
        let dpdu = Vector3f::new(-self.phi_max * p_hit.y(), self.phi_max * p_hit.x(), 0.);
        let dpdv =
            Vector3f::new(p_hit.x(), p_hit.y(), 0.) * (self.inner_radius - self.radius) / r_hit;
        let dndu = Normal3f::zero();
        let dndv = Normal3f::zero();

        p_hit[2] = self.height;

        let p_error = Vector3f::zero();
        let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness;
        let wo_object = self.object_from_render.apply_to_vector(wo);
        SurfaceInteraction::new(
            Point3fi::new_with_error(p_hit, p_error),
            Point2f::new(u, v),
            wo_object,
            dpdu,
            dpdv,
            dndu,
            dndv,
            time,
            flip_normal,
        )
    }
}

impl ShapeTrait for DiskShape {
    fn area(&self) -> Float {
        self.phi_max * 0.5 * (square(self.radius) - square(self.inner_radius))
    }

    fn bounds(&self) -> Bounds3f {
        self.render_from_object
            .apply_to_bounds(Bounds3f::from_points(
                Point3f::new(-self.radius, -self.radius, self.height),
                Point3f::new(self.radius, self.radius, self.height),
            ))
    }
    fn normal_bounds(&self) -> DirectionCone {
        let mut n = self
            .render_from_object
            .apply_to_normal(Normal3f::new(0., 0., 1.));
        if self.reverse_orientation {
            n = -n;
        }
        DirectionCone::new_from_vector(Vector3f::from(n))
    }

    fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
        let t = t_max.unwrap_or(Float::INFINITY);
        if let Some(isect) = self.basic_intersect(ray, t) {
            let intr = self.interaction_from_intersection(isect.clone(), -ray.d, ray.time);
            Some(ShapeIntersection::new(intr, isect.t_hit))
        } else {
            None
        }
    }

    fn sample(&self, u: Point2f) -> Option<ShapeSample> {
        let pd = sample_uniform_disk_concentric(u);
        let p_obj = Point3f::new(pd.x() * self.radius, pd.y() * self.radius, self.height);
        let pi = self
            .render_from_object
            .apply_to_interval(&Point3fi::new_from_point(p_obj));
        let mut n: Normal3f = self
            .render_from_object
            .apply_to_normal(Normal3f::new(0., 0., 1.))
            .normalize();
        if self.reverse_orientation {
            n = -n;
        }
        let mut phi = pd.y().atan2(pd.x());
        if phi < 0. {
            phi += 2. * PI;
        }
        let radius_sample = (square(p_obj.x()) + square(p_obj.y())).sqrt();
        let uv = Point2f::new(
            phi / self.phi_max,
            (self.radius - radius_sample) / (self.radius - self.inner_radius),
        );

        Some(ShapeSample {
            intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, uv)),
            pdf: 1. / self.area(),
        })
    }

    fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
        if let Some(t) = t_max {
            self.basic_intersect(ray, t).is_some()
        } else {
            self.basic_intersect(ray, Float::INFINITY).is_some()
        }
    }

    fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
        let mut ss = self.sample(u)?;
        ss.intr.get_common_mut().time = ctx.time;
        let mut wi = ss.intr.p() - ctx.p();
        if wi.norm_squared() == 0. {
            return None;
        }
        wi = wi.normalize();

        ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
        if ss.pdf.is_infinite() {
            return None;
        }

        Some(ss)
    }

    fn pdf(&self, _interaction: &Interaction) -> Float {
        1. / self.area()
    }

    fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
        let ray = ctx.spawn_ray(wi);
        if let Some(isect) = self.intersect(&ray, None) {
            let n = isect.intr.n();
            let absdot = Vector3f::from(n).dot(-wi).abs();
            let pdf = (1. / self.area()) / (absdot / ctx.p().distance_squared(isect.intr.p()));
            if pdf.is_infinite() {
                return 0.;
            }
            pdf
        } else {
            0.
        }
    }
}
