use crate::generic_math::{point, Point, Rect};
use crate::scalar::Scalar;
use crate::CubicBezierSegment;
use arrayvec::ArrayVec;
use std::ops::Range;
pub fn cubic_bezier_intersections_t<S: Scalar>(
    curve1: &CubicBezierSegment<S>,
    curve2: &CubicBezierSegment<S>,
) -> ArrayVec<[(S, S); 9]> {
    if !curve1.fast_bounding_rect().intersects(&curve2.fast_bounding_rect())
        || curve1 == curve2
        || (curve1.from == curve2.to
            && curve1.ctrl1 == curve2.ctrl2
            && curve1.ctrl2 == curve2.ctrl1
            && curve1.to == curve2.from)
    {
        return ArrayVec::new();
    }
    let mut result = ArrayVec::new();
    #[inline]
    fn midpoint<S: Scalar>(point1: &Point<S>, point2: &Point<S>) -> Point<S> {
        point((point1.x + point2.x) * S::HALF, (point1.y + point2.y) * S::HALF)
    }
    let curve1_is_a_point = curve1.is_a_point(S::EPSILON);
    let curve2_is_a_point = curve2.is_a_point(S::EPSILON);
    if curve1_is_a_point && !curve2_is_a_point {
        let point1 = midpoint(&curve1.from, &curve1.to);
        let curve_params = point_curve_intersections(&point1, &curve2, S::EPSILON);
        for t in curve_params {
            if t > S::EPSILON && t < S::ONE - S::EPSILON {
                result.push((S::ZERO, t));
            }
        }
    } else if !curve1_is_a_point && curve2_is_a_point {
        let point2 = midpoint(&curve2.from, &curve2.to);
        let curve_params = point_curve_intersections(&point2, &curve1, S::EPSILON);
        for t in curve_params {
            if t > S::EPSILON && t < S::ONE - S::EPSILON {
                result.push((t, S::ZERO));
            }
        }
    }
    if curve1_is_a_point || curve2_is_a_point {
        
        
        return result;
    }
    let linear1 = curve1.is_linear(S::EPSILON);
    let linear2 = curve2.is_linear(S::EPSILON);
    if linear1 && !linear2 {
        result = line_curve_intersections(curve1, curve2,  false);
    } else if !linear1 && linear2 {
        result = line_curve_intersections(curve2, curve1,  true);
    } else if linear1 && linear2 {
        result = line_line_intersections(curve1, curve2);
    } else {
        add_curve_intersections(
            &curve1, &curve2, &(S::ZERO..S::ONE), &(S::ZERO..S::ONE),
            &mut result,  false,  0,  0,
             &curve1,  &curve2,
        );
    }
    result
}
fn point_curve_intersections<S: Scalar>(
    pt: &Point<S>,
    curve: &CubicBezierSegment<S>,
    epsilon: S,
) -> ArrayVec<[S; 9]> {
    let mut result = ArrayVec::new();
    
    if (*pt - curve.from).square_length() < epsilon {
        result.push(S::ZERO);
        return result;
    }
    if (*pt - curve.to).square_length() < epsilon {
        result.push(S::ONE);
        return result;
    }
    let curve_x_t_params = curve.solve_t_for_x(pt.x);
    let curve_y_t_params = curve.solve_t_for_y(pt.y);
    
    
    
    
    let param_eps = S::TEN * epsilon;
    for params in [curve_x_t_params, curve_y_t_params].iter() {
        for t in params {
            let t = *t;
            if (*pt - curve.sample(t)).square_length() > epsilon {
                continue;
            }
            let mut already_found_t = false;
            for u in &result {
                if S::abs(t - *u) < param_eps {
                    already_found_t = true;
                    break;
                }
            }
            if !already_found_t {
                result.push(t);
            }
        }
    }
    if result.len() > 0 {
        return result;
    }
    
    
    
    
    
    #[inline]
    fn maybe_add<S: Scalar>(t: S, pt: &Point<S>, curve: &CubicBezierSegment<S>, epsilon: S,
                            result: &mut ArrayVec<[S; 9]>) -> bool
    {
        if (curve.sample(t) - *pt).square_length() < epsilon {
            result.push(t);
            return true;
        }
        false
    }
    let _ = maybe_add(curve.x_minimum_t(), pt, curve, epsilon, &mut result)
        || maybe_add(curve.x_maximum_t(), pt, curve, epsilon, &mut result)
        || maybe_add(curve.y_minimum_t(), pt, curve, epsilon, &mut result)
        || maybe_add(curve.y_maximum_t(), pt, curve, epsilon, &mut result);
    result
}
fn line_curve_intersections<S: Scalar>(
    line_as_curve: &CubicBezierSegment<S>,
    curve: &CubicBezierSegment<S>,
    flip: bool,
) -> ArrayVec<[(S, S); 9]> {
    let mut result = ArrayVec::new();
    let baseline = line_as_curve.baseline();
    let curve_intersections = curve.line_intersections_t(&baseline.to_line());
    let line_is_mostly_vertical =
        S::abs(baseline.from.y - baseline.to.y) >= S::abs(baseline.from.x - baseline.to.x);
    for curve_t in curve_intersections {
        let line_intersections = if line_is_mostly_vertical {
            let intersection_y = curve.y(curve_t);
            line_as_curve.solve_t_for_y(intersection_y)
        } else {
            let intersection_x = curve.x(curve_t);
            line_as_curve.solve_t_for_x(intersection_x)
        };
        for line_t in line_intersections {
            add_intersection(line_t, line_as_curve, curve_t, curve, flip, &mut result);
        }
    }
    result
}
fn line_line_intersections<S: Scalar>(
    curve1: &CubicBezierSegment<S>,
    curve2: &CubicBezierSegment<S>,
) -> ArrayVec<[(S, S); 9]> {
    let mut result = ArrayVec::new();
    let intersection = curve1.baseline().to_line().intersection(&curve2.baseline().to_line());
    if intersection.is_none() {
        return result;
    }
    let intersection = intersection.unwrap();
    #[inline]
    fn parameters_for_line_point<S: Scalar>(
        curve: &CubicBezierSegment<S>,
        pt: &Point<S>,
    ) -> ArrayVec<[S; 3]> {
        let line_is_mostly_vertical =
            S::abs(curve.from.y - curve.to.y) >= S::abs(curve.from.x - curve.to.x);
        if line_is_mostly_vertical {
            curve.solve_t_for_y(pt.y)
        } else {
            curve.solve_t_for_x(pt.x)
        }
    }
    let line1_params = parameters_for_line_point(&curve1, &intersection);
    if line1_params.len() == 0 {
        return result;
    }
    let line2_params = parameters_for_line_point(&curve2, &intersection);
    if line2_params.len() == 0 {
        return result;
    }
    for t1 in &line1_params {
        for t2 in &line2_params {
            
            
            add_intersection(*t1, curve1, *t2, curve2,  false, &mut result);
        }
    }
    result
}
fn add_curve_intersections<S: Scalar>(
    curve1: &CubicBezierSegment<S>,
    curve2: &CubicBezierSegment<S>,
    domain1: &Range<S>,
    domain2: &Range<S>,
    intersections: &mut ArrayVec<[(S, S); 9]>,
    flip: bool,
    mut recursion_count: u32,
    mut call_count: u32,
    orig_curve1: &CubicBezierSegment<S>,
    orig_curve2: &CubicBezierSegment<S>,
) -> u32  {
    call_count += 1;
    recursion_count += 1;
    if call_count >= 4096 || recursion_count >= 60 {
        return call_count;
    }
    let epsilon = if inputs_are_f32::<S>() { S::value(5e-6) } else { S::value(1e-9) };
    if domain2.start == domain2.end || curve2.is_a_point(S::ZERO) {
        add_point_curve_intersection(
            &curve2,  false, curve1, domain2, domain1, intersections, flip,
        );
        return call_count;
    } else if curve2.from == curve2.to {
        
        
        let new_2_curves = orig_curve2.split_range(domain2.clone()).split(S::HALF);
        let domain2_mid = (domain2.start + domain2.end) * S::HALF;
        call_count = add_curve_intersections(
            curve1, &new_2_curves.0, domain1, &(domain2.start..domain2_mid),
            intersections, flip, recursion_count, call_count,
            orig_curve1, orig_curve2,
        );
        call_count = add_curve_intersections(
            curve1, &new_2_curves.1, domain1, &(domain2_mid..domain2.end),
            intersections, flip, recursion_count, call_count,
            orig_curve1, orig_curve2,
        );
        return call_count;
    }
    
    
    if !rectangles_overlap(&curve1.fast_bounding_rect(), &curve2.fast_bounding_rect()) {
        return call_count;
    }
    let (t_min_clip, t_max_clip) = match restrict_curve_to_fat_line(curve1, curve2) {
        Some((min, max)) => (min, max),
        None => return call_count,
    };
    
    
    let new_domain1 =
        &(domain_value_at_t(&domain1, t_min_clip)..domain_value_at_t(&domain1, t_max_clip));
    if S::max(domain2.end - domain2.start, new_domain1.end - new_domain1.start) < epsilon {
        let t1 = (new_domain1.start + new_domain1.end) * S::HALF;
        let t2 = (domain2.start + domain2.end) * S::HALF;
        if inputs_are_f32::<S>() {
            
            
            
            let end_eps = S::value(1e-3);
            if (t2 < end_eps || t2 > S::ONE - end_eps)
                && (orig_curve1.sample(t1) - orig_curve2.sample(t2)).length() > S::FIVE
            {
                return call_count;
            }
        }
        add_intersection(t1, orig_curve1, t2, orig_curve2, flip, intersections);
        return call_count;
    }
    
    let curve1 = &orig_curve1.split_range(new_domain1.clone());
    
    
    
    if new_domain1.start == new_domain1.end || curve1.is_a_point(S::ZERO) {
        add_point_curve_intersection(
            &curve1,  true, curve2, new_domain1, domain2,
            intersections, flip,
        );
        return call_count;
    }
    
    if t_max_clip - t_min_clip > S::EIGHT / S::TEN {
        
        if new_domain1.end - new_domain1.start > domain2.end - domain2.start {
            let new_1_curves = curve1.split(S::HALF);
            let new_domain1_mid = (new_domain1.start + new_domain1.end) * S::HALF;
            call_count = add_curve_intersections(
                curve2, &new_1_curves.0, domain2, &(new_domain1.start..new_domain1_mid),
                intersections, !flip, recursion_count, call_count,
                orig_curve2, orig_curve1,
            );
            call_count = add_curve_intersections(
                curve2, &new_1_curves.1, domain2, &(new_domain1_mid..new_domain1.end),
                intersections, !flip, recursion_count, call_count,
                orig_curve2, orig_curve1,
            );
        } else {
            let new_2_curves = orig_curve2.split_range(domain2.clone()).split(S::HALF);
            let domain2_mid = (domain2.start + domain2.end) * S::HALF;
            call_count = add_curve_intersections(
                &new_2_curves.0, curve1, &(domain2.start..domain2_mid), new_domain1,
                intersections, !flip, recursion_count, call_count,
                orig_curve2, orig_curve1,
            );
            call_count = add_curve_intersections(
                &new_2_curves.1, curve1, &(domain2_mid..domain2.end), new_domain1,
                intersections, !flip, recursion_count, call_count,
                orig_curve2, orig_curve1,
            );
        }
    } else {
        
        if domain2.end - domain2.start >= epsilon {
            call_count = add_curve_intersections(
                curve2, curve1, domain2, new_domain1,
                intersections, !flip, recursion_count, call_count,
                orig_curve2, orig_curve1,
            );
        } else {
            
            call_count = add_curve_intersections(
                curve1, curve2, new_domain1, domain2,
                intersections, flip, recursion_count, call_count,
                orig_curve1, orig_curve2,
            );
        }
    }
    call_count
}
fn add_point_curve_intersection<S: Scalar>(
    pt_curve: &CubicBezierSegment<S>,
    pt_curve_is_curve1: bool,
    curve: &CubicBezierSegment<S>,
    pt_domain: &Range<S>,
    curve_domain: &Range<S>,
    intersections: &mut ArrayVec<[(S, S); 9]>,
    flip: bool,
) {
    let pt = pt_curve.from;
    
    let flip = if pt_curve_is_curve1 { flip } else { !flip };
    
    
    let epsilon = epsilon_for_point(&pt);
    let pt_t = (pt_domain.start + pt_domain.end) * S::HALF;
    let curve_t = {
        let mut t_for_min = S::ZERO;
        let mut min_dist_sq = epsilon;
        let tenths = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
        for &t in tenths.iter() {
            let t = S::value(t);
            let d = (pt - curve.sample(t)).square_length();
            if d < min_dist_sq {
                t_for_min = t;
                min_dist_sq = d;
            }
        }
        if min_dist_sq == epsilon {
            -S::ONE
        } else {
            let curve_t = domain_value_at_t(curve_domain, t_for_min);
            curve_t
        }
    };
    if curve_t != -S::ONE {
        add_intersection(pt_t, pt_curve, curve_t, curve, flip, intersections);
        return;
    }
    
    let results = point_curve_intersections(&pt, curve, epsilon);
    for t in results {
        let curve_t = domain_value_at_t(curve_domain, t);
        add_intersection(pt_t, pt_curve, curve_t, curve, flip, intersections);
    }
}
fn epsilon_for_point<S: Scalar>(pt: &Point<S>) -> S {
    let max = S::max(S::abs(pt.x), S::abs(pt.y));
    let epsilon = if inputs_are_f32::<S>() {
        match max.to_i32().unwrap() {
            0 ... 10 => S::value(0.001),
            10 ... 100 => S::value(0.01),
            100 ... 1_000 => S::value(0.1),
            1_000 ... 10_000 => S::value(0.25),
            10_000 ... 1_000_000 => S::HALF,
            _ => S::ONE,
        }
    } else {
        match max.to_i64().unwrap() {
            0 ... 100_000 => S::EPSILON,
            100_000 ... 100_000_000 => S::value(1e-5),
            100_000_000 ... 10_000_000_000 => S::value(1e-3),
            _ => S::value(1e-1),
        }
    };
    epsilon
}
fn add_intersection<S: Scalar>(
    t1: S,
    orig_curve1: &CubicBezierSegment<S>,
    t2: S,
    orig_curve2: &CubicBezierSegment<S>,
    flip: bool,
    intersections: &mut ArrayVec<[(S, S); 9]>,
) {
    let (t1, t2) = if flip { (t2, t1) } else { (t1, t2) };
    
    let epsilon = if inputs_are_f32::<S>() { S::value(1e-3) } else { S::EPSILON };
    
    let t1_is_an_endpoint = t1 < epsilon || t1 > S::ONE - epsilon;
    let t2_is_an_endpoint = t2 < epsilon || t2 > S::ONE - epsilon;
    if t1_is_an_endpoint && t2_is_an_endpoint {
        return;
    }
    
    
    
    for i in 0..intersections.len() {
        let (old_t1, old_t2) = intersections[i];
        
        
        if S::abs(t1 - old_t1) < epsilon && S::abs(t2 - old_t2) < epsilon {
            let cur_dist =
                (orig_curve1.sample(old_t1) - orig_curve2.sample(old_t2)).square_length();
            let new_dist = (orig_curve1.sample(t1) - orig_curve2.sample(t2)).square_length();
            if new_dist < cur_dist {
                intersections[i] = (t1, t2);
            }
            return;
        }
    }
    if intersections.len() < 9 {
        intersections.push((t1, t2));
    }
}
fn restrict_curve_to_fat_line<S: Scalar>(
    curve1: &CubicBezierSegment<S>,
    curve2: &CubicBezierSegment<S>,
) -> Option<(S, S)> {
    
    
    
    
    let baseline2 = curve2.baseline().to_line().equation();
    let d_0 = baseline2.signed_distance_to_point(&curve1.from);
    let d_1 = baseline2.signed_distance_to_point(&curve1.ctrl1);
    let d_2 = baseline2.signed_distance_to_point(&curve1.ctrl2);
    let d_3 = baseline2.signed_distance_to_point(&curve1.to);
    let (mut top, mut bottom) = convex_hull_of_distance_curve(d_0, d_1, d_2, d_3);
    let (d_min, d_max) = curve2.fat_line_min_max();
    clip_convex_hull_to_fat_line(&mut top, &mut bottom, d_min, d_max)
}
fn convex_hull_of_distance_curve<S: Scalar>(
    d0: S,
    d1: S,
    d2: S,
    d3: S,
) -> (Vec<Point<S>>, Vec<Point<S>>) {
    let p0 = point(S::ZERO, d0);
    let p1 = point(S::ONE / S::THREE, d1);
    let p2 = point(S::TWO / S::THREE, d2);
    let p3 = point(S::ONE, d3);
    
    let dist1 = d1 - (S::TWO * d0 + d3) / S::THREE;
    let dist2 = d2 - (d0 + S::TWO * d3) / S::THREE;
    
    let mut hull = if dist1 * dist2 < S::ZERO {
        
        (vec![p0, p1, p3], vec![p0, p2, p3])
    } else {
        
        
        
        
        let dist1 = S::abs(dist1);
        let dist2 = S::abs(dist2);
        if dist1 >= S::TWO * dist2 {
            (vec![p0, p1, p3], vec![p0, p3])
        } else if dist2 >= S::TWO * dist1 {
            (vec![p0, p2, p3], vec![p0, p3])
        } else {
            (vec![p0, p1, p2, p3], vec![p0, p3])
        }
    };
    
    if dist1 < S::ZERO || (dist1 == S::ZERO && dist2 < S::ZERO) {
        hull = (hull.1, hull.0);
    }
    hull
}
fn clip_convex_hull_to_fat_line<S: Scalar>(
    hull_top: &mut Vec<Point<S>>,
    hull_bottom: &mut Vec<Point<S>>,
    d_min: S,
    d_max: S,
) -> Option<(S, S)> {
    
    let t_clip_min = walk_convex_hull_start_to_fat_line(&hull_top, &hull_bottom, d_min, d_max);
    if let None = t_clip_min {
        return None;
    }
    
    
    
    hull_top.reverse();
    hull_bottom.reverse();
    let t_clip_max = walk_convex_hull_start_to_fat_line(&hull_top, &hull_bottom, d_min, d_max);
    if let None = t_clip_max {
        return None;
    }
    Some((t_clip_min.unwrap(), t_clip_max.unwrap()))
}
fn walk_convex_hull_start_to_fat_line<S: Scalar>(
    hull_top_vertices: &Vec<Point<S>>,
    hull_bottom_vertices: &Vec<Point<S>>,
    d_min: S,
    d_max: S,
) -> Option<S> {
    let start_corner = hull_top_vertices[0];
    if start_corner.y < d_min {
        return walk_convex_hull_edges_to_fat_line(hull_top_vertices, true, d_min);
    } else if start_corner.y > d_max {
        return walk_convex_hull_edges_to_fat_line(hull_bottom_vertices, false, d_max);
    } else {
        return Some(start_corner.x);
    }
}
fn walk_convex_hull_edges_to_fat_line<S: Scalar>(
    hull_vertices: &Vec<Point<S>>,
    vertices_are_for_top: bool,
    threshold: S,
) -> Option<S> {
    for i in 0..hull_vertices.len() - 1 {
        let p = hull_vertices[i];
        let q = hull_vertices[i + 1];
        if (vertices_are_for_top && q.y >= threshold) || (!vertices_are_for_top && q.y <= threshold)
        {
            if q.y == threshold {
                return Some(q.x);
            } else {
                return Some(p.x + (threshold - p.y) * (q.x - p.x) / (q.y - p.y));
            }
        }
    }
    
    None
}
#[inline]
fn inputs_are_f32<S: Scalar>() -> bool {
    S::EPSILON > S::value(1e-6)
}
#[inline]
fn domain_value_at_t<S: Scalar>(domain: &Range<S>, t: S) -> S {
    domain.start + (domain.end - domain.start) * t
}
#[inline]
fn rectangles_overlap<S: Scalar>(r1: &Rect<S>, r2: &Rect<S>) -> bool {
    r1.origin.x <= r2.origin.x + r2.size.width
        && r2.origin.x <= r1.origin.x + r1.size.width
        && r1.origin.y <= r2.origin.y + r2.size.height
        && r2.origin.y <= r1.origin.y + r1.size.height
}
#[cfg(test)]
fn do_test<S: Scalar>(
    curve1: &CubicBezierSegment<S>,
    curve2: &CubicBezierSegment<S>,
    intersection_count: i32,
) {
    do_test_once(curve1, curve2, intersection_count);
    do_test_once(curve2, curve1, intersection_count);
}
#[cfg(test)]
fn do_test_once<S: Scalar>(
    curve1: &CubicBezierSegment<S>,
    curve2: &CubicBezierSegment<S>,
    intersection_count: i32,
) {
    let intersections = cubic_bezier_intersections_t(&curve1, &curve2);
    for intersection in &intersections {
        let p1 = curve1.sample(intersection.0);
        let p2 = curve2.sample(intersection.1);
        check_dist(&p1, &p2);
    }
    assert_eq!(intersections.len() as i32, intersection_count);
}
#[cfg(test)]
fn check_dist<S: Scalar>(p1: &Point<S>, p2: &Point<S>) {
    let dist = S::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
    if dist > S::HALF {
        assert!(false, "Intersection points too far apart.");
    }
}
#[test]
fn test_cubic_curve_curve_intersections() {
    do_test(
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(0.0, 1.0),
            ctrl2: point(0.0, 1.0),
            to: point(1.0, 1.0),
        },
        &CubicBezierSegment {
            from: point(0.0, 1.0),
            ctrl1: point(1.0, 1.0),
            ctrl2: point(1.0, 1.0),
            to: point(1.0, 0.0),
        },
        1,
    );
    do_test(
        &CubicBezierSegment {
            from: point(48.0f32, 84.0),
            ctrl1: point(104.0, 176.0),
            ctrl2: point(190.0, 37.0),
            to: point(121.0, 75.0),
        },
        &CubicBezierSegment {
            from: point(68.0, 145.0),
            ctrl1: point(74.0, 6.0),
            ctrl2: point(143.0, 197.0),
            to: point(138.0, 55.0),
        },
        4,
    );
    do_test(
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(0.5, 1.0),
            ctrl2: point(0.5, 1.0),
            to: point(1.0, 0.0),
        },
        &CubicBezierSegment {
            from: point(0.0, 1.0),
            ctrl1: point(0.5, 0.0),
            ctrl2: point(0.5, 0.0),
            to: point(1.0, 1.0),
        },
        2,
    );
    do_test(
        &CubicBezierSegment {
            from: point(0.2, 0.0),
            ctrl1: point(0.5, 3.0),
            ctrl2: point(0.5, -2.0),
            to: point(0.8, 1.0),
        },
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(2.5, 0.5),
            ctrl2: point(-1.5, 0.5),
            to: point(1.0, 0.0),
        },
        9,
    );
    
    
    do_test(
        &CubicBezierSegment {
            from: point(718133.1363092018, 673674.987999388),
            ctrl1: point(-53014.13135835016, 286988.87959900266),
            ctrl2: point(-900630.1880107201, -7527.6889376943),
            to: point(417822.48349384824, -149039.14932848653),
        },
        &CubicBezierSegment {
            from: point(924715.3309247112, 719414.5221912428),
            ctrl1: point(965365.9679664494, -563421.3040676294),
            ctrl2: point(273552.85484064696, 643090.0890117711),
            to: point(-113963.134524995, 732017.9466050486),
        },
        1,
    );
    
    
    
    
    do_test(
        &CubicBezierSegment {
            from: point(423394.5967598548, -91342.7434613118),
            ctrl1: point(333212.450870987, 225564.45711810607),
            ctrl2: point(668108.668469816, -626100.8367380127),
            to: point(-481885.0610437216, 893767.5320803947),
        },
        &CubicBezierSegment {
            from: point(-484505.2601961801, -222621.44229855016),
            ctrl1: point(22432.829984141514, -944727.7102144773),
            ctrl2: point(-433294.66549074976, -168018.60431004688),
            to: point(567688.5977972192, 13975.09633399453),
        },
        3,
    );
}
#[test]
fn test_cubic_control_point_touching() {
    
    
    
    
    do_test(
        &CubicBezierSegment {
            from: point(-1.0, 0.0),
            ctrl1: point(0.0, 0.0),
            ctrl2: point(-1.0, -0.1),
            to: point(-1.0, -0.1),
        },
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(5.0, -5.0),
            ctrl2: point(-5.0, -5.0),
            to: point(0.0, 0.0),
        },
        0,
    );
}
#[test]
fn test_cubic_self_intersections() {
    
    do_test(
        &CubicBezierSegment {
            from: point(-10.0, -13.636363636363636),
            ctrl1: point(15.0, 11.363636363636363),
            ctrl2: point(-15.0, 11.363636363636363),
            to: point(10.0, -13.636363636363636),
        },
        &CubicBezierSegment {
            from: point(13.636363636363636, -10.0),
            ctrl1: point(-11.363636363636363, 15.0),
            ctrl2: point(-11.363636363636363, -15.0),
            to: point(13.636363636363636, 10.0),
        },
        4,
    );
}
#[test]
fn test_cubic_loops() {
    
    
    do_test(
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(-10.0, 10.0),
            ctrl2: point(10.0, 10.0),
            to: point(0.0, 0.0),
        },
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(-1.0, 1.0),
            ctrl2: point(1.0, 1.0),
            to: point(0.0, 0.0),
        },
        0,
    );
    do_test(
        &CubicBezierSegment {
            from: point(0.0f32, 0.0),
            ctrl1: point(-100.0, 0.0),
            ctrl2: point(-500.0, 500.0),
            to: point(0.0, 0.0),
        },
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(-800.0, 100.0),
            ctrl2: point(500.0, 500.0),
            to: point(0.0, 0.0),
        },
        3,
    );
}
#[test]
fn test_cubic_line_curve_intersections() {
    do_test(
        &CubicBezierSegment {
            
            from: point(1.0, 2.0),
            ctrl1: point(20.0, 1.0),
            ctrl2: point(1.0, 2.0),
            to: point(20.0, 1.0),
        },
        &CubicBezierSegment {
            from: point(1.0, 0.0),
            ctrl1: point(1.0, 5.0),
            ctrl2: point(20.0, 25.0),
            to: point(20.0, 0.0),
        },
        2,
    );
    do_test(
        &CubicBezierSegment {
            
            from: point(0.0f32, 0.0),
            ctrl1: point(-10.0, 0.0),
            ctrl2: point(20.0, 0.0),
            to: point(10.0, 0.0),
        },
        &CubicBezierSegment {
            from: point(-1.0, -1.0),
            ctrl1: point(0.0, 4.0),
            ctrl2: point(10.0, -4.0),
            to: point(11.0, 1.0),
        },
        5,
    );
    do_test(
        &CubicBezierSegment {
            from: point(-1.0, -2.0),
            ctrl1: point(-1.0, 8.0),
            ctrl2: point(1.0, -8.0),
            to: point(1.0, 2.0),
        },
        &CubicBezierSegment {
            
            from: point(-10.0, -10.0),
            ctrl1: point(20.0, 20.0),
            ctrl2: point(-20.0, -20.0),
            to: point(10.0, 10.0),
        },
        9,
    );
}
#[test]
fn test_cubic_line_line_intersections() {
    do_test(
        &CubicBezierSegment {
            from: point(-10.0, -10.0),
            ctrl1: point(20.0, 20.0),
            ctrl2: point(-20.0, -20.0),
            to: point(10.0, 10.0),
        },
        &CubicBezierSegment {
            from: point(-10.0, 10.0),
            ctrl1: point(20.0, -20.0),
            ctrl2: point(-20.0, 20.0),
            to: point(10.0, -10.0),
        },
        9,
    );
    
    do_test(
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(0.0, 0.0),
            ctrl2: point(10.0, 0.0),
            to: point(10.0, 0.0),
        },
        &CubicBezierSegment {
            from: point(5.0, 0.0),
            ctrl1: point(5.0, 0.0),
            ctrl2: point(15.0, 0.0),
            to: point(15.0, 0.0),
        },
        0,
    );
}
#[test]
fn test_cubic_similar_loops() {
    do_test(
        &CubicBezierSegment {
            from: point(-0.281604145719379, -0.3129629924180608),
            ctrl1: point(-0.04393998118946163, 0.13714701102906668),
            ctrl2: point(0.4472584256288119, 0.2876115686206546),
            to: point(-0.281604145719379, -0.3129629924180608),
        },
        &CubicBezierSegment {
            from: point(-0.281604145719379, -0.3129629924180608),
            ctrl1: point(-0.1560415754252551, -0.22924729391648402),
            ctrl2: point(-0.9224550447067958, 0.19110227764357646),
            to: point(-0.281604145719379, -0.3129629924180608),
        },
        2,
    );
}
#[test]
fn test_cubic_no_duplicated_root() {
    do_test(
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(-10.0, 1.0),
            ctrl2: point(10.0, 1.0),
            to: point(0.0, 0.0),
        },
        &CubicBezierSegment {
            from: point(0.0, 0.0),
            ctrl1: point(-1.0, 1.0),
            ctrl2: point(1.0, 1.0),
            to: point(0.0, 0.0),
        },
        1,
    );
}
#[test]
fn test_cubic_glancing_intersection() {
    use std::panic;
    
    let result = panic::catch_unwind(|| {
        do_test(
            &CubicBezierSegment {
                from: point(0.0, 0.0),
                ctrl1: point(0.0, 8.0),
                ctrl2: point(10.0, 8.0),
                to: point(10.0, 0.0),
            },
            &CubicBezierSegment {
                from: point(0.0, 12.0),
                ctrl1: point(0.0, 4.0),
                ctrl2: point(10.0, 4.0),
                to: point(10.0, 12.0),
            },
            1,
        );
    });
    assert!(result.is_err());
    let result = panic::catch_unwind(|| {
        do_test(
            &CubicBezierSegment {
                from: point(0.0f32, 0.0),
                ctrl1: point(0.0, 8.0),
                ctrl2: point(10.0, 8.0),
                to: point(10.0, 0.0),
            },
            &CubicBezierSegment {
                from: point(0.0, 12.0),
                ctrl1: point(0.0, 4.0),
                ctrl2: point(10.0, 4.0),
                to: point(10.0, 12.0),
            },
            1,
        );
    });
    assert!(result.is_err());
}
#[test]
fn test_cubic_duplicated_intersections() {
    use std::panic;
    let result = panic::catch_unwind(|| {
        
        
        
        do_test(
            &CubicBezierSegment {
                from: point(-33307.36f32, -1804.0625),
                ctrl1: point(-59259.727, 70098.31),
                ctrl2: point(98661.78, 48235.703),
                to: point(28422.234, 31845.219),
            },
            &CubicBezierSegment {
                from: point(-21501.133, 51935.344),
                ctrl1: point(-95301.96, 95031.45),
                ctrl2: point(-25882.242, -12896.75),
                to: point(94618.97, 94288.66),
            },
            2,
        );
    });
    assert!(result.is_err());
}
#[test]
fn test_cubic_endpoint_not_an_intersection() {
    
    
    
    use std::panic;
    let result = panic::catch_unwind(|| {
        do_test(
            &CubicBezierSegment {
                from: point(76868.875f32, 47679.28),
                ctrl1: point(65326.86, 856.21094),
                ctrl2: point(-85621.64, -80823.375),
                to: point(-56517.53, 28062.227),
            },
            &CubicBezierSegment {
                from: point(-67977.72, 77673.53),
                ctrl1: point(-59829.57, -41917.87),
                ctrl2: point(57.4375, 52822.97),
                to: point(51075.86, 85772.84),
            },
            0,
        );
    });
    assert!(result.is_err());
}
#[test]
fn test_cubic_interior_endpoint() {
    do_test(
        &CubicBezierSegment {
            
            from: point(-5.0, 0.0),
            ctrl1: point(-5.0, 8.0),
            ctrl2: point(5.0, 8.0),
            to: point(5.0, 0.0),
        },
        &CubicBezierSegment {
            from: point(0.0, 6.0),
            ctrl1: point(-5.0, 0.0),
            ctrl2: point(5.0, 0.0),
            to: point(0.0, 6.0),
        },
        2,
    );
}
#[test]
fn test_cubic_point_curve_intersections() {
    let epsilon = 1e-5;
    {
        let curve1 =
            CubicBezierSegment {
                from: point(0.0, 0.0),
                ctrl1: point(0.0, 1.0),
                ctrl2: point(0.0, 1.0),
                to: point(1.0, 1.0),
            };
        let sample_t = 0.123456789;
        let pt = curve1.sample(sample_t);
        let curve2 =
            CubicBezierSegment {
                from: pt,
                ctrl1: pt,
                ctrl2: pt,
                to: pt,
            };
        let intersections = cubic_bezier_intersections_t(&curve1, &curve2);
        assert_eq!(intersections.len(), 1);
        let intersection_t = intersections[0].0;
        assert!(f64::abs(intersection_t - sample_t) < epsilon);
    }
    {
        let curve1 =
            CubicBezierSegment {
                from: point(-10.0, -13.636363636363636),
                ctrl1: point(15.0, 11.363636363636363),
                ctrl2: point(-15.0, 11.363636363636363),
                to: point(10.0, -13.636363636363636),
            };
        
        let parameter1 = 0.7611164839335472;
        let parameter2 = 0.23888351606645375;
        let pt = curve1.sample(parameter1);
        let curve2 =
            CubicBezierSegment {
                from: pt,
                ctrl1: pt,
                ctrl2: pt,
                to: pt,
            };
        let intersections = cubic_bezier_intersections_t(&curve1, &curve2);
        assert_eq!(intersections.len(), 2);
        let intersection_t1 = intersections[0].0;
        let intersection_t2 = intersections[1].0;
        assert!(f64::abs(intersection_t1 - parameter1) < epsilon);
        assert!(f64::abs(intersection_t2 - parameter2) < epsilon);
    }
    {
        let epsilon = epsilon as f32;
        let curve1 =
            CubicBezierSegment {
                from: point(0.0f32, 0.0),
                ctrl1: point(50.0, 50.0),
                ctrl2: point(-50.0, -50.0),
                to: point(10.0, 10.0),
            };
        
        let parameter1 = 0.96984464;
        let parameter2 = 0.037427425;
        let parameter3 = 0.44434106;
        let pt = curve1.sample(parameter1);
        let curve2 =
            CubicBezierSegment {
                from: pt,
                ctrl1: pt,
                ctrl2: pt,
                to: pt,
            };
        let intersections = cubic_bezier_intersections_t(&curve1, &curve2);
        assert_eq!(intersections.len(), 3);
        let intersection_t1 = intersections[0].0;
        let intersection_t2 = intersections[1].0;
        let intersection_t3 = intersections[2].0;
        assert!(f32::abs(intersection_t1 - parameter1) < epsilon);
        assert!(f32::abs(intersection_t2 - parameter2) < epsilon);
        assert!(f32::abs(intersection_t3 - parameter3) < epsilon);
    }
}
#[test]
fn test_cubic_subcurve_intersections() {
    let curve1 = CubicBezierSegment {
        from: point(0.0, 0.0),
        ctrl1: point(0.0, 1.0),
        ctrl2: point(0.0, 1.0),
        to: point(1.0, 1.0),
    };
    let curve2 = curve1.split_range(0.25..0.75);
    
    
    do_test(&curve1, &curve2, 9);
}
#[test]
fn test_cubic_result_distance() {
    
    
    
    
    
    do_test(
        &CubicBezierSegment {
            from: point(5893.133f32, -51377.152),
            ctrl1: point(-94403.984, 37668.156),
            ctrl2: point(-58914.684, 30339.195),
            to: point(4895.875, 83473.3),
        },
        &CubicBezierSegment {
            from: point(-51523.734, 75047.05),
            ctrl1: point(-58162.76, -91093.875),
            ctrl2: point(82137.516, -59844.35),
            to: point(46856.406, 40479.64),
        },
        3,
    );
}