// Copyright 2008 The Android Open Source Project
// Copyright 2020 Yevhenii Reizner
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Based on SkStroke.cpp

use crate::{Path, Point, Transform};

use crate::dash::StrokeDash;
use crate::floating_point::{NonZeroPositiveF32, NormalizedF32, NormalizedF32Exclusive};
use crate::path::{PathSegment, PathSegmentsIter};
use crate::path_builder::{PathBuilder, PathDirection};
use crate::path_geometry;
use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO, SCALAR_ROOT_2_OVER_2};

#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use crate::NoStdFloat;

struct SwappableBuilders<'a> {
    inner: &'a mut PathBuilder,
    outer: &'a mut PathBuilder,
}

impl<'a> SwappableBuilders<'a> {
    fn swap(&mut self) {
        // Skia swaps pointers to inner and outer builders during joining,
        // but not builders itself. So a simple `core::mem::swap` will produce invalid results.
        // And if we try to use use `core::mem::swap` on references, like below,
        // borrow checker will be unhappy.
        // That's why we need this wrapper. Maybe there is a better solution.
        core::mem::swap(&mut self.inner, &mut self.outer);
    }
}

/// Stroke properties.
#[derive(Clone, PartialEq, Debug)]
pub struct Stroke {
    /// A stroke thickness.
    ///
    /// Must be >= 0.
    ///
    /// When set to 0, a hairline stroking will be used.
    ///
    /// Default: 1.0
    pub width: f32,

    /// The limit at which a sharp corner is drawn beveled.
    ///
    /// Default: 4.0
    pub miter_limit: f32,

    /// A stroke line cap.
    ///
    /// Default: Butt
    pub line_cap: LineCap,

    /// A stroke line join.
    ///
    /// Default: Miter
    pub line_join: LineJoin,

    /// A stroke dashing properties.
    ///
    /// Default: None
    pub dash: Option<StrokeDash>,
}

impl Default for Stroke {
    fn default() -> Self {
        Stroke {
            width: 1.0,
            miter_limit: 4.0,
            line_cap: LineCap::default(),
            line_join: LineJoin::default(),
            dash: None,
        }
    }
}

/// Draws at the beginning and end of an open path contour.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum LineCap {
    /// No stroke extension.
    Butt,
    /// Adds circle.
    Round,
    /// Adds square.
    Square,
}

impl Default for LineCap {
    fn default() -> Self {
        LineCap::Butt
    }
}

/// Specifies how corners are drawn when a shape is stroked.
///
/// Join affects the four corners of a stroked rectangle, and the connected segments in a
/// stroked path.
///
/// Choose miter join to draw sharp corners. Choose round join to draw a circle with a
/// radius equal to the stroke width on top of the corner. Choose bevel join to minimally
/// connect the thick strokes.
///
/// The fill path constructed to describe the stroked path respects the join setting but may
/// not contain the actual join. For instance, a fill path constructed with round joins does
/// not necessarily include circles at each connected segment.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum LineJoin {
    /// Extends to miter limit.
    Miter,
    /// Adds circle.
    Round,
    /// Connects outside edges.
    Bevel,
}

impl Default for LineJoin {
    fn default() -> Self {
        LineJoin::Miter
    }
}

const QUAD_RECURSIVE_LIMIT: usize = 3;

// quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure
// largest seen for normal cubics: 5, 26
// largest seen for normal quads: 11
const RECURSIVE_LIMITS: [i32; 4] = [5 * 3, 26 * 3, 11 * 3, 11 * 3]; // 3x limits seen in practice

type CapProc = fn(
    pivot: Point,
    normal: Point,
    stop: Point,
    other_path: Option<&PathBuilder>,
    path: &mut PathBuilder,
);

type JoinProc = fn(
    before_unit_normal: Point,
    pivot: Point,
    after_unit_normal: Point,
    radius: f32,
    inv_miter_limit: f32,
    prev_is_line: bool,
    curr_is_line: bool,
    builders: SwappableBuilders,
);

#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
enum ReductionType {
    Point,       // all curve points are practically identical
    Line,        // the control point is on the line between the ends
    Quad,        // the control point is outside the line between the ends
    Degenerate,  // the control point is on the line but outside the ends
    Degenerate2, // two control points are on the line but outside ends (cubic)
    Degenerate3, // three areas of max curvature found (for cubic)
}

#[derive(Copy, Clone, PartialEq, Debug)]
enum StrokeType {
    Outer = 1, // use sign-opposite values later to flip perpendicular axis
    Inner = -1,
}

#[derive(Copy, Clone, PartialEq, Debug)]
enum ResultType {
    Split,      // the caller should split the quad stroke in two
    Degenerate, // the caller should add a line
    Quad,       // the caller should (continue to try to) add a quad stroke
}

#[derive(Copy, Clone, PartialEq, Debug)]
enum IntersectRayType {
    CtrlPt,
    ResultType,
}

impl Path {
    /// Returns a stoked path.
    ///
    /// `resolution_scale` can be obtained via
    /// [`compute_resolution_scale`](PathStroker::compute_resolution_scale).
    ///
    /// If you plan stroking multiple paths, you can try using [`PathStroker`]
    /// which will preserve temporary allocations required during stroking.
    /// This might improve performance a bit.
    pub fn stroke(&self, stroke: &Stroke, resolution_scale: f32) -> Option<Path> {
        PathStroker::new().stroke(self, stroke, resolution_scale)
    }
}

/// A path stroker.
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct PathStroker {
    radius: f32,
    inv_miter_limit: f32,
    res_scale: f32,
    inv_res_scale: f32,
    inv_res_scale_squared: f32,

    first_normal: Point,
    prev_normal: Point,
    first_unit_normal: Point,
    prev_unit_normal: Point,

    // on original path
    first_pt: Point,
    prev_pt: Point,

    first_outer_pt: Point,
    first_outer_pt_index_in_contour: usize,
    segment_count: i32,
    prev_is_line: bool,

    capper: CapProc,
    joiner: JoinProc,

    // outer is our working answer, inner is temp
    inner: PathBuilder,
    outer: PathBuilder,
    cusper: PathBuilder,

    stroke_type: StrokeType,

    recursion_depth: i32, // track stack depth to abort if numerics run amok
    found_tangents: bool, // do less work until tangents meet (cubic)
    join_completed: bool, // previous join was not degenerate
}

impl Default for PathStroker {
    fn default() -> Self {
        PathStroker::new()
    }
}

impl PathStroker {
    /// Creates a new PathStroker.
    pub fn new() -> Self {
        PathStroker {
            radius: 0.0,
            inv_miter_limit: 0.0,
            res_scale: 1.0,
            inv_res_scale: 1.0,
            inv_res_scale_squared: 1.0,

            first_normal: Point::zero(),
            prev_normal: Point::zero(),
            first_unit_normal: Point::zero(),
            prev_unit_normal: Point::zero(),

            first_pt: Point::zero(),
            prev_pt: Point::zero(),

            first_outer_pt: Point::zero(),
            first_outer_pt_index_in_contour: 0,
            segment_count: -1,
            prev_is_line: false,

            capper: butt_capper,
            joiner: miter_joiner,

            inner: PathBuilder::new(),
            outer: PathBuilder::new(),
            cusper: PathBuilder::new(),

            stroke_type: StrokeType::Outer,

            recursion_depth: 0,
            found_tangents: false,
            join_completed: false,
        }
    }

    /// Computes a resolution scale.
    ///
    /// Resolution scale is the "intended" resolution for the output. Default is 1.0.
    ///
    /// Larger values (res > 1) indicate that the result should be more precise, since it will
    /// be zoomed up, and small errors will be magnified.
    ///
    /// Smaller values (0 < res < 1) indicate that the result can be less precise, since it will
    /// be zoomed down, and small errors may be invisible.
    pub fn compute_resolution_scale(ts: &Transform) -> f32 {
        let sx = Point::from_xy(ts.sx, ts.kx).length();
        let sy = Point::from_xy(ts.ky, ts.sy).length();
        if sx.is_finite() && sy.is_finite() {
            let scale = sx.max(sy);
            if scale > 0.0 {
                return scale;
            }
        }

        1.0
    }

    /// Stokes the path.
    ///
    /// Can be called multiple times to reuse allocated buffers.
    ///
    /// `resolution_scale` can be obtained via
    /// [`compute_resolution_scale`](Self::compute_resolution_scale).
    pub fn stroke(&mut self, path: &Path, stroke: &Stroke, resolution_scale: f32) -> Option<Path> {
        let width = NonZeroPositiveF32::new(stroke.width)?;
        self.stroke_inner(
            path,
            width,
            stroke.miter_limit,
            stroke.line_cap,
            stroke.line_join,
            resolution_scale,
        )
    }

    fn stroke_inner(
        &mut self,
        path: &Path,
        width: NonZeroPositiveF32,
        miter_limit: f32,
        line_cap: LineCap,
        mut line_join: LineJoin,
        res_scale: f32,
    ) -> Option<Path> {
        // TODO: stroke_rect optimization

        let mut inv_miter_limit = 0.0;

        if line_join == LineJoin::Miter {
            if miter_limit <= 1.0 {
                line_join = LineJoin::Bevel;
            } else {
                inv_miter_limit = miter_limit.invert();
            }
        }

        self.res_scale = res_scale;
        // The '4' below matches the fill scan converter's error term.
        self.inv_res_scale = (res_scale * 4.0).invert();
        self.inv_res_scale_squared = self.inv_res_scale.sqr();

        self.radius = width.get().half();
        self.inv_miter_limit = inv_miter_limit;

        self.first_normal = Point::zero();
        self.prev_normal = Point::zero();
        self.first_unit_normal = Point::zero();
        self.prev_unit_normal = Point::zero();

        self.first_pt = Point::zero();
        self.prev_pt = Point::zero();

        self.first_outer_pt = Point::zero();
        self.first_outer_pt_index_in_contour = 0;
        self.segment_count = -1;
        self.prev_is_line = false;

        self.capper = cap_factory(line_cap);
        self.joiner = join_factory(line_join);

        // Need some estimate of how large our final result (fOuter)
        // and our per-contour temp (fInner) will be, so we don't spend
        // extra time repeatedly growing these arrays.
        //
        // 1x for inner == 'wag' (worst contour length would be better guess)
        self.inner.clear();
        self.inner.reserve(path.verbs.len(), path.points.len());

        // 3x for result == inner + outer + join (swag)
        self.outer.clear();
        self.outer
            .reserve(path.verbs.len() * 3, path.points.len() * 3);

        self.cusper.clear();

        self.stroke_type = StrokeType::Outer;

        self.recursion_depth = 0;
        self.found_tangents = false;
        self.join_completed = false;

        let mut last_segment_is_line = false;
        let mut iter = path.segments();
        iter.set_auto_close(true);
        while let Some(segment) = iter.next() {
            match segment {
                PathSegment::MoveTo(p) => {
                    self.move_to(p);
                }
                PathSegment::LineTo(p) => {
                    self.line_to(p, Some(&iter));
                    last_segment_is_line = true;
                }
                PathSegment::QuadTo(p1, p2) => {
                    self.quad_to(p1, p2);
                    last_segment_is_line = false;
                }
                PathSegment::CubicTo(p1, p2, p3) => {
                    self.cubic_to(p1, p2, p3);
                    last_segment_is_line = false;
                }
                PathSegment::Close => {
                    if line_cap != LineCap::Butt {
                        // If the stroke consists of a moveTo followed by a close, treat it
                        // as if it were followed by a zero-length line. Lines without length
                        // can have square and round end caps.
                        if self.has_only_move_to() {
                            self.line_to(self.move_to_pt(), None);
                            last_segment_is_line = true;
                            continue;
                        }

                        // If the stroke consists of a moveTo followed by one or more zero-length
                        // verbs, then followed by a close, treat is as if it were followed by a
                        // zero-length line. Lines without length can have square & round end caps.
                        if self.is_current_contour_empty() {
                            last_segment_is_line = true;
                            continue;
                        }
                    }

                    self.close(last_segment_is_line);
                }
            }
        }

        self.finish(last_segment_is_line)
    }

    fn builders(&mut self) -> SwappableBuilders {
        SwappableBuilders {
            inner: &mut self.inner,
            outer: &mut self.outer,
        }
    }

    fn move_to_pt(&self) -> Point {
        self.first_pt
    }

    fn move_to(&mut self, p: Point) {
        if self.segment_count > 0 {
            self.finish_contour(false, false);
        }

        self.segment_count = 0;
        self.first_pt = p;
        self.prev_pt = p;
        self.join_completed = false;
    }

    fn line_to(&mut self, p: Point, iter: Option<&PathSegmentsIter>) {
        let teeny_line = self
            .prev_pt
            .equals_within_tolerance(p, SCALAR_NEARLY_ZERO * self.inv_res_scale);
        if fn_ptr_eq(self.capper, butt_capper) && teeny_line {
            return;
        }

        if teeny_line && (self.join_completed || iter.map(|i| i.has_valid_tangent()) == Some(true))
        {
            return;
        }

        let mut normal = Point::zero();
        let mut unit_normal = Point::zero();
        if !self.pre_join_to(p, true, &mut normal, &mut unit_normal) {
            return;
        }

        self.outer.line_to(p.x + normal.x, p.y + normal.y);
        self.inner.line_to(p.x - normal.x, p.y - normal.y);

        self.post_join_to(p, normal, unit_normal);
    }

    fn quad_to(&mut self, p1: Point, p2: Point) {
        let quad = [self.prev_pt, p1, p2];
        let (reduction, reduction_type) = check_quad_linear(&quad);
        if reduction_type == ReductionType::Point {
            // If the stroke consists of a moveTo followed by a degenerate curve, treat it
            // as if it were followed by a zero-length line. Lines without length
            // can have square and round end caps.
            self.line_to(p2, None);
            return;
        }

        if reduction_type == ReductionType::Line {
            self.line_to(p2, None);
            return;
        }

        if reduction_type == ReductionType::Degenerate {
            self.line_to(reduction, None);
            let save_joiner = self.joiner;
            self.joiner = round_joiner;
            self.line_to(p2, None);
            self.joiner = save_joiner;
            return;
        }

        debug_assert_eq!(reduction_type, ReductionType::Quad);

        let mut normal_ab = Point::zero();
        let mut unit_ab = Point::zero();
        let mut normal_bc = Point::zero();
        let mut unit_bc = Point::zero();
        if !self.pre_join_to(p1, false, &mut normal_ab, &mut unit_ab) {
            self.line_to(p2, None);
            return;
        }

        let mut quad_points = QuadConstruct::default();
        self.init_quad(
            StrokeType::Outer,
            NormalizedF32::ZERO,
            NormalizedF32::ONE,
            &mut quad_points,
        );
        self.quad_stroke(&quad, &mut quad_points);
        self.init_quad(
            StrokeType::Inner,
            NormalizedF32::ZERO,
            NormalizedF32::ONE,
            &mut quad_points,
        );
        self.quad_stroke(&quad, &mut quad_points);

        let ok = set_normal_unit_normal(
            quad[1],
            quad[2],
            self.res_scale,
            self.radius,
            &mut normal_bc,
            &mut unit_bc,
        );
        if !ok {
            normal_bc = normal_ab;
            unit_bc = unit_ab;
        }

        self.post_join_to(p2, normal_bc, unit_bc);
    }

    fn cubic_to(&mut self, pt1: Point, pt2: Point, pt3: Point) {
        let cubic = [self.prev_pt, pt1, pt2, pt3];
        let mut reduction = [Point::zero(); 3];
        let mut tangent_pt = Point::zero();
        let reduction_type = check_cubic_linear(&cubic, &mut reduction, Some(&mut tangent_pt));
        if reduction_type == ReductionType::Point {
            // If the stroke consists of a moveTo followed by a degenerate curve, treat it
            // as if it were followed by a zero-length line. Lines without length
            // can have square and round end caps.
            self.line_to(pt3, None);
            return;
        }

        if reduction_type == ReductionType::Line {
            self.line_to(pt3, None);
            return;
        }

        if ReductionType::Degenerate <= reduction_type
            && ReductionType::Degenerate3 >= reduction_type
        {
            self.line_to(reduction[0], None);
            let save_joiner = self.joiner;
            self.joiner = round_joiner;
            if ReductionType::Degenerate2 <= reduction_type {
                self.line_to(reduction[1], None);
            }

            if ReductionType::Degenerate3 == reduction_type {
                self.line_to(reduction[2], None);
            }

            self.line_to(pt3, None);
            self.joiner = save_joiner;
            return;
        }

        debug_assert_eq!(reduction_type, ReductionType::Quad);
        let mut normal_ab = Point::zero();
        let mut unit_ab = Point::zero();
        let mut normal_cd = Point::zero();
        let mut unit_cd = Point::zero();
        if !self.pre_join_to(tangent_pt, false, &mut normal_ab, &mut unit_ab) {
            self.line_to(pt3, None);
            return;
        }

        let mut t_values = path_geometry::new_t_values();
        let t_values = path_geometry::find_cubic_inflections(&cubic, &mut t_values);
        let mut last_t = NormalizedF32::ZERO;
        for index in 0..=t_values.len() {
            let next_t = t_values
                .get(index)
                .cloned()
                .map(|n| n.to_normalized())
                .unwrap_or(NormalizedF32::ONE);

            let mut quad_points = QuadConstruct::default();
            self.init_quad(StrokeType::Outer, last_t, next_t, &mut quad_points);
            self.cubic_stroke(&cubic, &mut quad_points);
            self.init_quad(StrokeType::Inner, last_t, next_t, &mut quad_points);
            self.cubic_stroke(&cubic, &mut quad_points);
            last_t = next_t;
        }

        if let Some(cusp) = path_geometry::find_cubic_cusp(&cubic) {
            let cusp_loc = path_geometry::eval_cubic_pos_at(&cubic, cusp.to_normalized());
            self.cusper.push_circle(cusp_loc.x, cusp_loc.y, self.radius);
        }

        // emit the join even if one stroke succeeded but the last one failed
        // this avoids reversing an inner stroke with a partial path followed by another moveto
        self.set_cubic_end_normal(&cubic, normal_ab, unit_ab, &mut normal_cd, &mut unit_cd);

        self.post_join_to(pt3, normal_cd, unit_cd);
    }

    fn cubic_stroke(&mut self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> bool {
        if !self.found_tangents {
            let result_type = self.tangents_meet(cubic, quad_points);
            if result_type != ResultType::Quad {
                let ok = points_within_dist(
                    quad_points.quad[0],
                    quad_points.quad[2],
                    self.inv_res_scale,
                );
                if (result_type == ResultType::Degenerate || ok)
                    && self.cubic_mid_on_line(cubic, quad_points)
                {
                    self.add_degenerate_line(quad_points);
                    return true;
                }
            } else {
                self.found_tangents = true;
            }
        }

        if self.found_tangents {
            let result_type = self.compare_quad_cubic(cubic, quad_points);
            if result_type == ResultType::Quad {
                let stroke = &quad_points.quad;
                if self.stroke_type == StrokeType::Outer {
                    self.outer
                        .quad_to(stroke[1].x, stroke[1].y, stroke[2].x, stroke[2].y);
                } else {
                    self.inner
                        .quad_to(stroke[1].x, stroke[1].y, stroke[2].x, stroke[2].y);
                }

                return true;
            }

            if result_type == ResultType::Degenerate {
                if !quad_points.opposite_tangents {
                    self.add_degenerate_line(quad_points);
                    return true;
                }
            }
        }

        if !quad_points.quad[2].x.is_finite() || !quad_points.quad[2].x.is_finite() {
            return false; // just abort if projected quad isn't representable
        }

        self.recursion_depth += 1;
        if self.recursion_depth > RECURSIVE_LIMITS[self.found_tangents as usize] {
            return false; // just abort if projected quad isn't representable
        }

        let mut half = QuadConstruct::default();
        if !half.init_with_start(quad_points) {
            self.add_degenerate_line(quad_points);
            self.recursion_depth -= 1;
            return true;
        }

        if !self.cubic_stroke(cubic, &mut half) {
            return false;
        }

        if !half.init_with_end(quad_points) {
            self.add_degenerate_line(quad_points);
            self.recursion_depth -= 1;
            return true;
        }

        if !self.cubic_stroke(cubic, &mut half) {
            return false;
        }

        self.recursion_depth -= 1;
        true
    }

    fn cubic_mid_on_line(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> bool {
        let mut stroke_mid = Point::zero();
        self.cubic_quad_mid(cubic, quad_points, &mut stroke_mid);
        let dist = pt_to_line(stroke_mid, quad_points.quad[0], quad_points.quad[2]);
        dist < self.inv_res_scale_squared
    }

    fn cubic_quad_mid(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct, mid: &mut Point) {
        let mut cubic_mid_pt = Point::zero();
        self.cubic_perp_ray(cubic, quad_points.mid_t, &mut cubic_mid_pt, mid, None);
    }

    // Given a cubic and t, return the point on curve,
    // its perpendicular, and the perpendicular tangent.
    fn cubic_perp_ray(
        &self,
        cubic: &[Point; 4],
        t: NormalizedF32,
        t_pt: &mut Point,
        on_pt: &mut Point,
        tangent: Option<&mut Point>,
    ) {
        *t_pt = path_geometry::eval_cubic_pos_at(cubic, t);
        let mut dxy = path_geometry::eval_cubic_tangent_at(cubic, t);

        let mut chopped = [Point::zero(); 7];
        if dxy.x == 0.0 && dxy.y == 0.0 {
            let mut c_points: &[Point] = cubic;
            if t.get().is_nearly_zero() {
                dxy = cubic[2] - cubic[0];
            } else if (1.0 - t.get()).is_nearly_zero() {
                dxy = cubic[3] - cubic[1];
            } else {
                // If the cubic inflection falls on the cusp, subdivide the cubic
                // to find the tangent at that point.
                //
                // Unwrap never fails, because we already checked that `t` is not 0/1,
                let t = NormalizedF32Exclusive::new(t.get()).unwrap();
                path_geometry::chop_cubic_at2(cubic, t, &mut chopped);
                dxy = chopped[3] - chopped[2];
                if dxy.x == 0.0 && dxy.y == 0.0 {
                    dxy = chopped[3] - chopped[1];
                    c_points = &chopped;
                }
            }

            if dxy.x == 0.0 && dxy.y == 0.0 {
                dxy = c_points[3] - c_points[0];
            }
        }

        self.set_ray_points(*t_pt, &mut dxy, on_pt, tangent);
    }

    fn set_cubic_end_normal(
        &mut self,
        cubic: &[Point; 4],
        normal_ab: Point,
        unit_normal_ab: Point,
        normal_cd: &mut Point,
        unit_normal_cd: &mut Point,
    ) {
        let mut ab = cubic[1] - cubic[0];
        let mut cd = cubic[3] - cubic[2];

        let mut degenerate_ab = degenerate_vector(ab);
        let mut degenerate_cb = degenerate_vector(cd);

        if degenerate_ab && degenerate_cb {
            *normal_cd = normal_ab;
            *unit_normal_cd = unit_normal_ab;
            return;
        }

        if degenerate_ab {
            ab = cubic[2] - cubic[0];
            degenerate_ab = degenerate_vector(ab);
        }

        if degenerate_cb {
            cd = cubic[3] - cubic[1];
            degenerate_cb = degenerate_vector(cd);
        }

        if degenerate_ab || degenerate_cb {
            *normal_cd = normal_ab;
            *unit_normal_cd = unit_normal_ab;
            return;
        }

        let res = set_normal_unit_normal2(cd, self.radius, normal_cd, unit_normal_cd);
        debug_assert!(res);
    }

    fn compare_quad_cubic(
        &self,
        cubic: &[Point; 4],
        quad_points: &mut QuadConstruct,
    ) -> ResultType {
        // get the quadratic approximation of the stroke
        self.cubic_quad_ends(cubic, quad_points);
        let result_type = self.intersect_ray(IntersectRayType::CtrlPt, quad_points);
        if result_type != ResultType::Quad {
            return result_type;
        }

        // project a ray from the curve to the stroke
        // points near midpoint on quad, midpoint on cubic
        let mut ray0 = Point::zero();
        let mut ray1 = Point::zero();
        self.cubic_perp_ray(cubic, quad_points.mid_t, &mut ray1, &mut ray0, None);
        self.stroke_close_enough(&quad_points.quad.clone(), &[ray0, ray1], quad_points)
    }

    // Given a cubic and a t range, find the start and end if they haven't been found already.
    fn cubic_quad_ends(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) {
        if !quad_points.start_set {
            let mut cubic_start_pt = Point::zero();
            self.cubic_perp_ray(
                cubic,
                quad_points.start_t,
                &mut cubic_start_pt,
                &mut quad_points.quad[0],
                Some(&mut quad_points.tangent_start),
            );
            quad_points.start_set = true;
        }

        if !quad_points.end_set {
            let mut cubic_end_pt = Point::zero();
            self.cubic_perp_ray(
                cubic,
                quad_points.end_t,
                &mut cubic_end_pt,
                &mut quad_points.quad[2],
                Some(&mut quad_points.tangent_end),
            );
            quad_points.end_set = true;
        }
    }

    fn close(&mut self, is_line: bool) {
        self.finish_contour(true, is_line);
    }

    fn finish_contour(&mut self, close: bool, curr_is_line: bool) {
        if self.segment_count > 0 {
            if close {
                (self.joiner)(
                    self.prev_unit_normal,
                    self.prev_pt,
                    self.first_unit_normal,
                    self.radius,
                    self.inv_miter_limit,
                    self.prev_is_line,
                    curr_is_line,
                    self.builders(),
                );
                self.outer.close();

                // now add inner as its own contour
                let pt = self.inner.last_point().unwrap_or_default();
                self.outer.move_to(pt.x, pt.y);
                self.outer.reverse_path_to(&self.inner);
                self.outer.close();
            } else {
                // add caps to start and end

                // cap the end
                let pt = self.inner.last_point().unwrap_or_default();
                let other_path = if curr_is_line {
                    Some(&self.inner)
                } else {
                    None
                };
                (self.capper)(
                    self.prev_pt,
                    self.prev_normal,
                    pt,
                    other_path,
                    &mut self.outer,
                );
                self.outer.reverse_path_to(&self.inner);

                // cap the start
                let other_path = if self.prev_is_line {
                    Some(&self.inner)
                } else {
                    None
                };
                (self.capper)(
                    self.first_pt,
                    -self.first_normal,
                    self.first_outer_pt,
                    other_path,
                    &mut self.outer,
                );
                self.outer.close();
            }

            if !self.cusper.is_empty() {
                self.outer.push_path(&self.cusper);
                self.cusper.clear();
            }
        }

        // since we may re-use `inner`, we rewind instead of reset, to save on
        // reallocating its internal storage.
        self.inner.clear();
        self.segment_count = -1;
        self.first_outer_pt_index_in_contour = self.outer.points.len();
    }

    fn pre_join_to(
        &mut self,
        p: Point,
        curr_is_line: bool,
        normal: &mut Point,
        unit_normal: &mut Point,
    ) -> bool {
        debug_assert!(self.segment_count >= 0);

        let prev_x = self.prev_pt.x;
        let prev_y = self.prev_pt.y;

        let normal_set = set_normal_unit_normal(
            self.prev_pt,
            p,
            self.res_scale,
            self.radius,
            normal,
            unit_normal,
        );
        if !normal_set {
            if fn_ptr_eq(self.capper, butt_capper) {
                return false;
            }

            // Square caps and round caps draw even if the segment length is zero.
            // Since the zero length segment has no direction, set the orientation
            // to upright as the default orientation.
            *normal = Point::from_xy(self.radius, 0.0);
            *unit_normal = Point::from_xy(1.0, 0.0);
        }

        if self.segment_count == 0 {
            self.first_normal = *normal;
            self.first_unit_normal = *unit_normal;
            self.first_outer_pt = Point::from_xy(prev_x + normal.x, prev_y + normal.y);

            self.outer
                .move_to(self.first_outer_pt.x, self.first_outer_pt.y);
            self.inner.move_to(prev_x - normal.x, prev_y - normal.y);
        } else {
            // we have a previous segment
            (self.joiner)(
                self.prev_unit_normal,
                self.prev_pt,
                *unit_normal,
                self.radius,
                self.inv_miter_limit,
                self.prev_is_line,
                curr_is_line,
                self.builders(),
            );
        }
        self.prev_is_line = curr_is_line;
        true
    }

    fn post_join_to(&mut self, p: Point, normal: Point, unit_normal: Point) {
        self.join_completed = true;
        self.prev_pt = p;
        self.prev_unit_normal = unit_normal;
        self.prev_normal = normal;
        self.segment_count += 1;
    }

    fn init_quad(
        &mut self,
        stroke_type: StrokeType,
        start: NormalizedF32,
        end: NormalizedF32,
        quad_points: &mut QuadConstruct,
    ) {
        self.stroke_type = stroke_type;
        self.found_tangents = false;
        quad_points.init(start, end);
    }

    fn quad_stroke(&mut self, quad: &[Point; 3], quad_points: &mut QuadConstruct) -> bool {
        let result_type = self.compare_quad_quad(quad, quad_points);
        if result_type == ResultType::Quad {
            let path = if self.stroke_type == StrokeType::Outer {
                &mut self.outer
            } else {
                &mut self.inner
            };

            path.quad_to(
                quad_points.quad[1].x,
                quad_points.quad[1].y,
                quad_points.quad[2].x,
                quad_points.quad[2].y,
            );

            return true;
        }

        if result_type == ResultType::Degenerate {
            self.add_degenerate_line(quad_points);
            return true;
        }

        self.recursion_depth += 1;
        if self.recursion_depth > RECURSIVE_LIMITS[QUAD_RECURSIVE_LIMIT] {
            return false; // just abort if projected quad isn't representable
        }

        let mut half = QuadConstruct::default();
        half.init_with_start(quad_points);
        if !self.quad_stroke(quad, &mut half) {
            return false;
        }

        half.init_with_end(quad_points);
        if !self.quad_stroke(quad, &mut half) {
            return false;
        }

        self.recursion_depth -= 1;
        true
    }

    fn compare_quad_quad(
        &mut self,
        quad: &[Point; 3],
        quad_points: &mut QuadConstruct,
    ) -> ResultType {
        // get the quadratic approximation of the stroke
        if !quad_points.start_set {
            let mut quad_start_pt = Point::zero();
            self.quad_perp_ray(
                quad,
                quad_points.start_t,
                &mut quad_start_pt,
                &mut quad_points.quad[0],
                Some(&mut quad_points.tangent_start),
            );
            quad_points.start_set = true;
        }

        if !quad_points.end_set {
            let mut quad_end_pt = Point::zero();
            self.quad_perp_ray(
                quad,
                quad_points.end_t,
                &mut quad_end_pt,
                &mut quad_points.quad[2],
                Some(&mut quad_points.tangent_end),
            );
            quad_points.end_set = true;
        }

        let result_type = self.intersect_ray(IntersectRayType::CtrlPt, quad_points);
        if result_type != ResultType::Quad {
            return result_type;
        }

        // project a ray from the curve to the stroke
        let mut ray0 = Point::zero();
        let mut ray1 = Point::zero();
        self.quad_perp_ray(quad, quad_points.mid_t, &mut ray1, &mut ray0, None);
        self.stroke_close_enough(&quad_points.quad.clone(), &[ray0, ray1], quad_points)
    }

    // Given a point on the curve and its derivative, scale the derivative by the radius, and
    // compute the perpendicular point and its tangent.
    fn set_ray_points(
        &self,
        tp: Point,
        dxy: &mut Point,
        on_p: &mut Point,
        mut tangent: Option<&mut Point>,
    ) {
        if !dxy.set_length(self.radius) {
            *dxy = Point::from_xy(self.radius, 0.0);
        }

        let axis_flip = self.stroke_type as i32 as f32; // go opposite ways for outer, inner
        on_p.x = tp.x + axis_flip * dxy.y;
        on_p.y = tp.y - axis_flip * dxy.x;

        if let Some(ref mut tangent) = tangent {
            tangent.x = on_p.x + dxy.x;
            tangent.y = on_p.y + dxy.y;
        }
    }

    // Given a quad and t, return the point on curve,
    // its perpendicular, and the perpendicular tangent.
    fn quad_perp_ray(
        &self,
        quad: &[Point; 3],
        t: NormalizedF32,
        tp: &mut Point,
        on_p: &mut Point,
        tangent: Option<&mut Point>,
    ) {
        *tp = path_geometry::eval_quad_at(quad, t);
        let mut dxy = path_geometry::eval_quad_tangent_at(quad, t);

        if dxy.is_zero() {
            dxy = quad[2] - quad[0];
        }

        self.set_ray_points(*tp, &mut dxy, on_p, tangent);
    }

    fn add_degenerate_line(&mut self, quad_points: &QuadConstruct) {
        if self.stroke_type == StrokeType::Outer {
            self.outer
                .line_to(quad_points.quad[2].x, quad_points.quad[2].y);
        } else {
            self.inner
                .line_to(quad_points.quad[2].x, quad_points.quad[2].y);
        }
    }

    fn stroke_close_enough(
        &self,
        stroke: &[Point; 3],
        ray: &[Point; 2],
        quad_points: &mut QuadConstruct,
    ) -> ResultType {
        let half = NormalizedF32::new_clamped(0.5);
        let stroke_mid = path_geometry::eval_quad_at(stroke, half);
        // measure the distance from the curve to the quad-stroke midpoint, compare to radius
        if points_within_dist(ray[0], stroke_mid, self.inv_res_scale) {
            // if the difference is small
            if sharp_angle(&quad_points.quad) {
                return ResultType::Split;
            }

            return ResultType::Quad;
        }

        // measure the distance to quad's bounds (quick reject)
        // an alternative : look for point in triangle
        if !pt_in_quad_bounds(stroke, ray[0], self.inv_res_scale) {
            // if far, subdivide
            return ResultType::Split;
        }

        // measure the curve ray distance to the quad-stroke
        let mut roots = path_geometry::new_t_values();
        let roots = intersect_quad_ray(ray, stroke, &mut roots);
        if roots.len() != 1 {
            return ResultType::Split;
        }

        let quad_pt = path_geometry::eval_quad_at(stroke, roots[0].to_normalized());
        let error = self.inv_res_scale * (1.0 - (roots[0].get() - 0.5).abs() * 2.0);
        if points_within_dist(ray[0], quad_pt, error) {
            // if the difference is small, we're done
            if sharp_angle(&quad_points.quad) {
                return ResultType::Split;
            }

            return ResultType::Quad;
        }

        // otherwise, subdivide
        ResultType::Split
    }

    // Find the intersection of the stroke tangents to construct a stroke quad.
    // Return whether the stroke is a degenerate (a line), a quad, or must be split.
    // Optionally compute the quad's control point.
    fn intersect_ray(
        &self,
        intersect_ray_type: IntersectRayType,
        quad_points: &mut QuadConstruct,
    ) -> ResultType {
        let start = quad_points.quad[0];
        let end = quad_points.quad[2];
        let a_len = quad_points.tangent_start - start;
        let b_len = quad_points.tangent_end - end;

        // Slopes match when denom goes to zero:
        //                   axLen / ayLen ==                   bxLen / byLen
        // (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen
        //          byLen  * axLen         ==  ayLen          * bxLen
        //          byLen  * axLen         -   ayLen          * bxLen         ( == denom )
        let denom = a_len.cross(b_len);
        if denom == 0.0 || !denom.is_finite() {
            quad_points.opposite_tangents = a_len.dot(b_len) < 0.0;
            return ResultType::Degenerate;
        }

        quad_points.opposite_tangents = false;
        let ab0 = start - end;
        let mut numer_a = b_len.cross(ab0);
        let numer_b = a_len.cross(ab0);
        if (numer_a >= 0.0) == (numer_b >= 0.0) {
            // if the control point is outside the quad ends

            // if the perpendicular distances from the quad points to the opposite tangent line
            // are small, a straight line is good enough
            let dist1 = pt_to_line(start, end, quad_points.tangent_end);
            let dist2 = pt_to_line(end, start, quad_points.tangent_start);
            if dist1.max(dist2) <= self.inv_res_scale_squared {
                return ResultType::Degenerate;
            }

            return ResultType::Split;
        }

        // check to see if the denominator is teeny relative to the numerator
        // if the offset by one will be lost, the ratio is too large
        numer_a /= denom;
        let valid_divide = numer_a > numer_a - 1.0;
        if valid_divide {
            if intersect_ray_type == IntersectRayType::CtrlPt {
                // the intersection of the tangents need not be on the tangent segment
                // so 0 <= numerA <= 1 is not necessarily true
                quad_points.quad[1].x =
                    start.x * (1.0 - numer_a) + quad_points.tangent_start.x * numer_a;
                quad_points.quad[1].y =
                    start.y * (1.0 - numer_a) + quad_points.tangent_start.y * numer_a;
            }

            return ResultType::Quad;
        }

        quad_points.opposite_tangents = a_len.dot(b_len) < 0.0;

        // if the lines are parallel, straight line is good enough
        ResultType::Degenerate
    }

    // Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
    fn tangents_meet(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> ResultType {
        self.cubic_quad_ends(cubic, quad_points);
        self.intersect_ray(IntersectRayType::ResultType, quad_points)
    }

    fn finish(&mut self, is_line: bool) -> Option<Path> {
        self.finish_contour(false, is_line);

        // Swap out the outer builder.
        let mut buf = PathBuilder::new();
        core::mem::swap(&mut self.outer, &mut buf);

        buf.finish()
    }

    fn has_only_move_to(&self) -> bool {
        self.segment_count == 0
    }

    fn is_current_contour_empty(&self) -> bool {
        self.inner.is_zero_length_since_point(0)
            && self
                .outer
                .is_zero_length_since_point(self.first_outer_pt_index_in_contour)
    }
}

fn cap_factory(cap: LineCap) -> CapProc {
    match cap {
        LineCap::Butt => butt_capper,
        LineCap::Round => round_capper,
        LineCap::Square => square_capper,
    }
}

fn butt_capper(_: Point, _: Point, stop: Point, _: Option<&PathBuilder>, path: &mut PathBuilder) {
    path.line_to(stop.x, stop.y);
}

fn round_capper(
    pivot: Point,
    normal: Point,
    stop: Point,
    _: Option<&PathBuilder>,
    path: &mut PathBuilder,
) {
    let mut parallel = normal;
    parallel.rotate_cw();

    let projected_center = pivot + parallel;

    path.conic_points_to(
        projected_center + normal,
        projected_center,
        SCALAR_ROOT_2_OVER_2,
    );
    path.conic_points_to(projected_center - normal, stop, SCALAR_ROOT_2_OVER_2);
}

fn square_capper(
    pivot: Point,
    normal: Point,
    stop: Point,
    other_path: Option<&PathBuilder>,
    path: &mut PathBuilder,
) {
    let mut parallel = normal;
    parallel.rotate_cw();

    if other_path.is_some() {
        path.set_last_point(Point::from_xy(
            pivot.x + normal.x + parallel.x,
            pivot.y + normal.y + parallel.y,
        ));
        path.line_to(
            pivot.x - normal.x + parallel.x,
            pivot.y - normal.y + parallel.y,
        );
    } else {
        path.line_to(
            pivot.x + normal.x + parallel.x,
            pivot.y + normal.y + parallel.y,
        );
        path.line_to(
            pivot.x - normal.x + parallel.x,
            pivot.y - normal.y + parallel.y,
        );
        path.line_to(stop.x, stop.y);
    }
}

fn join_factory(join: LineJoin) -> JoinProc {
    match join {
        LineJoin::Miter => miter_joiner,
        LineJoin::Round => round_joiner,
        LineJoin::Bevel => bevel_joiner,
    }
}

fn is_clockwise(before: Point, after: Point) -> bool {
    before.x * after.y > before.y * after.x
}

#[derive(Copy, Clone, PartialEq, Debug)]
enum AngleType {
    Nearly180,
    Sharp,
    Shallow,
    NearlyLine,
}

fn dot_to_angle_type(dot: f32) -> AngleType {
    if dot >= 0.0 {
        // shallow or line
        if (1.0 - dot).is_nearly_zero() {
            AngleType::NearlyLine
        } else {
            AngleType::Shallow
        }
    } else {
        // sharp or 180
        if (1.0 + dot).is_nearly_zero() {
            AngleType::Nearly180
        } else {
            AngleType::Sharp
        }
    }
}

fn handle_inner_join(pivot: Point, after: Point, inner: &mut PathBuilder) {
    // In the degenerate case that the stroke radius is larger than our segments
    // just connecting the two inner segments may "show through" as a funny
    // diagonal. To pseudo-fix this, we go through the pivot point. This adds
    // an extra point/edge, but I can't see a cheap way to know when this is
    // not needed :(
    inner.line_to(pivot.x, pivot.y);

    inner.line_to(pivot.x - after.x, pivot.y - after.y);
}

fn bevel_joiner(
    before_unit_normal: Point,
    pivot: Point,
    after_unit_normal: Point,
    radius: f32,
    _: f32,
    _: bool,
    _: bool,
    mut builders: SwappableBuilders,
) {
    let mut after = after_unit_normal.scaled(radius);

    if !is_clockwise(before_unit_normal, after_unit_normal) {
        builders.swap();
        after = -after;
    }

    builders.outer.line_to(pivot.x + after.x, pivot.y + after.y);
    handle_inner_join(pivot, after, builders.inner);
}

fn round_joiner(
    before_unit_normal: Point,
    pivot: Point,
    after_unit_normal: Point,
    radius: f32,
    _: f32,
    _: bool,
    _: bool,
    mut builders: SwappableBuilders,
) {
    let dot_prod = before_unit_normal.dot(after_unit_normal);
    let angle_type = dot_to_angle_type(dot_prod);

    if angle_type == AngleType::NearlyLine {
        return;
    }

    let mut before = before_unit_normal;
    let mut after = after_unit_normal;
    let mut dir = PathDirection::CW;

    if !is_clockwise(before, after) {
        builders.swap();
        before = -before;
        after = -after;
        dir = PathDirection::CCW;
    }

    let ts = Transform::from_row(radius, 0.0, 0.0, radius, pivot.x, pivot.y);

    let mut conics = [path_geometry::Conic::default(); 5];
    let conics = path_geometry::Conic::build_unit_arc(before, after, dir, ts, &mut conics);
    if let Some(conics) = conics {
        for conic in conics {
            builders
                .outer
                .conic_points_to(conic.points[1], conic.points[2], conic.weight);
        }

        after.scale(radius);
        handle_inner_join(pivot, after, builders.inner);
    }
}

fn miter_joiner(
    before_unit_normal: Point,
    pivot: Point,
    after_unit_normal: Point,
    radius: f32,
    inv_miter_limit: f32,
    prev_is_line: bool,
    mut curr_is_line: bool,
    mut builders: SwappableBuilders,
) {
    fn do_blunt(
        builders: SwappableBuilders,
        pivot: Point,
        radius: f32,
        curr_is_line: bool,
        mut after: Point,
    ) {
        after.scale(radius);
        if !curr_is_line {
            builders.outer.line_to(pivot.x + after.x, pivot.y + after.y);
        }

        handle_inner_join(pivot, after, builders.inner);
    }

    fn do_miter(
        builders: SwappableBuilders,
        pivot: Point,
        radius: f32,
        prev_is_line: bool,
        curr_is_line: bool,
        mid: Point,
        after: Point,
    ) {
        if prev_is_line {
            builders
                .outer
                .set_last_point(Point::from_xy(pivot.x + mid.x, pivot.y + mid.y));
        } else {
            builders.outer.line_to(pivot.x + mid.x, pivot.y + mid.y);
        }

        do_blunt(builders, pivot, radius, curr_is_line, after);
    }

    // negate the dot since we're using normals instead of tangents
    let dot_prod = before_unit_normal.dot(after_unit_normal);
    let angle_type = dot_to_angle_type(dot_prod);
    let mut before = before_unit_normal;
    let mut after = after_unit_normal;
    let mut mid;

    if angle_type == AngleType::NearlyLine {
        return;
    }

    if angle_type == AngleType::Nearly180 {
        curr_is_line = false;
        do_blunt(builders, pivot, radius, curr_is_line, after);
        return;
    }

    let ccw = !is_clockwise(before, after);
    if ccw {
        builders.swap();
        before = -before;
        after = -after;
    }

    // Before we enter the world of square-roots and divides,
    // check if we're trying to join an upright right angle
    // (common case for stroking rectangles). If so, special case
    // that (for speed an accuracy).
    // Note: we only need to check one normal if dot==0
    if dot_prod == 0.0 && inv_miter_limit <= SCALAR_ROOT_2_OVER_2 {
        mid = (before + after).scaled(radius);
        do_miter(
            builders,
            pivot,
            radius,
            prev_is_line,
            curr_is_line,
            mid,
            after,
        );
        return;
    }

    // midLength = radius / sinHalfAngle
    // if (midLength > miterLimit * radius) abort
    // if (radius / sinHalf > miterLimit * radius) abort
    // if (1 / sinHalf > miterLimit) abort
    // if (1 / miterLimit > sinHalf) abort
    // My dotProd is opposite sign, since it is built from normals and not tangents
    // hence 1 + dot instead of 1 - dot in the formula
    let sin_half_angle = (1.0 + dot_prod).half().sqrt();
    if sin_half_angle < inv_miter_limit {
        curr_is_line = false;
        do_blunt(builders, pivot, radius, curr_is_line, after);
        return;
    }

    // choose the most accurate way to form the initial mid-vector
    if angle_type == AngleType::Sharp {
        mid = Point::from_xy(after.y - before.y, before.x - after.x);
        if ccw {
            mid = -mid;
        }
    } else {
        mid = Point::from_xy(before.x + after.x, before.y + after.y);
    }

    mid.set_length(radius / sin_half_angle);
    do_miter(
        builders,
        pivot,
        radius,
        prev_is_line,
        curr_is_line,
        mid,
        after,
    );
}

fn set_normal_unit_normal(
    before: Point,
    after: Point,
    scale: f32,
    radius: f32,
    normal: &mut Point,
    unit_normal: &mut Point,
) -> bool {
    if !unit_normal.set_normalize((after.x - before.x) * scale, (after.y - before.y) * scale) {
        return false;
    }

    unit_normal.rotate_ccw();
    *normal = unit_normal.scaled(radius);
    true
}

fn set_normal_unit_normal2(
    vec: Point,
    radius: f32,
    normal: &mut Point,
    unit_normal: &mut Point,
) -> bool {
    if !unit_normal.set_normalize(vec.x, vec.y) {
        return false;
    }

    unit_normal.rotate_ccw();
    *normal = unit_normal.scaled(radius);
    true
}

fn fn_ptr_eq(f1: CapProc, f2: CapProc) -> bool {
    core::ptr::eq(f1 as *const (), f2 as *const ())
}

#[derive(Debug)]
struct QuadConstruct {
    // The state of the quad stroke under construction.
    quad: [Point; 3],       // the stroked quad parallel to the original curve
    tangent_start: Point,   // a point tangent to quad[0]
    tangent_end: Point,     // a point tangent to quad[2]
    start_t: NormalizedF32, // a segment of the original curve
    mid_t: NormalizedF32,
    end_t: NormalizedF32,
    start_set: bool, // state to share common points across structs
    end_set: bool,
    opposite_tangents: bool, // set if coincident tangents have opposite directions
}

impl Default for QuadConstruct {
    fn default() -> Self {
        Self {
            quad: Default::default(),
            tangent_start: Point::default(),
            tangent_end: Point::default(),
            start_t: NormalizedF32::ZERO,
            mid_t: NormalizedF32::ZERO,
            end_t: NormalizedF32::ZERO,
            start_set: false,
            end_set: false,
            opposite_tangents: false,
        }
    }
}

impl QuadConstruct {
    // return false if start and end are too close to have a unique middle
    fn init(&mut self, start: NormalizedF32, end: NormalizedF32) -> bool {
        self.start_t = start;
        self.mid_t = NormalizedF32::new_clamped((start.get() + end.get()).half());
        self.end_t = end;
        self.start_set = false;
        self.end_set = false;
        self.start_t < self.mid_t && self.mid_t < self.end_t
    }

    fn init_with_start(&mut self, parent: &Self) -> bool {
        if !self.init(parent.start_t, parent.mid_t) {
            return false;
        }

        self.quad[0] = parent.quad[0];
        self.tangent_start = parent.tangent_start;
        self.start_set = true;
        true
    }

    fn init_with_end(&mut self, parent: &Self) -> bool {
        if !self.init(parent.mid_t, parent.end_t) {
            return false;
        }

        self.quad[2] = parent.quad[2];
        self.tangent_end = parent.tangent_end;
        self.end_set = true;
        true
    }
}

fn check_quad_linear(quad: &[Point; 3]) -> (Point, ReductionType) {
    let degenerate_ab = degenerate_vector(quad[1] - quad[0]);
    let degenerate_bc = degenerate_vector(quad[2] - quad[1]);
    if degenerate_ab & degenerate_bc {
        return (Point::zero(), ReductionType::Point);
    }

    if degenerate_ab | degenerate_bc {
        return (Point::zero(), ReductionType::Line);
    }

    if !quad_in_line(quad) {
        return (Point::zero(), ReductionType::Quad);
    }

    let t = path_geometry::find_quad_max_curvature(quad);
    if t == NormalizedF32::ZERO || t == NormalizedF32::ONE {
        return (Point::zero(), ReductionType::Line);
    }

    (
        path_geometry::eval_quad_at(quad, t),
        ReductionType::Degenerate,
    )
}

fn degenerate_vector(v: Point) -> bool {
    !v.can_normalize()
}

/// Given quad, see if all there points are in a line.
/// Return true if the inside point is close to a line connecting the outermost points.
///
/// Find the outermost point by looking for the largest difference in X or Y.
/// Since the XOR of the indices is 3  (0 ^ 1 ^ 2)
/// the missing index equals: outer_1 ^ outer_2 ^ 3.
fn quad_in_line(quad: &[Point; 3]) -> bool {
    let mut pt_max = -1.0;
    let mut outer1 = 0;
    let mut outer2 = 0;
    for index in 0..2 {
        for inner in index + 1..3 {
            let test_diff = quad[inner] - quad[index];
            let test_max = test_diff.x.abs().max(test_diff.y.abs());
            if pt_max < test_max {
                outer1 = index;
                outer2 = inner;
                pt_max = test_max;
            }
        }
    }

    debug_assert!(outer1 <= 1);
    debug_assert!(outer2 >= 1 && outer2 <= 2);
    debug_assert!(outer1 < outer2);

    let mid = outer1 ^ outer2 ^ 3;
    const CURVATURE_SLOP: f32 = 0.000005; // this multiplier is pulled out of the air
    let line_slop = pt_max * pt_max * CURVATURE_SLOP;
    pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= line_slop
}

// returns the distance squared from the point to the line
fn pt_to_line(pt: Point, line_start: Point, line_end: Point) -> f32 {
    let dxy = line_end - line_start;
    let ab0 = pt - line_start;
    let numer = dxy.dot(ab0);
    let denom = dxy.dot(dxy);
    let t = numer / denom;
    if t >= 0.0 && t <= 1.0 {
        let hit = Point::from_xy(
            line_start.x * (1.0 - t) + line_end.x * t,
            line_start.y * (1.0 - t) + line_end.y * t,
        );
        hit.distance_to_sqd(pt)
    } else {
        pt.distance_to_sqd(line_start)
    }
}

// Intersect the line with the quad and return the t values on the quad where the line crosses.
fn intersect_quad_ray<'a>(
    line: &[Point; 2],
    quad: &[Point; 3],
    roots: &'a mut [NormalizedF32Exclusive; 3],
) -> &'a [NormalizedF32Exclusive] {
    let vec = line[1] - line[0];
    let mut r = [0.0; 3];
    for n in 0..3 {
        r[n] = (quad[n].y - line[0].y) * vec.x - (quad[n].x - line[0].x) * vec.y;
    }
    let mut a = r[2];
    let mut b = r[1];
    let c = r[0];
    a += c - 2.0 * b; // A = a - 2*b + c
    b -= c; // B = -(b - c)

    let len = path_geometry::find_unit_quad_roots(a, 2.0 * b, c, roots);
    &roots[0..len]
}

fn points_within_dist(near_pt: Point, far_pt: Point, limit: f32) -> bool {
    near_pt.distance_to_sqd(far_pt) <= limit * limit
}

fn sharp_angle(quad: &[Point; 3]) -> bool {
    let mut smaller = quad[1] - quad[0];
    let mut larger = quad[1] - quad[2];
    let smaller_len = smaller.length_sqd();
    let mut larger_len = larger.length_sqd();
    if smaller_len > larger_len {
        core::mem::swap(&mut smaller, &mut larger);
        larger_len = smaller_len;
    }

    if !smaller.set_length(larger_len) {
        return false;
    }

    let dot = smaller.dot(larger);
    dot > 0.0
}

// Return true if the point is close to the bounds of the quad. This is used as a quick reject.
fn pt_in_quad_bounds(quad: &[Point; 3], pt: Point, inv_res_scale: f32) -> bool {
    let x_min = quad[0].x.min(quad[1].x).min(quad[2].x);
    if pt.x + inv_res_scale < x_min {
        return false;
    }

    let x_max = quad[0].x.max(quad[1].x).max(quad[2].x);
    if pt.x - inv_res_scale > x_max {
        return false;
    }

    let y_min = quad[0].y.min(quad[1].y).min(quad[2].y);
    if pt.y + inv_res_scale < y_min {
        return false;
    }

    let y_max = quad[0].y.max(quad[1].y).max(quad[2].y);
    if pt.y - inv_res_scale > y_max {
        return false;
    }

    true
}

fn check_cubic_linear(
    cubic: &[Point; 4],
    reduction: &mut [Point; 3],
    tangent_pt: Option<&mut Point>,
) -> ReductionType {
    let degenerate_ab = degenerate_vector(cubic[1] - cubic[0]);
    let degenerate_bc = degenerate_vector(cubic[2] - cubic[1]);
    let degenerate_cd = degenerate_vector(cubic[3] - cubic[2]);
    if degenerate_ab & degenerate_bc & degenerate_cd {
        return ReductionType::Point;
    }

    if degenerate_ab as i32 + degenerate_bc as i32 + degenerate_cd as i32 == 2 {
        return ReductionType::Line;
    }

    if !cubic_in_line(cubic) {
        if let Some(tangent_pt) = tangent_pt {
            *tangent_pt = if degenerate_ab { cubic[2] } else { cubic[1] };
        }

        return ReductionType::Quad;
    }

    let mut t_values = [NormalizedF32::ZERO; 3];
    let t_values = path_geometry::find_cubic_max_curvature(cubic, &mut t_values);
    let mut r_count = 0;
    // Now loop over the t-values, and reject any that evaluate to either end-point
    for t in t_values {
        if 0.0 >= t.get() || t.get() >= 1.0 {
            continue;
        }

        reduction[r_count] = path_geometry::eval_cubic_pos_at(cubic, *t);
        if reduction[r_count] != cubic[0] && reduction[r_count] != cubic[3] {
            r_count += 1;
        }
    }

    match r_count {
        0 => ReductionType::Line,
        1 => ReductionType::Degenerate,
        2 => ReductionType::Degenerate2,
        3 => ReductionType::Degenerate3,
        _ => unreachable!(),
    }
}

/// Given a cubic, determine if all four points are in a line.
///
/// Return true if the inner points is close to a line connecting the outermost points.
///
/// Find the outermost point by looking for the largest difference in X or Y.
/// Given the indices of the outermost points, and that outer_1 is greater than outer_2,
/// this table shows the index of the smaller of the remaining points:
///
/// ```text
///                   outer_2
///               0    1    2    3
///   outer_1     ----------------
///      0     |  -    2    1    1
///      1     |  -    -    0    0
///      2     |  -    -    -    0
///      3     |  -    -    -    -
/// ```
///
/// If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2.
///
/// This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1
///
/// Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is:
///
/// ```text
/// mid_2 == (outer_1 ^ outer_2 ^ mid_1)
/// ```
fn cubic_in_line(cubic: &[Point; 4]) -> bool {
    let mut pt_max = -1.0;
    let mut outer1 = 0;
    let mut outer2 = 0;
    for index in 0..3 {
        for inner in index + 1..4 {
            let test_diff = cubic[inner] - cubic[index];
            let test_max = test_diff.x.abs().max(test_diff.y.abs());
            if pt_max < test_max {
                outer1 = index;
                outer2 = inner;
                pt_max = test_max;
            }
        }
    }
    debug_assert!(outer1 <= 2);
    debug_assert!(outer2 >= 1 && outer2 <= 3);
    debug_assert!(outer1 < outer2);
    let mid1 = (1 + (2 >> outer2)) >> outer1;
    debug_assert!(mid1 <= 2);
    debug_assert!(outer1 != mid1 && outer2 != mid1);
    let mid2 = outer1 ^ outer2 ^ mid1;
    debug_assert!(mid2 >= 1 && mid2 <= 3);
    debug_assert!(mid2 != outer1 && mid2 != outer2 && mid2 != mid1);
    debug_assert!(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f);
    let line_slop = pt_max * pt_max * 0.00001; // this multiplier is pulled out of the air

    pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= line_slop
        && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= line_slop
}

#[rustfmt::skip]
#[cfg(test)]
mod tests {
    use super::*;

    impl PathSegment {
        fn new_move_to(x: f32, y: f32) -> Self {
            PathSegment::MoveTo(Point::from_xy(x, y))
        }

        fn new_line_to(x: f32, y: f32) -> Self {
            PathSegment::LineTo(Point::from_xy(x, y))
        }

        // fn new_quad_to(x1: f32, y1: f32, x: f32, y: f32) -> Self {
        //     PathSegment::QuadTo(Point::from_xy(x1, y1), Point::from_xy(x, y))
        // }

        // fn new_cubic_to(x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) -> Self {
        //     PathSegment::CubicTo(Point::from_xy(x1, y1), Point::from_xy(x2, y2), Point::from_xy(x, y))
        // }

        fn new_close() -> Self {
            PathSegment::Close
        }
    }

    // Make sure that subpath auto-closing is enabled.
    #[test]
    fn auto_close() {
        // A triangle.
        let mut pb = PathBuilder::new();
        pb.move_to(10.0, 10.0);
        pb.line_to(20.0, 50.0);
        pb.line_to(30.0, 10.0);
        pb.close();
        let path = pb.finish().unwrap();

        let stroke = Stroke::default();
        let stroke_path = PathStroker::new().stroke(&path, &stroke, 1.0).unwrap();

        let mut iter = stroke_path.segments();
        iter.set_auto_close(true);

        assert_eq!(iter.next().unwrap(), PathSegment::new_move_to(10.485071, 9.878732));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.485071, 49.878731));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.0, 50.0));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(19.514929, 49.878731));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(29.514929, 9.878732));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.0, 10.0));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.0, 10.5));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.0, 10.5));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.0, 10.0));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.485071, 9.878732));
        assert_eq!(iter.next().unwrap(), PathSegment::new_close());
        assert_eq!(iter.next().unwrap(), PathSegment::new_move_to(9.3596115, 9.5));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.640388, 9.5));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.485071, 50.121269));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(19.514929, 50.121269));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(9.514929, 10.121268));
        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(9.3596115, 9.5));
        assert_eq!(iter.next().unwrap(), PathSegment::new_close());
    }

    // From skia/tests/StrokeTest.cpp
    #[test]
    fn cubic_1() {
        let mut pb = PathBuilder::new();
        pb.move_to(51.0161362, 1511.52478);
        pb.cubic_to(
            51.0161362, 1511.52478,
            51.0161362, 1511.52478,
            51.0161362, 1511.52478,
        );
        let path = pb.finish().unwrap();

        let mut stroke = Stroke::default();
        stroke.width = 0.394537568;

        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_none());
    }

    // From skia/tests/StrokeTest.cpp
    #[test]
    fn cubic_2() {
        let mut pb = PathBuilder::new();
        pb.move_to(f32::from_bits(0x424c1086), f32::from_bits(0x44bcf0cb)); // 51.0161362, 1511.52478
        pb.cubic_to(
            f32::from_bits(0x424c107c), f32::from_bits(0x44bcf0cb), // 51.0160980, 1511.52478
            f32::from_bits(0x424c10c2), f32::from_bits(0x44bcf0cb), // 51.0163651, 1511.52478
            f32::from_bits(0x424c1119), f32::from_bits(0x44bcf0ca), // 51.0166969, 1511.52466
        );
        let path = pb.finish().unwrap();

        let mut stroke = Stroke::default();
        stroke.width = 0.394537568;

        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some());
    }

    // From skia/tests/StrokeTest.cpp
    // From skbug.com/6491. The large stroke width can cause numerical instabilities.
    #[test]
    fn big() {
        // Skia uses `kStrokeAndFill_Style` here, but we do not support it.

        let mut pb = PathBuilder::new();
        pb.move_to(f32::from_bits(0x46380000), f32::from_bits(0xc6380000)); // 11776, -11776
        pb.line_to(f32::from_bits(0x46a00000), f32::from_bits(0xc6a00000)); // 20480, -20480
        pb.line_to(f32::from_bits(0x468c0000), f32::from_bits(0xc68c0000)); // 17920, -17920
        pb.line_to(f32::from_bits(0x46100000), f32::from_bits(0xc6100000)); // 9216, -9216
        pb.line_to(f32::from_bits(0x46380000), f32::from_bits(0xc6380000)); // 11776, -11776
        pb.close();
        let path = pb.finish().unwrap();

        let mut stroke = Stroke::default();
        stroke.width = 1.49679073e+10;

        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some());
    }

    // From skia/tests/StrokerTest.cpp
    #[test]
    fn quad_stroker_one_off() {
        let mut pb = PathBuilder::new();
        pb.move_to(f32::from_bits(0x43c99223), f32::from_bits(0x42b7417e));
        pb.quad_to(
            f32::from_bits(0x4285d839), f32::from_bits(0x43ed6645),
            f32::from_bits(0x43c941c8), f32::from_bits(0x42b3ace3),
        );
        let path = pb.finish().unwrap();

        let mut stroke = Stroke::default();
        stroke.width = 164.683548;

        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some());
    }

    // From skia/tests/StrokerTest.cpp
    #[test]
    fn cubic_stroker_one_off() {
        let mut pb = PathBuilder::new();
        pb.move_to(f32::from_bits(0x433f5370), f32::from_bits(0x43d1f4b3));
        pb.cubic_to(
            f32::from_bits(0x4331cb76), f32::from_bits(0x43ea3340),
            f32::from_bits(0x4388f498), f32::from_bits(0x42f7f08d),
            f32::from_bits(0x43f1cd32), f32::from_bits(0x42802ec1),
        );
        let path = pb.finish().unwrap();

        let mut stroke = Stroke::default();
        stroke.width = 42.835968;

        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some());
    }
}
