use crate::core::geometry::{
    Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
    Vector3fi, VectorLike, ray,
};
use crate::core::interaction::{
    Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::light::Light;
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::shapes::*;
use crate::utils::math::{next_float_down, next_float_up};
use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
use enum_dispatch::enum_dispatch;

// Define Intersection objects. This only varies for
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeIntersection {
    pub intr: SurfaceInteraction,
    pub t_hit: Float,
}

impl ShapeIntersection {
    pub fn new(intr: SurfaceInteraction, t_hit: Float) -> Self {
        Self { intr, t_hit }
    }

    pub fn t_hit(&self) -> Float {
        self.t_hit
    }

    pub fn set_t_hit(&mut self, new_t: Float) {
        self.t_hit = new_t;
    }

    pub fn set_intersection_properties(
        &mut self,
        mtl: Ptr<Material>,
        area: Ptr<Light>,
        prim_medium_interface: MediumInterface,
        ray_medium: Ptr<Medium>,
    ) {
        self.intr
            .set_intersection_properties(&mtl, &area, &ray_medium, prim_medium_interface);
    }
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct QuadricIntersection {
    pub t_hit: Float,
    pub p_obj: Point3f,
    pub phi: Float,
}

impl QuadricIntersection {
    pub fn new(t_hit: Float, p_obj: Point3f, phi: Float) -> Self {
        Self { t_hit, p_obj, phi }
    }
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeSample {
    pub intr: Interaction,
    pub pdf: Float,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeSampleContext {
    pub pi: Point3fi,
    pub n: Normal3f,
    pub ns: Normal3f,
    pub time: Float,
}

impl ShapeSampleContext {
    pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f, time: Float) -> Self {
        Self { pi, n, ns, time }
    }

    pub fn new_from_interaction(si: &SurfaceInteraction) -> Self {
        Self {
            pi: si.pi(),
            n: si.n(),
            ns: si.shading.n,
            time: si.time(),
        }
    }

    pub fn p(&self) -> Point3f {
        Point3f::from(self.pi)
    }

    pub fn offset_ray_origin(&self, w: Vector3f) -> Point3f {
        let d = self.n.abs().dot(self.pi.error().into());
        let mut offset = d * Vector3f::from(self.n);
        if w.dot(self.n.into()) < 0.0 {
            offset = -offset;
        }

        let mut po = Point3f::from(self.pi) + offset;
        for i in 0..3 {
            if offset[i] > 0.0 {
                po[i] = next_float_up(po[i]);
            } else {
                po[i] = next_float_down(po[i]);
            }
        }
        po
    }

    pub fn offset_ray_origin_from_point(&self, pt: Point3f) -> Point3f {
        self.offset_ray_origin(pt - self.p())
    }

    pub fn spawn_ray(&self, w: Vector3f) -> Ray {
        Ray::new(self.offset_ray_origin(w), w, Some(self.time), &Ptr::null())
    }
}

#[enum_dispatch]
pub trait ShapeTrait {
    fn bounds(&self) -> Bounds3f;
    fn normal_bounds(&self) -> DirectionCone;
    fn area(&self) -> Float;
    fn sample(&self, u: Point2f) -> Option<ShapeSample>;
    fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample>;
    fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
    fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool;
    fn pdf(&self, interaction: &Interaction) -> Float;
    fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float;
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[enum_dispatch(ShapeTrait)]
pub enum Shape {
    Sphere(SphereShape),
    Cylinder(CylinderShape),
    Disk(DiskShape),
    Triangle(TriangleShape),
    BilinearPatch(BilinearPatchShape),
    Curve(CurveShape),
}

impl Default for Shape {
    fn default() -> Self {
        Shape::Sphere(SphereShape::default())
    }
}
