#![allow(dead_code)]

use crate::constants::CIRCLE_TOLERANCE;
use crate::point::point;
use crate::utils::diff_of_prod;
use crate::{circle::Circle, point::Point};

// #00019
/// Configuration for circle-circle intersection results.
#[derive(Debug, PartialEq)]
pub enum CircleCircleConfig {
    NoIntersection(),
    NoncocircularOnePoint(Point),         // point[0]
    NoncocircularTwoPoints(Point, Point), // point[0], point[1]
    SameCircles(),
}

const ZERO: f64 = 0f64;

/// Computes the intersection of two circles.
///
/// This function determines the intersection points of two circles defined by their centers and radii.
///
/// # Arguments
/// * `circle0` - The first circle.
/// * `circle1` - The second circle.
///
/// # Returns
/// Returns a `CircleCircleConfig` enum indicating the result of the intersection test.
///
/// # Circle Intersection Logic
/// The circles are defined by the equations:
/// /// - |X - C0| = R0
/// /// - |X - C1| = R1
/// /// The vector U = C1 - C0 is computed, and its squared length is used to determine the relationship between the circles.
///
/// # Conditions for Intersection
/// The circles can have the following relationships:
///     - **Same Circles**: If the centers and radii are identical, they are the same circle.
///     - **No Intersection**: If the distance between centers is greater than the sum of the radii or less than the absolute difference of the radii, they do not intersect.
///     - **Tangent Circles**: If the distance equals the sum or absolute difference of the radii, they touch at one point.
///     - **Intersecting Circles**: If the distance is strictly between the absolute difference and sum of the radii, they intersect at two points.
///
/// # Examples
/// ```
/// use togo::prelude::*;
///
/// let circle0 = circle(point(0.0, 0.0), 1.0);
/// let circle1 = circle(point(1.0, 0.0), 1.0);
/// let result = int_circle_circle(circle0, circle1);
/// // result should be CircleCircleConfig::NoncocircularTwoPoints(Point(0.5, 0.8660254037844386), Point(0.5, -0.8660254037844386))
/// let circle0 = circle(point(0.0, 0.0), 1.0);
/// let circle1 = circle(point(2.0, 0.0), 1.0);
/// let result = int_circle_circle(circle0, circle1);
/// // result should be CircleCircleConfig::NoIntersection()
/// ```
pub fn int_circle_circle(circle0: Circle, circle1: Circle) -> CircleCircleConfig {
    debug_assert!(circle0.r.is_finite());
    debug_assert!(circle1.r.is_finite());

    let u = circle1.c - circle0.c;
    let usqr_len = u.dot(u);
    let r0 = circle0.r;
    let r1 = circle1.r;
    let r0_m_r1 = r0 - r1;
    
    if usqr_len < CIRCLE_TOLERANCE * CIRCLE_TOLERANCE && r0_m_r1.abs() < CIRCLE_TOLERANCE {
        // Circles are the same (or nearly identical within tolerance)
        return CircleCircleConfig::SameCircles();
    }

    let r0_m_r1_sqr = r0_m_r1 * r0_m_r1;
    if usqr_len < r0_m_r1_sqr {
        // The circles do not intersect.
        return CircleCircleConfig::NoIntersection();
    }

    let r0_p_r1 = r0 + r1;
    let r0_p_r1_sqr = r0_p_r1 * r0_p_r1;
    if usqr_len > r0_p_r1_sqr {
        // The circles do not intersect.
        return CircleCircleConfig::NoIntersection();
    }

    if usqr_len < r0_p_r1_sqr {
        if r0_m_r1_sqr < usqr_len {
            //let inv_usqr_len = 1.0 / usqr_len;
            // let s = 0.5 * ((r0 * r0 - r1 * r1) * inv_usqr_len + 1.0);
            let s = 0.5 * (diff_of_prod(r0, r0, r1, r1) / usqr_len + 1.0);
            //let s = 0.5 * (((r1 + r0) / (usqr_len / (r0 - r1))) + 1.0);
            //let s = (0.5 / (usqr_len / (r1 + r0))).mul_add(r0 - r1, 0.5);

            // In theory, discr is nonnegative.  However, numerical round-off
            // errors can make it slightly negative.  Clamp it to zero.
            //let mut discr = r0 * r0 * inv_usqr_len - s * s;
            let mut discr = diff_of_prod(r0 / usqr_len, r0, s, s);
            //println!("{:.40} {:.40}", discr, discr);
            if discr < ZERO {
                discr = ZERO;
            }
            let t = discr.sqrt();
            let v = point(u.y, -u.x);
            let tmp = circle0.c + u * s;
            let p0 = tmp - v * t;
            let p1 = tmp + v * t;
            
            // Bounds check: intersection points should have finite coordinates.
            // If they are non-finite (inf, -inf, NaN), it indicates numerical issues.
            if !p0.x.is_finite() || !p0.y.is_finite() || !p1.x.is_finite() || !p1.y.is_finite() {
                return CircleCircleConfig::NoIntersection();
            }
            
            if t > 0f64 {
                CircleCircleConfig::NoncocircularTwoPoints(p0, p1)
            } else {
                // t==0.0
                CircleCircleConfig::NoncocircularOnePoint(p0)
            }
        } else {
            // |U| = |R0-R1|, circles are tangent.
            let p0 = circle0.c + u * (r0 / r0_m_r1);
            
            // Bounds check: tangent point should have finite coordinates
            if !p0.x.is_finite() || !p0.y.is_finite() {
                return CircleCircleConfig::NoIntersection();
            }
            
            CircleCircleConfig::NoncocircularOnePoint(p0)
        }
    } else {
        // |U| = |R0+R1|, circles are tangent.
        let p0 = circle0.c + u * (r0 / r0_p_r1);
        
        // Bounds check: tangent point should have finite coordinates
        if !p0.x.is_finite() || !p0.y.is_finite() {
            return CircleCircleConfig::NoIntersection();
        }
        
        CircleCircleConfig::NoncocircularOnePoint(p0)
    }
}

/// Circle Circle intersect
#[cfg(test)]
mod tests_circle {
    use super::*;
    use crate::circle::circle;

    // short funtion
    fn ff(circle0: Circle, circle1: Circle) -> CircleCircleConfig {
        int_circle_circle(circle0, circle1)
    }

    #[test]
    fn test_same_circles_01() {
        let circle0 = circle(point(100.0, -100.0), 1.0);
        let circle1 = circle(point(100.0, -100.0), 1.0);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
    }

    #[test]
    fn test_same_non_intersection_01() {
        let circle0 = circle(point(1000.0, -1000.0), 1.01);
        let circle1 = circle(point(1000.0, -1000.0), 1.0);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::NoIntersection());
    }

    #[test]
    fn test_same_non_intersection_02() {
        let circle0 = circle(point(1000.0, -1000.0), 1.0);
        let circle1 = circle(point(1002.0, -1002.0), 1.0);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::NoIntersection());
    }

    #[test]
    fn test_noncircular_two_points() {
        let eps = f64::EPSILON * 10.0;
        let circle0 = circle(point(10.0, -10.0), 1.0);
        let circle1 = circle(point(10.0, -12.0 + eps), 1.0);
        let point0 = point(10.000000042146848, -11.0);
        let point1 = point(9.999999957853152, -11.0);
        let res = ff(circle0, circle1);
        assert_eq!(
            res,
            CircleCircleConfig::NoncocircularTwoPoints(point0, point1)
        );
    }

    #[test]
    fn test_noncircular_one_point_01() {
        let eps = f64::EPSILON * 2.0;
        let circle0 = circle(point(10.0, -10.0), 1.0);
        let circle1 = circle(point(10.0, -12.0 + eps), 1.0);
        let point0 = point(10.0, -11.0);
        let res = ff(circle0, circle1);
        assert_eq!(res, CircleCircleConfig::NoncocircularOnePoint(point0));
    }

    #[test]
    fn test_noncircular_one_point_02() {
        let circle0 = circle(point(10.0, -10.0), 1.0);
        let circle1 = circle(point(10.0, -10.5), 0.5);
        let point0 = point(10.0, -11.0);
        let res = ff(circle0, circle1);
        assert_eq!(res, CircleCircleConfig::NoncocircularOnePoint(point0));
    }

    #[test]
    fn test_noncircular_two_points_1() {
        let eps = f64::EPSILON * 5.0;
        let circle0 = circle(point(10.0, -10.0), 1.0);
        let circle1 = circle(point(10.0, -10.5 - eps), 0.5);
        let point0 = point(10.000000059604645, -10.999999999999998);
        let point1 = point(9.999999940395355, -10.999999999999998);
        let res = ff(circle0, circle1);
        assert_eq!(
            res,
            CircleCircleConfig::NoncocircularTwoPoints(point0, point1)
        );
    }

    #[test]
    // Test for numerical stability
    fn test_noncircular_one_point_03() {
        let eps = f64::EPSILON * 2.0;
        let circle0 = circle(point(1000.0, -1000.0), 100.0);
        let circle1 = circle(point(1000.0, -1200.0 + eps), 100.0);
        let point0 = point(1000.0, -1100.0);
        let res = ff(circle0, circle1);
        assert_eq!(res, CircleCircleConfig::NoncocircularOnePoint(point0));
    }

    #[test]
    // Test tolerance boundary at CIRCLE_TOLERANCE = 1e-10
    fn test_tolerance_boundary_within_tolerance() {
        // Circles separated by distance < 1e-10 with same radii should be treated as same
        let tolerance = 1e-10;
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(tolerance * 0.5, 0.0), 1.0); // Well within tolerance
        let res = ff(circle0, circle1);
        assert_eq!(res, CircleCircleConfig::SameCircles());
    }

    #[test]
    // Test tolerance boundary just outside tolerance
    fn test_tolerance_boundary_outside_tolerance() {
        // Circles separated by distance > 1e-10 with same radii should intersect
        let tolerance = 1e-10;
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(tolerance * 2.0, 0.0), 1.0); // Beyond tolerance
        let res = ff(circle0, circle1);
        // Should have two intersection points (circles overlap)
        match res {
            CircleCircleConfig::NoncocircularTwoPoints(_, _) => {
                // Correct: circles overlap with two points
            }
            _ => panic!("Expected two intersection points for circles beyond tolerance"),
        }
    }

    #[test]
    // Test nearly identical radii at tolerance boundary
    fn test_radius_tolerance_boundary() {
        let tolerance = 1e-10;
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(0.0, 0.0), 1.0 + tolerance * 0.5); // Radius diff < tolerance
        let res = ff(circle0, circle1);
        // Should be treated as same circle (within tolerance)
        assert_eq!(res, CircleCircleConfig::SameCircles());
    }

    #[test]
    // Test one circle inside another without touching
    fn test_one_inside_other_no_intersection() {
        let circle0 = circle(point(0.0, 0.0), 10.0);
        let circle1 = circle(point(0.0, 0.0), 5.0);
        let res = ff(circle0, circle1);
        assert_eq!(res, CircleCircleConfig::NoIntersection());
    }

    #[test]
    // Test external tangency
    fn test_external_tangent() {
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(2.0, 0.0), 1.0); // Distance = sum of radii
        let res = ff(circle0, circle1);
        match res {
            CircleCircleConfig::NoncocircularOnePoint(_) => {
                // Correct: external tangent
            }
            _ => panic!("Expected one point for external tangent circles"),
        }
    }

    #[test]
    // Test internal tangency
    fn test_internal_tangent() {
        let circle0 = circle(point(0.0, 0.0), 2.0);
        let circle1 = circle(point(1.0, 0.0), 1.0); // Distance = |r0 - r1|
        let res = ff(circle0, circle1);
        match res {
            CircleCircleConfig::NoncocircularOnePoint(_) => {
                // Correct: internal tangent
            }
            _ => panic!("Expected one point for internal tangent circles"),
        }
    }

    #[test]
    // Test circles with zero radius (degenerate points)
    fn test_circles_with_zero_radius_same_center() {
        let circle0 = circle(point(0.0, 0.0), 0.0);
        let circle1 = circle(point(0.0, 0.0), 0.0);
        let res = ff(circle0, circle1);
        assert_eq!(res, CircleCircleConfig::SameCircles());
    }

    #[test]
    // Test circles with zero radius at different centers
    fn test_circles_with_zero_radius_different_center() {
        let circle0 = circle(point(0.0, 0.0), 0.0);
        let circle1 = circle(point(1.0, 1.0), 0.0);
        let res = ff(circle0, circle1);
        assert_eq!(res, CircleCircleConfig::NoIntersection());
    }
}

#[cfg(test)]
mod tests_circle_old {

    // Old tests
    ///////////////////////
    use super::*;
    use crate::{circle::circle, utils::perturbed_ulps_as_int};

    // short funtion
    fn ff(circle0: Circle, circle1: Circle) -> CircleCircleConfig {
        int_circle_circle(circle0, circle1)
    }

    #[test]
    fn test_same_circles01() {
        // Circles are the same
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(0.0, 0.0), 1.0);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
    }
    #[test]
    fn test_same_circles02() {
        // Circles are the same, radius zero
        let circle0 = circle(point(0.0, 0.0), 0.0);
        let circle1 = circle(point(0.0, 0.0), 0.0);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
    }
    #[test]
    fn test_same_circles03() {
        // Circles are the same, radius is large
        let circle0 = circle(point(0.0, 0.0), f64::MAX);
        let circle1 = circle(point(0.0, 0.0), f64::MAX);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
    }
    #[test]
    fn test_same_circles04() {
        // Circles are the same, center is large
        let circle0 = circle(point(f64::MAX, f64::MAX), f64::MAX);
        let circle1 = circle(point(f64::MAX, f64::MAX), f64::MAX);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
    }

    #[test]
    fn test_donot_intersect01() {
        // The circles do not intersect.
        let r = perturbed_ulps_as_int(1.0, -2);
        let circle0 = circle(point(-1.0, 0.0), r);
        let circle1 = circle(point(1.0, 0.0), 1.0);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::NoIntersection());
    }
    #[test]
    fn test_donot_intersect02() {
        // The circles do not intersect.
        let x = perturbed_ulps_as_int(1.0, 2);
        let circle0 = circle(point(-1.0, 0.0), 1.0);
        let circle1 = circle(point(x, 0.0), 1.0);
        assert_eq!(ff(circle0, circle1), CircleCircleConfig::NoIntersection());
    }

    #[test]
    fn test_tangent01() {
        // The circles touch in one point.
        let x = perturbed_ulps_as_int(1.0, 1);
        let circle0 = circle(point(-1.0, 0.0), 1.0);
        let circle1 = circle(point(x, 0.0), 1.0);
        assert_eq!(
            ff(circle0, circle1),
            CircleCircleConfig::NoncocircularOnePoint(point(0.0, 0.0))
        );
    }

    #[test]
    fn test_tangent02() {
        // Circles with larger shift to exceed tolerance and intersect in two points
        let circle0 = circle(point(1.0, 0.0), 1.0);
        let circle1 = circle(point(1.0 + 1e-9, 0.0), 1.0); // Beyond CIRCLE_TOLERANCE (1e-10)
        let res = int_circle_circle(circle0, circle1);
        // Now they intersect in two points (execution path: usqr_len < r0_p_r1_sqr && r0_m_r1_sqr < usqr_len)
        match res {
            CircleCircleConfig::NoncocircularTwoPoints(p0, p1) => {
                // Points shift slightly due to offset, but still two distinct points
                assert!((p0.x - 1.0).abs() < 1e-7);
                assert!((p0.y - 1.0).abs() < 1e-7);
                assert!((p1.x - 1.0).abs() < 1e-7);
                assert!((p1.y + 1.0).abs() < 1e-7);
            }
            _ => panic!("Expected two intersection points"),
        }
    }

    #[test]
    fn test_tangent03() {
        // Circles with very small shift are same (cocircular)
        let _0 = perturbed_ulps_as_int(0.0, 1);
        let _1 = perturbed_ulps_as_int(1.0, -1);
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(_0, 0.0), 1.0);
        let res = ff(circle0, circle1);
        assert_eq!(res, CircleCircleConfig::SameCircles());
    }

    #[test]
    fn test_tangent04() {
        // Different radius beyond tolerance to trigger no intersection path
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(0.0, 0.0), 1.0 - 1e-9); // Beyond CIRCLE_TOLERANCE
        let res = ff(circle0, circle1);
        // Now circles have different radii beyond tolerance, no intersection (execution path: usqr_len < r0_m_r1_sqr)
        assert_eq!(res, CircleCircleConfig::NoIntersection());
    }

    #[test]
    fn test_tangent05() {
        // Differences that create internal tangency (execution path: |U| = |R0-R1|)
        let circle0 = circle(point(1.0, 0.0), 1.0);
        let circle1 = circle(point(1.0 + 5e-10, 0.0), 1.0 - 5e-10); // Creates tangency
        let res = ff(circle0, circle1);
        // Circles are internally tangent
        match res {
            CircleCircleConfig::NoncocircularOnePoint(p) => {
                // Internal tangency at approximately (2.0, 0.0)
                assert!((p.x - 2.0).abs() < 1e-8);
                assert!((p.y - 0.0).abs() < 1e-8);
            }
            _ => panic!("Expected tangent point, got {:?}", res),
        }
    }

    #[test]
    fn test_tangent06() {
        // Smaller differences - just beyond tolerance to get two intersection points
        let circle0 = circle(point(1.0, 0.0), 1.0);
        let circle1 = circle(point(1.0 + 1e-9, 0.0), 1.0 - 1e-9); // Beyond tolerance with overlap
        let res = ff(circle0, circle1);
        // Two distinct intersection points (execution path: usqr_len < r0_p_r1_sqr && r0_m_r1_sqr < usqr_len)
        match res {
            CircleCircleConfig::NoncocircularTwoPoints(_, _) => {
                // Correct: two intersection points
            }
            _ => panic!("Expected two intersection points, got {:?}", res),
        }
    }

    #[test]
    fn test_no_intersection2() {
        let c0 = circle(point(0.5, 0.0), 0.5);
        let c1 = circle(point(-1.0, 0.0), 1.0);
        let res = ff(c0, c1);
        assert_eq!(
            res,
            CircleCircleConfig::NoncocircularOnePoint(point(0.0, 0.0))
        );
    }

    use crate::svg::svg;
    #[test]
    fn test_intersection_issue_01() {
        let mut svg = svg(150.0, 200.0);
        let c0 = circle(point(100.0, 130.0), 20.0);
        let c1 = circle(point(75.0, 40.0), 85.0);
        svg.circle(&c0, "red");
        svg.circle(&c1, "blue");
        let p0 = point(113.87064429562277, 115.59148769566033);
        let p1 = point(80.68522962987866, 124.80965843614482);

        svg.circle(&circle(p0, 1.0), "red");
        svg.write();
        let res = ff(c0, c1);
        assert_eq!(res, CircleCircleConfig::NoncocircularTwoPoints(p0, p1));
    }

    #[test]
    fn test_external_tangent_exact() {
        // Test external tangency: |U| == R0 + R1 (execution path: usqr_len == r0_p_r1_sqr)
        // This tests the else branch at line 117 (line 118 in the function)
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(2.0, 0.0), 1.0); // Distance = 2.0 = 1.0 + 1.0 (exactly)
        let res = ff(circle0, circle1);
        match res {
            CircleCircleConfig::NoncocircularOnePoint(p) => {
                // External tangency at point (1.0, 0.0)
                assert!((p.x - 1.0).abs() < 1e-10);
                assert!((p.y - 0.0).abs() < 1e-10);
            }
            _ => panic!("Expected external tangent point, got {:?}", res),
        }
    }

    #[test]
    fn test_discriminant_zero_path() {
        // Test the path where t == 0.0 at line 112 (discriminant == 0 path)
        // This is when the discriminant computation produces exactly 0
        // For equal radii circles: s = 0.5
        // We need: s² = r0²/usqr_len, so 0.25 = r0²/usqr_len
        // Therefore: distance = 2*r0 (circles exactly opposite at equal distance)
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(2.0, 0.0), 1.0); // Distance = 2.0 = 2 * radius
        let res = ff(circle0, circle1);
        // This should produce t=0, giving NoncocircularOnePoint
        match res {
            CircleCircleConfig::NoncocircularOnePoint(p) => {
                // Tangent point should be at (1.0, 0.0)
                assert!((p.x - 1.0).abs() < 1e-10);
                assert!((p.y - 0.0).abs() < 1e-10);
            }
            _ => panic!("Expected NoncocircularOnePoint from discriminant zero path, got {:?}", res),
        }
    }

    #[test]
    fn test_bounds_check_non_finite_intersection_points() {
        // Test that non-finite intersection points are rejected for two-point case
        // Create circles with extreme coordinates that might produce non-finite results
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(1e100, 0.0), 1e100);
        let res = ff(circle0, circle1);
        // Should either be NoIntersection or have finite points
        match res {
            CircleCircleConfig::NoIntersection() => {
                // Acceptable - circles are far apart
            }
            CircleCircleConfig::NoncocircularOnePoint(p) => {
                assert!(p.x.is_finite() && p.y.is_finite(), "Point coordinates should be finite");
            }
            CircleCircleConfig::NoncocircularTwoPoints(p0, p1) => {
                assert!(p0.x.is_finite() && p0.y.is_finite(), "p0 should be finite");
                assert!(p1.x.is_finite() && p1.y.is_finite(), "p1 should be finite");
            }
            _ => {}
        }
    }

    #[test]
    fn test_bounds_check_tangent_non_finite_point() {
        // Test that non-finite tangent points are rejected
        let circle0 = circle(point(0.0, 0.0), 1.0);
        let circle1 = circle(point(2.0, 0.0), 1e-100); // Very small circle far away
        let res = ff(circle0, circle1);
        // Should handle gracefully
        match res {
            CircleCircleConfig::NoIntersection() => {
                // Expected - circles don't intersect
            }
            CircleCircleConfig::NoncocircularOnePoint(p) => {
                assert!(p.x.is_finite() && p.y.is_finite(), "Tangent point should be finite");
            }
            _ => {}
        }
    }
}
