"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.bez3SelfIntersections = exports.bez3Intersections = void 0;
const fn_1 = require("../../fn");
const point_1 = require("../../point/point");
const bez3_overlap_1 = require("./bez3-overlap");
function possibleIntersect(v1, v2) {
    return (Math.max(v1.a.x, v1.b.x, v1.c.x, v1.d.x) + fn_1.EPSILON >
        Math.min(v2.a.x, v2.b.x, v2.c.x, v2.d.x) &&
        Math.min(v1.a.x, v1.b.x, v1.c.x, v1.d.x) - fn_1.EPSILON <
            Math.max(v2.a.x, v2.b.x, v2.c.x, v2.d.x) &&
        Math.max(v1.a.y, v1.b.y, v1.c.y, v1.d.y) + fn_1.EPSILON >
            Math.min(v2.a.y, v2.b.y, v2.c.y, v2.d.y) &&
        Math.min(v1.a.y, v1.b.y, v1.c.y, v1.d.y) - fn_1.EPSILON <
            Math.max(v2.a.y, v2.b.y, v2.c.y, v2.d.y));
}
function bez3Intersections(v1, v2, sink) {
    if (!possibleIntersect(v1, v2))
        return;
    const overlaps = (0, bez3_overlap_1.getOverlaps)(v1, v2);
    if (overlaps) {
        for (const [ta, tb] of overlaps) {
            sink.add(ta, tb);
        }
    }
    else {
        var straight1 = v1.isStraight(), straight2 = v2.isStraight();
        if (straight1 && straight2) {
            lineIntersectionImpl(v1, v2, sink);
        }
        else if (straight1) {
            lineCurveIntersectionImpl(v2, v1, sink, true);
        }
        else if (straight2) {
            lineCurveIntersectionImpl(v1, v2, sink, false);
        }
        else {
            curveIntersectionImpl(v1, v2, sink, false, 0, 0, 0, 1, 0, 1);
        }
    }
}
exports.bez3Intersections = bez3Intersections;
function bez3SelfIntersections(arc, sink) {
    const rs = new fn_1.RootSolver.ClampedRootSink(0, 1, false);
    arc.classify(rs);
    arc.getXExtrema(rs);
    arc.getYExtrema(rs);
    for (const root of rs.roots)
        sink.add(root);
}
exports.bez3SelfIntersections = bez3SelfIntersections;
function lineIntersectionImpl(v1, v2, sink) {
    const pt = point_1.Point2.intersect(v1.a, v1.d, v2.a, v2.d);
    if (pt) {
        const t1 = v1.getTOf(pt), t2 = v2.getTOf(pt);
        if (t1 != null && t2 != null)
            sink.add(t1, t2);
    }
}
function lineCurveIntersectionImpl(v1, v2, sink, flip) {
    const x1 = v2.a.x, y1 = v2.a.y, x2 = v2.d.x, y2 = v2.d.y;
    const roots = getCurveLineIntersections(v1, x1, y1, x2 - x1, y2 - y1);
    for (let i = 0, l = roots.length; i < l; i++) {
        var t1 = roots[i], p1 = v1.eval(t1), t2 = v2.getTOf(p1);
        if (t2 != null) {
            if (flip) {
                sink.add(t2, t1);
            }
            else {
                sink.add(t1, t2);
            }
        }
    }
}
function getCurveLineIntersections(v, px, py, vx, vy) {
    if ((0, fn_1.numberClose)(vx, 0) && (0, fn_1.numberClose)(vy, 0)) {
        const t = v.getTOf(new point_1.Point2(px, py));
        return t === null ? [] : [t];
    }
    const angle = Math.atan2(-vy, vx), sin = Math.sin(angle), cos = Math.cos(angle), rv = [], zs = [v.a, v.b, v.c, v.d];
    for (var i = 0; i < 4; i++) {
        var x = zs[i].x - px, y = zs[i].y - py;
        rv.push(x * sin + y * cos);
    }
    const rs = new fn_1.RootSolver.ClampedRootSink(0, 1, true);
    fn_1.RootSolver.bezierSolveCubic(rv[0], rv[1], rv[2], rv[3], 0, rs);
    return rs.roots;
}
const FAT_LINE_EPSILON = 1e-9;
const MAX_CALLS = 65535;
const MAX_RECURSE = 40;
function curveIntersectionImpl(v1, v2, sink, flip, recursion, calls, tMin, tMax, uMin, uMax) {
    if (++calls >= MAX_CALLS || ++recursion >= MAX_RECURSE)
        return calls;
    let d1 = point_1.Point2.signedPointLineDist(v2.a, v2.d, v2.b), d2 = point_1.Point2.signedPointLineDist(v2.a, v2.d, v2.c), factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9, dMin = factor * Math.min(0, d1, d2), dMax = factor * Math.max(0, d1, d2);
    let dp0 = point_1.Point2.signedPointLineDist(v2.a, v2.d, v1.a);
    let dp1 = point_1.Point2.signedPointLineDist(v2.a, v2.d, v1.b);
    let dp2 = point_1.Point2.signedPointLineDist(v2.a, v2.d, v1.c);
    let dp3 = point_1.Point2.signedPointLineDist(v2.a, v2.d, v1.d);
    let hull = getConvexHull(dp0, dp1, dp2, dp3), top = hull[0], bottom = hull[1];
    let tMinClip = null, tMaxClip = null;
    if ((d1 === 0 && d2 === 0 && dp0 === 0 && dp1 === 0 && dp2 === 0 && dp3 === 0) ||
        (tMinClip = clipConvexHull(top, bottom, dMin, dMax)) == null ||
        (tMaxClip = clipConvexHull(top.reverse(), bottom.reverse(), dMin, dMax)) == null)
        return calls;
    let tMinNew = tMin + (tMax - tMin) * tMinClip, tMaxNew = tMin + (tMax - tMin) * tMaxClip;
    if (Math.max(uMax - uMin, tMaxNew - tMinNew) < FAT_LINE_EPSILON) {
        var t = (tMinNew + tMaxNew) / 2, u = (uMin + uMax) / 2;
        if (flip) {
            sink.add(u, t);
        }
        else {
            sink.add(t, u);
        }
    }
    else {
        v1 = v1.sliceRatio(tMinClip, tMaxClip);
        let uDiff = uMax - uMin;
        if (tMaxClip - tMinClip > 0.8) {
            if (tMaxNew - tMinNew > uDiff) {
                let parts = v1.splitRatio(0.5), t = (tMinNew + tMaxNew) / 2;
                calls = curveIntersectionImpl(v2, parts[0], sink, !flip, recursion, calls, uMin, uMax, tMinNew, t);
                calls = curveIntersectionImpl(v2, parts[1], sink, !flip, recursion, calls, uMin, uMax, t, tMaxNew);
            }
            else {
                var parts = v2.splitRatio(0.5), u = (uMin + uMax) / 2;
                calls = curveIntersectionImpl(parts[0], v1, sink, !flip, recursion, calls, uMin, u, tMinNew, tMaxNew);
                calls = curveIntersectionImpl(parts[1], v1, sink, !flip, recursion, calls, u, uMax, tMinNew, tMaxNew);
            }
        }
        else {
            if (uDiff === 0 || uDiff >= FAT_LINE_EPSILON) {
                calls = curveIntersectionImpl(v2, v1, sink, !flip, recursion, calls, uMin, uMax, tMinNew, tMaxNew);
            }
            else {
                calls = curveIntersectionImpl(v1, v2, sink, flip, recursion, calls, tMinNew, tMaxNew, uMin, uMax);
            }
        }
    }
    return calls;
}
function getConvexHull(dq0, dq1, dq2, dq3) {
    var p0 = [0, dq0], p1 = [1 / 3, dq1], p2 = [2 / 3, dq2], p3 = [1, dq3], dist1 = dq1 - (2 * dq0 + dq3) / 3, dist2 = dq2 - (dq0 + 2 * dq3) / 3, hull;
    if (dist1 * dist2 < 0) {
        hull = [
            [p0, p1, p3],
            [p0, p2, p3]
        ];
    }
    else {
        var distRatio = dist1 / dist2;
        hull = [
            distRatio >= 2
                ? [p0, p1, p3]
                :
                    distRatio <= 0.5
                        ? [p0, p2, p3]
                        :
                            [p0, p1, p2, p3],
            [p0, p3]
        ];
    }
    return (dist1 || dist2) < 0 ? hull.reverse() : hull;
}
function clipConvexHull(hullTop, hullBottom, dMin, dMax) {
    if (hullTop[0][1] < dMin) {
        return clipConvexHullPart(hullTop, true, dMin);
    }
    else if (hullBottom[0][1] > dMax) {
        return clipConvexHullPart(hullBottom, false, dMax);
    }
    else {
        return hullTop[0][0];
    }
}
function clipConvexHullPart(part, top, threshold) {
    let [px, py] = part[0];
    for (let i = 1; i < part.length; i++) {
        let [qx, qy] = part[i];
        if (top ? qy >= threshold : qy <= threshold) {
            return px + ((threshold - py) * (qx - px)) / (qy - py);
        }
        [px, py] = [qx, qy];
    }
    return null;
}
function getFatline(v) {
    let d1 = point_1.Point2.signedPointLineDist(v.a, v.d, v.b) || 0;
    let d2 = point_1.Point2.signedPointLineDist(v.a, v.d, v.c) || 0;
    let factor = d1 * d2 > 0 ? 3.0 / 4.0 : 4.0 / 9.0;
    let dMin = factor * Math.min(0, d1, d2);
    let dMax = factor * Math.max(0, d1, d2);
    return [dMin, dMax];
}
