"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CombinedArc = exports.Transformed = exports.Mixed3 = exports.Mixed = exports.StraightSegment = exports.Circle = exports.Reverted = exports.Reparametrized = exports.Bez3 = exports.FromXY = void 0;
const fn_1 = require("../fn");
const point_1 = require("../point/point");
const seg_index_search_1 = require("../util/seg-index-search");
class FromXY {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    eval(t) {
        return new point_1.Point2(this.x.eval(t), this.y.eval(t));
    }
    derivative(t) {
        return new point_1.Offset2(this.x.derivative(t), this.y.derivative(t));
    }
}
exports.FromXY = FromXY;
function bez3(a, b, c, d, t) {
    const cot = 1 - t;
    return cot * cot * (a * cot + 3 * b * t) + t * t * (3 * c * cot + d * t);
}
function bezT3(a, b, c, d, t) {
    const cot = 1 - t;
    return 3 * cot * cot * (b - a) + 6 * t * cot * (c - b) + 3 * t * t * (d - c);
}
class Bez3 {
    constructor(a, b, c, d) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
    }
    eval(t) {
        return new point_1.Point2(bez3(this.a.x, this.b.x, this.c.x, this.d.x, t), bez3(this.a.y, this.b.y, this.c.y, this.d.y, t));
    }
    derivative(t) {
        return new point_1.Offset2(bezT3(this.a.x, this.b.x, this.c.x, this.d.x, t), bezT3(this.a.y, this.b.y, this.c.y, this.d.y, t));
    }
    isStraight() {
        const p1 = point_1.Point2.from(this.a), p2 = point_1.Point2.from(this.d);
        const h1 = point_1.Offset2.differenceFrom(this.b, this.a), h2 = point_1.Offset2.differenceFrom(this.c, this.d);
        if (h1.isAlmostZero() || h2.isAlmostZero())
            return true;
        const v = p2.minus(p1);
        if (v.isAlmostZero())
            return false;
        if ((0, fn_1.numberClose)(0, point_1.Point2.pointLineDist(this.a, this.d, this.b)) &&
            (0, fn_1.numberClose)(0, point_1.Point2.pointLineDist(this.a, this.d, this.c))) {
            const div = v.dot(v), s1 = v.dot(h1) / div, s2 = v.dot(h2) / div;
            return s1 >= 0 && s1 <= 1 && s2 <= 0 && s2 >= -1;
        }
        return false;
    }
    static fromStraightSegment(ss) {
        return new Bez3(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);
    }
}
exports.Bez3 = Bez3;
class Reparametrized {
    constructor(c, fn) {
        this.curve = c;
        this.fn = fn;
    }
    eval(t) {
        return this.curve.eval(this.fn.eval(t));
    }
    derivative(t) {
        const d = this.curve.derivative(this.fn.eval(t));
        const dF = this.fn.derivative(t);
        return new point_1.Offset2(d.x * dF, d.y * dF);
    }
}
exports.Reparametrized = Reparametrized;
class Reverted {
    constructor(c) {
        this.curve = c;
    }
    eval(t) {
        return this.curve.eval(1 - t);
    }
    derivative(t) {
        const d = this.curve.derivative(1 - t);
        return new point_1.Offset2(-d.x, -d.y);
    }
}
exports.Reverted = Reverted;
class Circle {
    constructor(cx, cy, radius) {
        this.centerX = cx;
        this.centerY = cy;
        this.radius = radius;
    }
    eval(t) {
        return new point_1.Point2(this.centerX + this.radius * Math.cos(t), this.centerY + this.radius * Math.sin(t));
    }
    derivative(t) {
        return new point_1.Offset2(-this.radius * Math.sin(t), this.radius * Math.cos(t));
    }
}
exports.Circle = Circle;
class StraightSegment {
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
    eval(t) {
        return new point_1.Point2((0, fn_1.mix)(this.a.x, this.b.x, t), (0, fn_1.mix)(this.a.y, this.b.y, t));
    }
    derivative() {
        return new point_1.Offset2(this.b.x - this.a.x, this.b.y - this.a.y);
    }
}
exports.StraightSegment = StraightSegment;
class Mixed {
    constructor(a, b, mix) {
        this.a = a;
        this.b = b;
        this.mix = mix;
    }
    eval(t) {
        const za = this.a.eval(t);
        const zb = this.b.eval(t);
        const m = this.mix.eval(t);
        return new point_1.Point2(za.x + (zb.x - za.x) * m, za.y + (zb.y - za.y) * m);
    }
    derivative(t) {
        const za = this.a.eval(t);
        const zb = this.b.eval(t);
        const dza = this.a.derivative(t);
        const dzb = this.b.derivative(t);
        const m = this.mix.eval(t);
        const dm = this.mix.derivative(t);
        return new point_1.Offset2((1 - m) * dza.x + (zb.x - za.x) * dm + m * dzb.x, (1 - m) * dza.y + (zb.y - za.y) * dm + m * dzb.y);
    }
}
exports.Mixed = Mixed;
class Mixed3 {
    constructor(n, f, a, g, b) {
        this.n = n;
        this.a = a;
        this.b = b;
        this.f = f;
        this.g = g;
    }
    eval(t) {
        const n = this.n.eval(t);
        const a = this.a.eval(t);
        const b = this.b.eval(t);
        const f = this.f.eval(t);
        const g = this.g.eval(t);
        return new point_1.Point2((1 - f - g) * n.x + f * a.x + g * b.x, (1 - f - g) * n.y + f * a.y + g * b.y);
    }
    derivative(t) {
        const n = this.n.eval(t);
        const a = this.a.eval(t);
        const b = this.b.eval(t);
        const f = this.f.eval(t);
        const g = this.g.eval(t);
        const dn = this.n.derivative(t);
        const da = this.a.derivative(t);
        const db = this.b.derivative(t);
        const df = this.f.derivative(t);
        const dg = this.g.derivative(t);
        return new point_1.Offset2(f * da.x + a.x * df + g * db.x + b.x * dg - n.x * (df + dg) - (f + g - 1) * dn.x, f * da.y + a.y * df + g * db.y + b.y * dg - n.y * (df + dg) - (f + g - 1) * dn.y);
    }
}
exports.Mixed3 = Mixed3;
class Transformed {
    constructor(t, c) {
        this.c = c;
        this.tfm = t;
    }
    eval(t) {
        return this.tfm.eval(this.c.eval(t));
    }
    derivative(t) {
        const z = this.c.eval(t);
        const d = this.c.derivative(t);
        const j = this.tfm.derivative(z);
        return new point_1.Offset2(d.x * j.dxx + d.y * j.dxy, d.x * j.dyx + d.y * j.dyy);
    }
}
exports.Transformed = Transformed;
class CombinedArc {
    constructor(measurer, _segments) {
        this.stops = [];
        this.endAdjustments = [];
        this.segments = [..._segments];
        let rear = 0;
        for (let j = 0; j < this.segments.length; j++) {
            if (measurer.measureLength(this.segments[j]) > 0) {
                this.segments[rear++] = this.segments[j];
            }
        }
        this.segments.length = rear;
        let totalLength = 0;
        for (let j = 0; j < this.segments.length; j++) {
            this.stops[j] = totalLength;
            totalLength += measurer.measureLength(this.segments[j]);
        }
        for (let j = 0; j < this.segments.length; j++) {
            this.stops[j] /= totalLength;
        }
        this.totalLength = totalLength;
        for (let i = 0; i < this.segments.length; i++) {
            if (i + 1 < this.segments.length) {
                const a = this.segments[i].eval(1);
                const b = this.segments[i + 1].eval(0);
                this.endAdjustments[i] = point_1.Offset2.differenceFrom(b, a);
            }
            else {
                this.endAdjustments[i] = new point_1.Offset2(0, 0);
            }
        }
    }
    isEmpty() {
        return this.segments.length === 0 || this.totalLength === 0;
    }
    eval(t) {
        const j = (0, seg_index_search_1.segTSearch)(this.stops, t);
        const tBefore = this.stops[j];
        const tNext = j < this.stops.length - 1 ? this.stops[j + 1] : 1;
        let tRelative = (t - tBefore) / (tNext - tBefore);
        if (t <= tBefore)
            tRelative = 0;
        if (t >= tNext)
            tRelative = 1;
        return point_1.Point2.addScale(this.segments[j].eval(tRelative), tRelative, this.endAdjustments[j]);
    }
    derivative(t) {
        const j = (0, seg_index_search_1.segTSearch)(this.stops, t);
        const tBefore = this.stops[j];
        const tNext = j < this.stops.length - 1 ? this.stops[j + 1] : 1;
        let tRelative = (t - tBefore) / (tNext - tBefore);
        if (t <= tBefore)
            tRelative = 0;
        if (t >= tNext)
            tRelative = 1;
        return this.endAdjustments[j]
            .add(this.segments[j].derivative(tRelative))
            .scale(1 / (tNext - tBefore));
    }
}
exports.CombinedArc = CombinedArc;
