"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fairizeBezierShape = fairizeBezierShape;
exports.fairizeGenericShape = fairizeGenericShape;
const fn_1 = require("../../fn");
const point_1 = require("../../point/point");
const in_place_array_1 = require("../../util/in-place-array");
const shape_to_bez3_1 = require("../shape-to-bez3");
const slice_arc_1 = require("../shared/slice-arc");
const split_at_extrema_1 = require("../shared/split-at-extrema");
const combined_curve_1 = require("./combined-curve");
function fairizeBezierShape(shape) {
    const results = [];
    for (const contour of shape) {
        if (!contour.length)
            continue;
        const bezSlicesContour = [];
        for (const arc of contour)
            bezSlicesContour.push(new slice_arc_1.Bez3Slice(arc.a, arc.b, arc.c, arc.d));
        results.push(fairizeBezierContour(bezSlicesContour));
    }
    return results;
}
function fairizeGenericShape(shape, tolerance = 1 / 16) {
    const results = [];
    for (const contour of shape) {
        results.push(fairizeBezierContour((0, shape_to_bez3_1.convertContourToBez3)(contour, tolerance)));
    }
    return results;
}
function fairizeBezierContour(contour) {
    const splitContour = [];
    inPlaceFilterDegenerates(contour);
    markCornersAndSplit(contour, splitContour);
    canonicalStart(splitContour);
    const results = [];
    let front = 0, rear = 0;
    advanceFront: for (; front < splitContour.length;) {
        for (; rear < splitContour.length; rear++) {
            if (isStopCt(splitContour[rear].cornerTypeAfter)) {
                const c = new combined_curve_1.FairizeCombinedArc(splitContour.slice(front, rear + 1));
                if (!c.isEmpty())
                    results.push(c.reduceIfStraight());
                front = rear = rear + 1;
                continue advanceFront;
            }
        }
        const c = new combined_curve_1.FairizeCombinedArc(splitContour.slice(front));
        if (!c.isEmpty())
            results.push(c.reduceIfStraight());
        front = rear = splitContour.length;
        break;
    }
    return results;
}
function markCornersAndSplit(contour, sink) {
    for (let j = 0; j < contour.length; j++) {
        const cBefore = j === 0 ? contour[contour.length - 1] : contour[j - 1];
        const cAfter = contour[j];
        const z1 = cAfter.a, z0 = cBefore.c, z2 = cAfter.b, z11 = cBefore.d;
        if (!point_1.Point2.from(z1).isClose(z11, fn_1.GEOMETRIC_EPSILON)) {
            cBefore.cornerTypeAfter = cAfter.cornerTypeBefore = slice_arc_1.CornerType.Corner;
        }
        else {
            const hetero = cBefore.isStraight() !== cAfter.isStraight();
            const almostLinear = point_1.Point2.pointLineDist(z0, z2, z1) < fn_1.GEOMETRIC_EPSILON;
            const inBetween = point_1.Offset2.dot(point_1.Offset2.differenceFrom(z0, z1), point_1.Offset2.differenceFrom(z2, z1)) < 0;
            if (hetero) {
                cBefore.cornerTypeAfter = cAfter.cornerTypeBefore = slice_arc_1.CornerType.Hetero;
            }
            else if (almostLinear && inBetween) {
                const isExtrema = !cBefore.isStraight() &&
                    !cAfter.isStraight() &&
                    (Math.abs(z0.x - z2.x) < fn_1.GEOMETRIC_EPSILON ||
                        Math.abs(z0.y - z2.y) < fn_1.GEOMETRIC_EPSILON);
                if (isExtrema) {
                    cBefore.cornerTypeAfter = cAfter.cornerTypeBefore = slice_arc_1.CornerType.Extrema;
                }
                else {
                    cBefore.cornerTypeAfter = cAfter.cornerTypeBefore = slice_arc_1.CornerType.Smooth;
                }
            }
            else {
                cBefore.cornerTypeAfter = cAfter.cornerTypeBefore = slice_arc_1.CornerType.Corner;
            }
        }
    }
    for (let j = 0; j < contour.length; j++) {
        (0, split_at_extrema_1.splitAtExtrema)(contour[j], sink);
    }
}
function inPlaceFilterDegenerates(contour) {
    let i = 0, j = 0;
    while (i < contour.length) {
        const arc = contour[i];
        const isStraight = arc.isStraight();
        const isDegenerate = isStraight && point_1.Point2.areClose(arc.d, arc.a, fn_1.GEOMETRIC_EPSILON);
        if (!isDegenerate) {
            if (isStraight) {
                contour[j++] = arc.forceStraight();
            }
            else {
                contour[j++] = arc;
            }
        }
        i++;
    }
    contour.length = j;
}
function isStopCt(ct) {
    return ct !== slice_arc_1.CornerType.Smooth;
}
function canonicalStart(contour) {
    let zStart = null, jStart = 0;
    for (let j = 0; j < contour.length; j++) {
        if (isStopCt(contour[j].cornerTypeBefore)) {
            if (!zStart ||
                contour[j].a.y < zStart.y ||
                (contour[j].a.y === zStart.y && contour[j].a.x < zStart.x)) {
                zStart = contour[j].a;
                jStart = j;
            }
        }
    }
    if (zStart)
        (0, in_place_array_1.inPlaceRotateArray)(contour, -jStart);
}
