use std::sync::{Arc, Mutex};

use derive_builder::Builder;

use crate::{
    intersections::Intersections,
    materials::{Material, MaterialBuilder},
    matrices::Matrix4x4,
    rays::Ray,
    tuples::Tuple,
};

#[derive(Default, PartialEq, Debug, Clone)]
pub struct TestData {
    pub saved_ray: Option<Ray>,
}

#[derive(Debug, Clone)]
pub enum Geometry {
    /// Shape with predictable normals useful for debugging.
    TestShape(Arc<Mutex<TestData>>),
    /// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
    Sphere,
    /// Flat surface that extends infinitely in the XZ axes.
    Plane,
    /// AABB cube at origin from -1,1 in each direction.
    Cube,
    /// Takes shape from children held within.
    Group(Vec<Shape>),
}

impl Default for Geometry {
    fn default() -> Geometry {
        Geometry::Sphere
    }
}

impl PartialEq for Geometry {
    fn eq(&self, rhs: &Geometry) -> bool {
        use Geometry::*;
        match (self, rhs) {
            (TestShape(l), TestShape(r)) => *l.lock().unwrap() == *r.lock().unwrap(),
            (Sphere, Sphere) => true,
            (Plane, Plane) => true,
            (Cube, Cube) => true,
            (Group(v1), Group(v2)) => v1 == v2,
            _ => false,
        }
    }
}

/// Shape represents visible objects.  A signal instance of Shape can generically represent one of
/// many different shapes based on the value of it's geometry field.  Users chose the shape by
/// calling the appropriate constructor, i.e. [Shape::sphere].
#[derive(Builder, Debug, Clone, PartialEq)]
#[builder(default, pattern = "owned", build_fn(skip))]
pub struct Shape {
    transform: Matrix4x4,
    inverse_transform: Matrix4x4,
    pub material: Material,
    geometry: Geometry,
}

/// Short hand for creating a ShapeBuilder with a plane geometry.
pub fn plane() -> ShapeBuilder {
    ShapeBuilder::plane()
}
/// Short hand for creating a ShapeBuilder with a sphere geometry.
pub fn sphere() -> ShapeBuilder {
    ShapeBuilder::sphere()
}

/// Short hand for creating a ShapeBuilder with a test shape geometry.
pub fn test_shape() -> ShapeBuilder {
    ShapeBuilder::test_shape()
}

/// Short hand for creating a ShapeBuilder with a cube geometry.
pub fn cube() -> ShapeBuilder {
    ShapeBuilder::cube()
}

/// Short hand for creating a ShapeBuilder with a group geometry.
pub fn group() -> ShapeBuilder {
    ShapeBuilder::group()
}

/// Helper for producing a sphere with a glassy material.
pub fn glass_sphere() -> ShapeBuilder {
    ShapeBuilder::sphere().material(
        MaterialBuilder::default()
            .transparency(1.)
            .refractive_index(1.5)
            .build()
            .unwrap(),
    )
}
impl ShapeBuilder {
    /// Short hand for creating a ShapeBuilder with a plane geometry.
    pub fn plane() -> ShapeBuilder {
        ShapeBuilder::default().geometry(Geometry::Plane)
    }
    /// Short hand for creating a ShapeBuilder with a sphere geometry.
    pub fn sphere() -> ShapeBuilder {
        ShapeBuilder::default().geometry(Geometry::Sphere)
    }
    /// Short hand for creating a ShapeBuilder with a test shape geometry.
    pub fn test_shape() -> ShapeBuilder {
        ShapeBuilder::default().geometry(Geometry::TestShape(Arc::new(Mutex::new(
            TestData::default(),
        ))))
    }
    /// Short hand for creating a ShapeBuilder with a cube geometry.
    pub fn cube() -> ShapeBuilder {
        ShapeBuilder::default().geometry(Geometry::Cube)
    }
    /// Short hand for creating a ShapeBuilder with a group geometry.
    pub fn group() -> ShapeBuilder {
        ShapeBuilder::default().geometry(Geometry::Group(Vec::new()))
    }

    /// Add child shapes, only valid for Group::Geometry.
    pub fn add_child(mut self, child: Shape) -> ShapeBuilder {
        if let Some(Geometry::Group(ref mut children)) = &mut self.geometry {
            children.push(child);
        }
        self
    }

    pub fn build(&self) -> Result<Shape, ShapeBuilderError> {
        let mut s = Shape::default();

        if let Some(transform) = &self.transform {
            s.set_transform(*transform);
        }
        if let Some(material) = &self.material {
            s.material = material.clone();
        };
        if let Some(geometry) = &self.geometry {
            s.geometry = geometry.clone();
        };
        let transform = s.transform();
        if let Geometry::Group(ref mut children) = s.geometry {
            children
                .iter_mut()
                .for_each(|c| c.set_transform(transform * c.transform()));
        }
        Ok(s)
    }
}

impl Default for Shape {
    fn default() -> Shape {
        Shape {
            transform: Matrix4x4::identity(),
            inverse_transform: Matrix4x4::identity(),
            material: Material::default(),
            geometry: Geometry::default(),
        }
    }
}

impl Shape {
    /// Create a test shape useful for debugging.
    pub fn test_shape() -> Shape {
        Shape {
            transform: Matrix4x4::identity(),
            inverse_transform: Matrix4x4::identity(),
            material: Material::default(),
            geometry: Geometry::TestShape(Arc::new(Mutex::new(TestData::default()))),
        }
    }
    pub fn sphere() -> Shape {
        Shape {
            transform: Matrix4x4::identity(),
            inverse_transform: Matrix4x4::identity(),
            material: Material::default(),
            geometry: Geometry::Sphere,
        }
    }
    pub fn plane() -> Shape {
        Shape {
            transform: Matrix4x4::identity(),
            inverse_transform: Matrix4x4::identity(),
            material: Material::default(),
            geometry: Geometry::Plane,
        }
    }
    pub fn cube() -> Shape {
        Shape {
            transform: Matrix4x4::identity(),
            inverse_transform: Matrix4x4::identity(),
            material: Material::default(),
            geometry: Geometry::Cube,
        }
    }
    pub fn group() -> Shape {
        Shape {
            transform: Matrix4x4::identity(),
            inverse_transform: Matrix4x4::identity(),
            material: Material::default(),
            geometry: Geometry::Group(Vec::new()),
        }
    }
    /// Find the normal at the point on the sphere.
    pub fn normal_at(&self, world_point: Tuple) -> Tuple {
        let object_point = self.inverse_transform * world_point;
        let object_normal = match self.geometry {
            Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
            Geometry::Plane => Tuple::vector(0., 1., 0.),
            Geometry::TestShape(_) => object_point,
            Geometry::Cube => cube::local_normal_at(object_point),
            Geometry::Group(_) => todo!("normal_at"),
        };
        let mut world_normal = self.inverse_transform.transpose() * object_normal;
        world_normal.w = 0.;
        world_normal.normalize()
    }

    pub fn transform(&self) -> Matrix4x4 {
        self.transform
    }

    pub fn inverse_transform(&self) -> Matrix4x4 {
        self.inverse_transform
    }

    pub fn set_transform(&mut self, t: Matrix4x4) {
        self.transform = t;
        self.inverse_transform = t.inverse();
    }
    pub fn geometry(&self) -> &Geometry {
        &self.geometry
    }
}

/// Intersect a ray with a shapes.
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
    let local_ray = ray.transform(shape.inverse_transform);
    match shape.geometry {
        Geometry::Sphere => sphere::intersect(shape, &local_ray),
        Geometry::Plane => plane::intersect(shape, &local_ray),
        Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray),
        Geometry::Cube => cube::intersect(shape, &local_ray),
        Geometry::Group(_) => group::intersect(shape, ray),
    }
}

mod test_shape {
    use crate::{
        intersections::Intersections,
        rays::Ray,
        shapes::{Geometry, Shape},
    };
    pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
        if let Geometry::TestShape(s) = &shape.geometry {
            s.lock()
                .expect("couldn't grab mutex for TestData")
                .saved_ray = Some(ray.clone());
        }
        Intersections::default()
    }
}

mod sphere {
    use crate::{
        intersections::{Intersection, Intersections},
        rays::Ray,
        shapes::Shape,
        tuples::{dot, Tuple},
    };
    pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
        let sphere_to_ray = ray.origin - Tuple::point(0., 0., 0.);
        let a = dot(ray.direction, ray.direction);
        let b = 2. * dot(ray.direction, sphere_to_ray);
        let c = dot(sphere_to_ray, sphere_to_ray) - 1.;
        let discriminant = b * b - 4. * a * c;
        if discriminant < 0. {
            return Intersections::default();
        }
        Intersections::new(vec![
            Intersection::new((-b - discriminant.sqrt()) / (2. * a), shape),
            Intersection::new((-b + discriminant.sqrt()) / (2. * a), shape),
        ])
    }
}

mod plane {
    use crate::{
        intersections::{Intersection, Intersections},
        rays::Ray,
        shapes::Shape,
        EPSILON,
    };
    pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
        if (ray.direction.y).abs() < EPSILON {
            return Intersections::default();
        }
        Intersections::new(vec![Intersection::new(
            -ray.origin.y / ray.direction.y,
            shape,
        )])
    }
}

mod cube {
    use crate::{
        intersections::{Intersection, Intersections},
        rays::Ray,
        shapes::Shape,
        tuples::{vector, Tuple},
        Float, EPSILON,
    };

    fn check_axis(origin: Float, direction: Float) -> (Float, Float) {
        let tmin_numerator = -1. - origin;
        let tmax_numerator = 1. - origin;

        let (tmin, tmax) = if direction.abs() >= EPSILON {
            (tmin_numerator / direction, tmax_numerator / direction)
        } else {
            (
                tmin_numerator * Float::INFINITY,
                tmax_numerator * Float::INFINITY,
            )
        };

        if tmin > tmax {
            return (tmax, tmin);
        }
        (tmin, tmax)
    }

    pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
        let (xtmin, xtmax) = check_axis(ray.origin.x, ray.direction.x);
        let (ytmin, ytmax) = check_axis(ray.origin.y, ray.direction.y);
        let (ztmin, ztmax) = check_axis(ray.origin.z, ray.direction.z);

        let tmin = xtmin.max(ytmin).max(ztmin);
        let tmax = xtmax.min(ytmax).min(ztmax);

        if tmin > tmax {
            return Intersections::default();
        }
        Intersections::new(vec![
            Intersection::new(tmin, shape),
            Intersection::new(tmax, shape),
        ])
    }
    pub fn local_normal_at(point: Tuple) -> Tuple {
        let x = point.x.abs();
        let y = point.y.abs();
        let z = point.z.abs();
        let maxc = x.max(y).max(z);
        if maxc == x {
            return vector(point.x, 0., 0.);
        }
        if maxc == y {
            return vector(0., point.y, 0.);
        }
        vector(0., 0., point.z)
    }
    #[cfg(test)]
    mod tests {
        use crate::{
            rays::Ray,
            shapes::Shape,
            tuples::{point, vector},
            EPSILON,
        };

        use super::{intersect, local_normal_at};

        #[test]
        fn ray_intersects_cube() {
            let c = Shape::cube();
            for (name, o, d, t1, t2) in [
                ("+x", point(5., 0.5, 0.), vector(-1., 0., 0.), 4., 6.),
                ("-x", point(-5., 0.5, 0.), vector(1., 0., 0.), 4., 6.),
                ("+y", point(0.5, 5., 0.), vector(0., -1., 0.), 4., 6.),
                ("-y", point(0.5, -5., 0.), vector(0., 1., 0.), 4., 6.),
                ("+z", point(0.5, 0., 5.), vector(0., 0., -1.), 4., 6.),
                ("-z", point(0.5, 0., -5.), vector(0., 0., 1.), 4., 6.),
                ("inside", point(0., 0.5, 0.), vector(0., 0., 1.), -1., 1.),
            ] {
                let r = Ray::new(o, d);
                let xs = intersect(&c, &r);
                assert_eq!(xs.len(), 2, "{}", name);
                assert!((xs[0].t - t1).abs() < EPSILON, "{} t1 {}", name, xs[0].t);
                assert!((xs[1].t - t2).abs() < EPSILON, "{} t2 {}", name, xs[1].t);
            }
        }

        #[test]
        fn ray_misses_cube() {
            let c = Shape::cube();
            for (o, d) in [
                (point(-2., 0., 0.), vector(0.2673, 0.5345, 0.8018)),
                (point(0., -2., 0.), vector(0.8018, 0.2673, 0.5345)),
                (point(0., 0., -2.), vector(0.5345, 0.8018, 0.2673)),
                (point(2., 0., 2.), vector(0., 0., -1.)),
                (point(0., 2., 2.), vector(0., -1., 0.)),
                (point(2., 2., -5.), vector(-1., 0., 0.)),
            ] {
                let r = Ray::new(o, d);
                let xs = intersect(&c, &r);
                assert_eq!(xs.len(), 0, "({:?}, {:?})", o, d);
            }
        }
        #[test]
        fn normal_cube_surface() {
            for (p, n) in [
                (point(1., 0.8, -0.8), vector(1., 0., 0.)),
                (point(-1., -0.2, 0.9), vector(-1., 0., 0.)),
                (point(-0.4, 1., -0.1), vector(0., 1., 0.)),
                (point(0.3, -1., -0.7), vector(0., -1., 0.)),
                (point(-0.6, 0.3, 1.), vector(0., 0., 1.)),
                (point(0.4, 0.4, -1.), vector(0., 0., -1.)),
                (point(1., 1., 1.), vector(1., 0., 0.)),
                (point(-1., -1., -1.), vector(-1., 0., 0.)),
            ] {
                let normal = local_normal_at(p);
                assert_eq!(n, normal);
            }
        }
    }
}

mod group {
    use crate::{intersections::Intersections, rays::Ray, shapes::Shape};

    use super::Geometry;

    pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
        if let Geometry::Group(children) = &shape.geometry {
            let mut intersections: Vec<_> = children
                .iter()
                .flat_map(|c| super::intersect(c, ray))
                .collect();
            intersections.sort_by(|a, b| {
                a.t.partial_cmp(&b.t)
                    .expect("an intersection has a t value that is NaN")
            });
            return Intersections::new(intersections);
        }
        unreachable!();
    }
    #[cfg(test)]
    mod tests {
        use crate::{
            matrices::{scaling, translation},
            rays::Ray,
            shapes::{intersect, Shape, ShapeBuilder},
            tuples::{point, vector},
        };

        #[test]
        fn intersecting_empty_group() {
            let g = Shape::group();
            let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.));
            let xs = intersect(&g, &r);
            assert_eq!(xs.len(), 0);
        }

        #[test]
        fn intersecting_nonempty_group() -> Result<(), Box<dyn std::error::Error>> {
            let s1 = Shape::sphere();
            let s2 = ShapeBuilder::sphere()
                .transform(translation(0., 0., -3.))
                .build()?;
            let s3 = ShapeBuilder::sphere()
                .transform(translation(5., 0., 0.))
                .build()?;
            let g = ShapeBuilder::group()
                .add_child(s1.clone())
                .add_child(s2.clone())
                .add_child(s3.clone())
                .build()?;
            let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
            let xs = intersect(&g, &r);
            assert_eq!(xs.len(), 4);
            assert_eq!(xs[0].object, &s2);
            assert_eq!(xs[1].object, &s2);
            assert_eq!(xs[2].object, &s1);
            assert_eq!(xs[3].object, &s1);
            Ok(())
        }
        #[test]
        fn intersecting_transformed_group() -> Result<(), Box<dyn std::error::Error>> {
            let g = ShapeBuilder::group()
                .transform(scaling(2., 2., 2.))
                .add_child(
                    ShapeBuilder::sphere()
                        .transform(translation(5., 0., 0.))
                        .build()?,
                )
                .build()?;
            let r = Ray::new(point(10., 0., -10.), vector(0., 0., 1.));
            let xs = intersect(&g, &r);
            assert_eq!(xs.len(), 2);
            Ok(())
        }
    }
}
#[cfg(test)]
mod tests {
    mod shape_builder {
        use std::error::Error;

        use crate::shapes::{plane, sphere, test_shape, Shape};

        #[test]
        fn plane_builder() -> Result<(), Box<dyn Error>> {
            assert_eq!(plane().build()?, Shape::plane());
            Ok(())
        }
        #[test]
        fn sphere_builder() -> Result<(), Box<dyn Error>> {
            assert_eq!(sphere().build()?, Shape::sphere());
            Ok(())
        }
        #[test]
        fn test_shape_builder() -> Result<(), Box<dyn Error>> {
            assert_eq!(test_shape().build()?, Shape::test_shape());
            Ok(())
        }
    }
    mod shape {
        use crate::{
            materials::Material,
            matrices::{identity, translation},
            shapes::Shape,
        };

        #[test]
        fn test_shape() {
            let mut s = Shape::test_shape();
            // The default transform.
            assert_eq!(s.transform(), identity());
            // The default material.
            assert_eq!(s.material, Material::default());
            // Assigning a material.
            let m = Material {
                ambient: 1.,
                ..Material::default()
            };
            s.material = m.clone();
            assert_eq!(s.material, m);
        }
        #[test]
        fn sphere() {
            // A sphere's default transform is the identity matrix.
            let s = Shape::sphere();
            assert_eq!(s.transform(), identity());

            // It can be changed by directly setting the transform member.
            let mut s = Shape::sphere();
            let t = translation(2., 3., 4.);
            s.set_transform(t.clone());
            assert_eq!(s.transform(), t);

            // Default Sphere has the default material.
            assert_eq!(s.material, Material::default());
            // It can be overridden.
            let mut s = Shape::sphere();
            let mut m = Material::default();
            m.ambient = 1.;
            s.material = m.clone();
            assert_eq!(s.material, m);
        }
    }
    mod normal_at {
        use crate::{float::consts::PI, matrices::Matrix4x4, shapes::Shape, tuples::Tuple, Float};
        #[test]
        fn compute_normal_on_translated_shape() {
            // Computing the normal on a translated shape.
            let mut s = Shape::test_shape();
            s.set_transform(Matrix4x4::translation(0., 1., 0.));
            let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711));
            assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711));
        }
        #[test]
        fn compute_normal_on_scaled_shape() {
            // Computing the normal on a scaled shape.
            let mut s = Shape::test_shape();
            s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.));
            let n = s.normal_at(Tuple::point(
                0.,
                (2. as Float).sqrt() / 2.,
                -(2. as Float).sqrt() / 2.,
            ));
            assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254));
        }
        #[test]
        fn sphere_normal_on_x_axis() {
            // Normal on X-axis
            let s = Shape::sphere();
            let n = s.normal_at(Tuple::point(1., 0., 0.));
            assert_eq!(n, Tuple::vector(1., 0., 0.));
        }
        #[test]
        fn sphere_normal_on_y_axis() {
            // Normal on Y-axis
            let s = Shape::sphere();
            let n = s.normal_at(Tuple::point(0., 1., 0.));
            assert_eq!(n, Tuple::vector(0., 1., 0.));
        }
        #[test]
        fn sphere_normal_on_z_axis() {
            // Normal on Z-axis
            let s = Shape::sphere();
            let n = s.normal_at(Tuple::point(0., 0., 1.));
            assert_eq!(n, Tuple::vector(0., 0., 1.));
        }
        #[test]
        fn sphere_normal_non_axial() {
            // Normal on a sphere at a nonaxial point.
            let s = Shape::sphere();
            let n = s.normal_at(Tuple::point(
                (3. as Float).sqrt() / 3.,
                (3. as Float).sqrt() / 3.,
                (3. as Float).sqrt() / 3.,
            ));
            assert_eq!(
                n,
                Tuple::vector(
                    (3. as Float).sqrt() / 3.,
                    (3. as Float).sqrt() / 3.,
                    (3. as Float).sqrt() / 3.,
                )
            );
        }
        #[test]
        fn normals_are_normalized() {
            // Normals returned are normalized.
            let s = Shape::sphere();
            let n = s.normal_at(Tuple::point(
                (3. as Float).sqrt() / 3.,
                (3. as Float).sqrt() / 3.,
                (3. as Float).sqrt() / 3.,
            ));
            assert_eq!(n, n.normalize());
        }
        #[test]
        fn compute_normal_on_translated_sphere() {
            // Compute the normal on a translated sphere.
            let mut s = Shape::sphere();
            s.set_transform(Matrix4x4::translation(0., 1., 0.));
            let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711));
            assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711));
        }
        #[test]
        fn compute_normal_on_scaled_sphere() {
            // Compute the normal on a transformed sphere.
            let mut s = Shape::sphere();
            s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.));
            let n = s.normal_at(Tuple::point(
                0.,
                (2. as Float).sqrt() / 2.,
                -(2. as Float).sqrt() / 2.,
            ));
            assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254));
        }
        #[test]
        fn nomal_of_plane_constant() {
            // Normal of a plane is constant everywhere.
            let p = Shape::plane();
            assert_eq!(
                p.normal_at(Tuple::point(0., 0., 0.)),
                Tuple::vector(0., 1., 0.)
            );
            assert_eq!(
                p.normal_at(Tuple::point(10., 0., -10.)),
                Tuple::vector(0., 1., 0.)
            );
            assert_eq!(
                p.normal_at(Tuple::point(-5., 0., 150.)),
                Tuple::vector(0., 1., 0.)
            );
        }
    }
    mod intersect {
        use crate::{
            intersections::{Intersection, Intersections},
            matrices::Matrix4x4,
            rays::Ray,
            shapes::{intersect, Geometry, Shape},
            tuples::Tuple,
        };

        #[test]
        fn scaled_shape_with_ray() {
            // Intersecting a scaled shape with a ray.
            let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
            let mut s = Shape::test_shape();
            s.set_transform(Matrix4x4::scaling(2., 2., 2.));
            let _xs = intersect(&s, &r);
            if let Geometry::TestShape(data) = s.geometry() {
                if let Some(ray) = &data.lock().unwrap().saved_ray {
                    assert_eq!(ray.origin, Tuple::point(0., 0., -2.5));
                    assert_eq!(ray.direction, Tuple::vector(0., 0., 0.5));
                } else {
                    panic!("ray wasn't set");
                };
            } else {
                panic!("test_shape returned a non-TestShape geometry")
            };
        }
        #[test]
        fn translated_shape_with_ray() {
            // Intersecting a translated shape with a ray.
            let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
            let mut s = Shape::test_shape();
            s.set_transform(Matrix4x4::translation(5., 0., 0.));
            let _xs = intersect(&s, &r);
            if let Geometry::TestShape(data) = s.geometry() {
                if let Some(ray) = &data.lock().unwrap().saved_ray {
                    assert_eq!(ray.origin, Tuple::point(-5., 0., -5.));
                    assert_eq!(ray.direction, Tuple::vector(0., 0., 1.));
                } else {
                    panic!("ray wasn't set");
                };
            } else {
                panic!("test_shape returned a non-TestShape geometry")
            };
        }
        #[test]
        fn ray_intersects_sphere_two_points() {
            // A ray intersects a sphere in two points.
            let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
            let s = Shape::sphere();
            let xs = intersect(&s, &r);
            assert_eq!(
                xs,
                Intersections::new(vec![Intersection::new(4., &s), Intersection::new(6., &s)])
            );
        }
        #[test]
        fn ray_intersects_at_tangent() {
            // A ray intersects a sphere at a tangent.
            let r = Ray::new(Tuple::point(0., 2., -5.), Tuple::vector(0., 0., 1.));
            let s = Shape::sphere();
            let xs = intersect(&s, &r);
            assert_eq!(xs, Intersections::default());
        }
        #[test]
        fn ray_originates_inside_sphere() {
            // A ray originates inside a sphere.
            let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
            let s = Shape::sphere();
            let xs = intersect(&s, &r);
            assert_eq!(
                xs,
                Intersections::new(vec![Intersection::new(-1., &s), Intersection::new(1., &s)])
            );
        }
        #[test]
        fn sphere_behind_ray() {
            // A sphere is behind a ray.
            let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
            let s = Shape::sphere();
            let xs = intersect(&s, &r);
            assert_eq!(
                xs,
                Intersections::new(vec![Intersection::new(-6., &s), Intersection::new(-4., &s)])
            );
        }
        #[test]
        fn ray_intersects_scaled_sphere() {
            // Intersect a scaled sphere with a ray.
            let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
            let mut s = Shape::sphere();
            s.set_transform(Matrix4x4::scaling(2., 2., 2.));
            let xs = intersect(&s, &r);
            assert_eq!(xs.len(), 2, "xs {:?}", xs);
            assert_eq!(xs[0].t, 3., "xs {:?}", xs);
            assert_eq!(xs[1].t, 7., "xs {:?}", xs);
        }
        #[test]
        fn ray_intersects_translated_sphere() {
            // Intersect a translated sphere with a ray.
            let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
            let mut s = Shape::sphere();
            s.set_transform(Matrix4x4::translation(5., 0., 0.));
            let xs = intersect(&s, &r);
            assert_eq!(xs.len(), 0);
        }
        #[test]
        fn ray_parallel_to_plane() {
            // Intersect with a ray parallel to the plane.
            let p = Shape::plane();
            let r = Ray::new(Tuple::point(0., 10., 0.), Tuple::vector(0., 0., 1.));
            let xs = intersect(&p, &r);
            assert_eq!(xs.len(), 0);
        }
        #[test]
        fn ray_coplanar_to_plane() {
            // Intersect with a coplanar.
            let p = Shape::plane();
            let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
            let xs = intersect(&p, &r);
            assert_eq!(xs.len(), 0);
        }
        #[test]
        fn ray_intersects_plane_from_above() {
            // A ray intersecting a plane from above.
            let p = Shape::plane();
            let r = Ray::new(Tuple::point(0., 1., 0.), Tuple::vector(0., -1., 0.));
            let xs = intersect(&p, &r);
            assert_eq!(xs.len(), 1);
            assert_eq!(xs[0].t, 1.);
            assert_eq!(xs[0].object, &p);
        }
        #[test]
        fn ray_intersects_plane_from_below() {
            // A ray intersecting a plane from below.
            let p = Shape::plane();
            let r = Ray::new(Tuple::point(0., -1., 0.), Tuple::vector(0., 1., 0.));
            let xs = intersect(&p, &r);
            assert_eq!(xs.len(), 1);
            assert_eq!(xs[0].t, 1.);
            assert_eq!(xs[0].object, &p);
        }
    }
}
