"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Bez3Slice = exports.CornerType = void 0;
const derivable_1 = require("../../derivable");
const fn_1 = require("../../fn");
const point_1 = require("../../point/point");
var CornerType;
(function (CornerType) {
    CornerType[CornerType["Smooth"] = 0] = "Smooth";
    CornerType[CornerType["Corner"] = 1] = "Corner";
    CornerType[CornerType["Extrema"] = 2] = "Extrema";
    CornerType[CornerType["Hetero"] = 3] = "Hetero";
})(CornerType || (exports.CornerType = CornerType = {}));
class Bez3Slice extends derivable_1.Arcs.Bez3 {
    constructor() {
        super(...arguments);
        this.cornerTypeBefore = CornerType.Corner;
        this.cornerTypeAfter = CornerType.Corner;
    }
    forceStraight() {
        const arc = new Bez3Slice(this.a, point_1.Point2.from(this.a).mix(point_1.Point2.from(this.d), 1 / 3), point_1.Point2.from(this.a).mix(point_1.Point2.from(this.d), 2 / 3), this.d);
        arc.cornerTypeBefore = this.cornerTypeBefore;
        arc.cornerTypeAfter = this.cornerTypeAfter;
        return arc;
    }
    isStraight() {
        if (this.isStraightCache != null)
            return this.isStraightCache;
        const isStraight = super.isStraight();
        this.isStraightCache = isStraight;
        return isStraight;
    }
    toString() {
        return (`(${this.a.x}, ${this.a.y}) -- (${this.b.x}, ${this.b.y}) .. ` +
            `(${this.c.x}, ${this.c.y}) -- (${this.d.x}, ${this.d.y})`);
    }
    splitRatio(t = 0.5) {
        const u = 1 - t;
        const p3x = u * this.a.x + t * this.b.x;
        const p3y = u * this.a.y + t * this.b.y;
        const p4x = u * this.b.x + t * this.c.x;
        const p4y = u * this.b.y + t * this.c.y;
        const p5x = u * this.c.x + t * this.d.x;
        const p5y = u * this.c.y + t * this.d.y;
        const p6x = u * p3x + t * p4x;
        const p6y = u * p3y + t * p4y;
        const p7x = u * p4x + t * p5x;
        const p7y = u * p4y + t * p5y;
        const p8x = u * p6x + t * p7x;
        const p8y = u * p6y + t * p7y;
        return [
            new Bez3Slice(this.a, new point_1.Point2(p3x, p3y), new point_1.Point2(p6x, p6y), new point_1.Point2(p8x, p8y)),
            new Bez3Slice(new point_1.Point2(p8x, p8y), new point_1.Point2(p7x, p7y), new point_1.Point2(p5x, p5y), this.d),
        ];
    }
    sliceRatio(t1, t2) {
        let v = this;
        if (t1 > 0) {
            v = v.splitRatio(t1)[1];
        }
        if (t2 < 1) {
            v = v.splitRatio((t2 - t1) / (1.0 - t1))[0];
        }
        return v;
    }
    getTOf(point) {
        const p0 = point_1.Point2.from(this.a), p3 = point_1.Point2.from(this.d);
        if (p0.isClose(point, fn_1.EPSILON))
            return 0;
        if (p3.isClose(point, fn_1.EPSILON))
            return 1;
        const coords = [point.x, point.y], coeffs = [
            this.a.x,
            this.b.x,
            this.c.x,
            this.d.x,
            this.a.y,
            this.b.y,
            this.c.y,
            this.d.y,
        ];
        for (let c = 0; c < 2; c++) {
            const rs = new fn_1.RootSolver.ClampedRootSink(0, 1, true);
            fn_1.RootSolver.bezierSolveCubic(coeffs[c * 4 + 0], coeffs[c * 4 + 1], coeffs[c * 4 + 2], coeffs[c * 4 + 3], coords[c], rs);
            for (let i = 0; i < rs.rootCount; i++) {
                const u = rs.roots[i];
                if (this.eval(u).isClose(point, fn_1.GEOMETRIC_EPSILON))
                    return u;
            }
        }
        if (p0.isClose(point, fn_1.GEOMETRIC_EPSILON))
            return 0;
        if (p3.isClose(point, fn_1.GEOMETRIC_EPSILON))
            return 1;
        return null;
    }
    classify(sink) {
        const x0 = this.a.x, y0 = this.a.y, x1 = this.b.x, y1 = this.b.y, x2 = this.c.x, y2 = this.c.y, x3 = this.d.x, y3 = this.d.y;
        let a1 = x0 * (y3 - y2) + y0 * (x2 - x3) + x3 * y2 - y3 * x2, a2 = x1 * (y0 - y3) + y1 * (x3 - x0) + x0 * y3 - y0 * x3, a3 = x2 * (y1 - y0) + y2 * (x0 - x1) + x1 * y0 - y1 * x0, d3 = 3 * a3, d2 = d3 - a2, d1 = d2 - a2 + a1, l = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3), s = l !== 0 ? 1 / l : 0;
        d1 *= s;
        d2 *= s;
        d3 *= s;
        if ((0, fn_1.numberClose)(d1, 0)) {
            return (0, fn_1.numberClose)(d2, 0)
                ? this.cleanupClassifyResults((0, fn_1.numberClose)(d3, 0) ? "line" : "quadratic", sink)
                : this.cleanupClassifyResults("serpentine", sink, d3 / (3 * d2));
        }
        var d = 3 * d2 * d2 - 4 * d1 * d3;
        if ((0, fn_1.numberClose)(d, 0)) {
            return this.cleanupClassifyResults("cusp", sink, d2 / (2 * d1));
        }
        var f1 = d > 0 ? Math.sqrt(d / 3) : Math.sqrt(-d), f2 = 2 * d1;
        return this.cleanupClassifyResults(d > 0 ? "serpentine" : "loop", sink, (d2 + f1) / f2, (d2 - f1) / f2);
    }
    cleanupClassifyResults(type, sink, t1, t2) {
        let hasRoots = t1 !== undefined, t1Ok = hasRoots && t1 != null && t1 > 0 && t1 < 1, t2Ok = hasRoots && t2 != null && t2 > 0 && t2 < 1;
        if (hasRoots && (!(t1Ok || t2Ok) || (type === "loop" && !(t1Ok && t2Ok)))) {
            type = "arch";
            t1Ok = t2Ok = false;
        }
        if (sink && t1Ok)
            sink.addRoot(t1);
        if (sink && t2Ok)
            sink.addRoot(t2);
    }
    getLength(a = 0, b = 1) {
        if (this.isStraight()) {
            const slice = this.sliceRatio(a, b);
            return point_1.Point2.dist(slice.a, slice.d);
        }
        else {
            return fn_1.Integral.gaussLegendre(this.getLengthIntegrand(), a, b, this.getLengthSteps(a, b));
        }
    }
    getLengthIntegrand() {
        const ax = 9 * (this.b.x - this.c.x) + 3 * (this.d.x - this.a.x), bx = 6 * (this.a.x + this.c.x) - 12 * this.b.x, cx = 3 * (this.b.x - this.a.x), ay = 9 * (this.b.y - this.c.y) + 3 * (this.d.y - this.a.y), by = 6 * (this.a.y + this.c.y) - 12 * this.b.y, cy = 3 * (this.b.y - this.a.y);
        return (t) => {
            return Math.hypot((ax * t + bx) * t + cx, (ay * t + by) * t + cy);
        };
    }
    getLengthSteps(a, b) {
        return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32)));
    }
    getXExtrema(sink) {
        return this.getExtremaImpl(this.a.x, this.b.x, this.c.x, this.d.x, sink);
    }
    getYExtrema(sink) {
        return this.getExtremaImpl(this.a.y, this.b.y, this.c.y, this.d.y, sink);
    }
    getExtremaImpl(v0, v1, v2, v3, sink) {
        const a = 3 * (-v0 + 3 * v1 - 3 * v2 + v3);
        const b = 6 * (v0 - 2 * v1 + v2);
        const c = 3 * (v1 - v0);
        fn_1.RootSolver.solveQuadratic(a, b, c, sink);
    }
    static fromStraightSegment(ss) {
        return new Bez3Slice(ss.a, point_1.Point2.from(ss.a).mix(point_1.Point2.from(ss.b), 1 / 3), point_1.Point2.from(ss.a).mix(point_1.Point2.from(ss.b), 2 / 3), ss.b);
    }
    static fromArcSlice(arc, t0, t1) {
        const scalar = t1 - t0;
        const z0 = arc.eval(t0), z1 = arc.eval(t1);
        return new Bez3Slice(z0, point_1.Point2.from(z0).addScale(scalar / 3, point_1.Offset2.from(arc.derivative(t0))), point_1.Point2.from(z1).addScale(-scalar / 3, point_1.Offset2.from(arc.derivative(t1))), z1);
    }
}
exports.Bez3Slice = Bez3Slice;
