use parry2d::{math::Point, query::Ray, shape::Segment};
pub(crate) fn which_side(eye: Point<f32>, old: Point<f32>, new: Point<f32>) -> Side {
    debug_assert!(Point::from(old - eye) != Point::origin());
    let perp: f32 = (eye - old).perp(&(eye - new));
    if perp < 0. {
        Side::Left
    } else if perp > 0. {
        Side::Right
    } else {
        Side::Straight
    }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Side {
    Left,
    Straight,
    Right,
}
#[derive(Clone, Copy)]
pub(crate) struct RayProjection {
    parameter: Option<f32>,
    endpoint_a_side: SimpleSide,
}
impl RayProjection {
    pub(crate) fn calculate(ray: Ray, target: Segment) -> Self {
        let segment_dir = target.scaled_direction();
        let origin_diff = target.a - ray.origin;
        let ray_perp_origin = ray.dir.perp(&origin_diff);
        let ray_perp_dir = ray.dir.perp(&segment_dir);
        let dir_perp_origin = segment_dir.perp(&origin_diff);
        let is_parallel = ray_perp_dir.abs() < 0.0001;
        let is_behind = dir_perp_origin * ray_perp_dir > 0.;
        let parameter = if is_parallel || is_behind {
            None
        } else {
            let parameter = -ray_perp_origin / ray_perp_dir;
            if (0. ..=1.).contains(¶meter) {
                Some(parameter)
            } else {
                None
            }
        };
        let endpoint_a_side = if ray_perp_origin < 0. {
            SimpleSide::Left
        } else if ray_perp_origin > 0. {
            SimpleSide::Right
        } else if ray.dir.perp(&(target.b - ray.origin)) > 0. {
            SimpleSide::Left
        } else {
            SimpleSide::Right
        };
        Self::new(parameter, endpoint_a_side)
    }
    pub(crate) fn new(parameter: Option<f32>, endpoint_a_side: SimpleSide) -> Self {
        #[cfg(debug_assertions)]
        if let Some(parameter) = parameter {
            assert!(parameter.is_finite());
            assert!(0. <= parameter);
            assert!(parameter <= 1.);
        }
        Self {
            parameter,
            endpoint_a_side,
        }
    }
    pub(crate) fn parameter(&self) -> Option<f32> {
        self.parameter
    }
    pub(crate) fn endpoint_a_side(&self) -> SimpleSide {
        self.endpoint_a_side
    }
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum SimpleSide {
    Left,
    Right,
}
impl PartialEq<Side> for SimpleSide {
    fn eq(&self, other: &Side) -> bool {
        match self {
            Self::Left => *other == Side::Left,
            Self::Right => *other == Side::Right,
        }
    }
}
#[cfg(test)]
mod tests {
    use nalgebra::Vector2;
    use super::*;
    #[test]
    fn test_which_side() {
        let eye = Point::new(-1., 2.);
        let a = Point::new(5., -6.);
        let b = Point::new(5., 6.);
        let c = Point::new(-5., 6.);
        let d = Point::new(-5., -6.);
        assert_eq!(which_side(eye, a, b), Side::Right);
        assert_eq!(which_side(eye, b, c), Side::Right);
        assert_eq!(which_side(eye, c, d), Side::Right);
        assert_eq!(which_side(eye, d, a), Side::Right);
        assert_eq!(which_side(eye, b, a), Side::Left);
        assert_eq!(which_side(eye, c, b), Side::Left);
        assert_eq!(which_side(eye, d, c), Side::Left);
        assert_eq!(which_side(eye, a, d), Side::Left);
        assert_eq!(which_side(eye, a, a), Side::Straight);
        assert_eq!(which_side(Point::origin(), a, 0.5 * a), Side::Straight);
    }
    #[test]
    fn test_ray_projection() {
        let segment = Segment::new(Point::new(3., 1.), Point::new(1., 3.));
        let proj =
            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(1., 1.)), segment);
        assert_eq!(0.5, proj.parameter().unwrap());
        assert_eq!(proj.endpoint_a_side(), SimpleSide::Left);
        let proj =
            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(2., 2.)), segment);
        assert_eq!(0.5, proj.parameter().unwrap());
        assert_eq!(proj.endpoint_a_side(), SimpleSide::Left);
        let proj =
            RayProjection::calculate(Ray::new(Point::new(2., 1.), Vector2::new(1., 0.)), segment);
        assert_eq!(0., proj.parameter().unwrap());
        assert_eq!(proj.endpoint_a_side(), SimpleSide::Left);
        let proj = RayProjection::calculate(
            Ray::new(Point::new(2., 1.), Vector2::new(1., -0.5)),
            segment,
        );
        assert!(proj.parameter().is_none());
        assert_eq!(proj.endpoint_a_side(), SimpleSide::Right);
        let proj =
            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(1., -1.)), segment);
        assert!(proj.parameter().is_none());
        let proj =
            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(-1., 1.)), segment);
        assert!(proj.parameter().is_none());
        let proj =
            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(0., -3.)), segment);
        assert!(proj.parameter().is_none());
        assert_eq!(proj.endpoint_a_side(), SimpleSide::Right);
        let proj =
            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(-3., 0.)), segment);
        assert!(proj.parameter().is_none());
        assert_eq!(proj.endpoint_a_side(), SimpleSide::Left);
    }
}