"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClampedRootSink = void 0;
exports.solveQuadratic = solveQuadratic;
exports.solveCubic = solveCubic;
exports.bezierSolveCubic = bezierSolveCubic;
const constants_1 = require("./constants");
const utility_1 = require("./utility");
function getDiscriminant(a, b, c) {
    function split(v) {
        const x = v * 134217729, y = v - x, hi = y + x, lo = v - hi;
        return [hi, lo];
    }
    let D = b * b - a * c;
    const E = b * b + a * c;
    if (Math.abs(D) * 3 < E) {
        const ad = split(a), bd = split(b), cd = split(c), p = b * b, dp = bd[0] * bd[0] - p + 2 * bd[0] * bd[1] + bd[1] * bd[1], q = a * c, dq = ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0] + ad[1] * cd[1];
        D = p - q + (dp - dq);
    }
    return D;
}
function getNormalizationFactor(...args) {
    var norm = Math.max.apply(Math, args);
    return norm && (norm < 1e-8 || norm > 1e8) ? 2 ** -Math.round(Math.log2(norm)) : 0;
}
function solveQuadratic(a, b, c, sink) {
    let x1 = Infinity, x2 = Infinity;
    if (Math.abs(a) < constants_1.EPSILON) {
        if (Math.abs(b) < constants_1.EPSILON)
            sink.degenerated();
        x1 = -c / b;
    }
    else {
        b *= -0.5;
        let D = getDiscriminant(a, b, c);
        if (D && Math.abs(D) < constants_1.MACHINE_EPSILON) {
            const f = getNormalizationFactor(Math.abs(a), Math.abs(b), Math.abs(c));
            if (f) {
                a *= f;
                b *= f;
                c *= f;
                D = getDiscriminant(a, b, c);
            }
        }
        if (D >= -constants_1.MACHINE_EPSILON) {
            const Q = D < 0 ? 0 : Math.sqrt(D), R = b + (b < 0 ? -Q : Q);
            if (R === 0) {
                x1 = c / a;
                x2 = -x1;
            }
            else {
                x1 = R / a;
                x2 = c / R;
            }
        }
    }
    if (Number.isFinite(x1))
        sink.addRoot(x1);
    if (Number.isFinite(x2))
        sink.addRoot(x2);
}
function solveCubic(a, b, c, d, sink) {
    let f = getNormalizationFactor(Math.abs(a), Math.abs(b), Math.abs(c), Math.abs(d)), x = 0, b1 = 0, c2 = 0, qd = 0, q = 0;
    if (f) {
        a *= f;
        b *= f;
        c *= f;
        d *= f;
    }
    function evaluate(x0) {
        x = x0;
        var tmp = a * x;
        b1 = tmp + b;
        c2 = b1 * x + c;
        qd = (tmp + b1) * x + c2;
        q = c2 * x + d;
    }
    if (Math.abs(a) < constants_1.EPSILON) {
        a = b;
        b1 = c;
        c2 = d;
        x = Infinity;
    }
    else if (Math.abs(d) < constants_1.EPSILON) {
        b1 = b;
        c2 = c;
        x = 0;
    }
    else {
        evaluate(-(b / a) / 3);
        let t = q / a, r = Math.abs(t) ** (1 / 3), s = t < 0 ? -1 : 1, td = -qd / a, rd = td > 0 ? 1.324717957244746 * Math.max(r, Math.sqrt(td)) : r, x0 = x - s * rd;
        if (x0 !== x) {
            do {
                evaluate(x0);
                x0 = qd === 0 ? x : x - q / qd / (1 + constants_1.MACHINE_EPSILON);
            } while (s * x0 > s * x);
            if (Math.abs(a) * x * x > Math.abs(d / x)) {
                c2 = -d / x;
                b1 = (c2 - c) / x;
            }
        }
    }
    solveQuadratic(a, b1, c2, sink);
    if (Number.isFinite(x))
        sink.addRoot(x);
}
function bezierSolveCubic(v0, v1, v2, v3, val, sink) {
    if (!((v0 < val && v3 < val && v1 < val && v2 < val) ||
        (v0 > val && v3 > val && v1 > val && v2 > val))) {
        const c = 3 * (v1 - v0), b = 3 * (v2 - v1) - c, a = v3 - v0 - c - b;
        solveCubic(a, b, c, v0 - val, sink);
    }
}
class ClampedRootSink {
    constructor(min, max, fInclusive) {
        this.min = min;
        this.max = max;
        this.fInclusive = fInclusive;
        this.roots = [];
        this.rootCount = 0;
    }
    addRoot(x) {
        let addable = this.fInclusive
            ? x > this.min - constants_1.EPSILON && x < this.max + constants_1.EPSILON
            : x > this.min && x < this.max;
        for (let j = 0; j < this.rootCount; j++) {
            if (this.roots[j] === x)
                addable = false;
        }
        if (addable) {
            this.roots[this.rootCount++] = (0, utility_1.clamp)(x, this.min, this.max);
            return true;
        }
        else {
            return false;
        }
    }
    degenerated() {
        this.rootCount = -1;
    }
}
exports.ClampedRootSink = ClampedRootSink;
