"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TupleVariationWriteOpt = void 0;
const bin_util_1 = require("@ot-builder/bin-util");
const ImpLib = require("@ot-builder/common-impl");
const errors_1 = require("@ot-builder/errors");
const primitive_1 = require("@ot-builder/primitive");
const flags_1 = require("../shared/flags");
const runs_1 = require("../shared/runs");
const collect_1 = require("./collect");
const iup_optimize_1 = require("./iup-optimize");
const tuple_allocator_1 = require("./tuple-allocator");
exports.TupleVariationWriteOpt = (0, bin_util_1.WriteOpt)((source, ctx) => {
    const col = new collect_1.TvsCollector();
    const data = (0, collect_1.collectDeltaData)(col, source.dimensions, source.data);
    const tuc = new tuple_allocator_1.MasterToTupleConverter(ctx.designSpace, !!ctx.forceIntermediate);
    const knownMasters = col.getMasterList();
    if (!knownMasters.length)
        return null;
    if (knownMasters.length > flags_1.TvhSetFlags.COUNT_MASK)
        throw errors_1.Errors.Variation.TooManyMasters();
    const blobResults = [];
    for (const [mid, master] of knownMasters) {
        blobResults.push(writeBlob(source, ctx, tuc, data, mid, master));
    }
    // Write the frag
    const frRoot = new bin_util_1.Frag();
    const frData = new bin_util_1.Frag();
    // - Header
    const fHaveSharedPoints = !ctx.forcePrivatePointNumbers &&
        !ImpLib.BitMask.allTrue(blobResults.map(x => x.embedPointIndex))
        ? flags_1.TvhSetFlags.SHARED_POINT_NUMBERS
        : 0;
    frRoot.uint16(fHaveSharedPoints | blobResults.length);
    frRoot.ptr16(frData);
    // - Shared point numbers
    if (fHaveSharedPoints)
        frData.push(AllPoints, undefined);
    // - Data
    for (const result of blobResults) {
        frData.bytes(result.bufBody);
        frRoot.uint16(result.bufBody.byteLength).bytes(result.bufHeader);
    }
    return frRoot;
});
function writeBlob(source, ctx, tuc, data, mid, master) {
    let result = writeBlobImpl(source, ctx, tuc, data, mid, master, 0);
    if (ctx.iupTolerance) {
        const resOpt = writeBlobImpl(source, ctx, tuc, data, mid, master, ctx.iupTolerance);
        if ((resOpt.bufBody.byteLength <= result.bufBody.byteLength ||
            result.hasNonIntegerDelta) &&
            !resOpt.hasNonIntegerDelta) {
            result = resOpt;
        }
    }
    // if (result.hasNonIntegerDelta) process.stderr.write("Non-integer delta recorded\n");
    return result;
}
function writeBlobImpl(source, ctx, tuc, data, mid, master, tolerance) {
    const frBody = new bin_util_1.Frag();
    const { n, mask, deltas, hasNonIntegerDelta } = decidePointsAndDeltas(source, data, mid, tolerance);
    if (ImpLib.BitMask.allFalseN(n, mask))
        mask[0] = true;
    const embedPointIndex = ctx.forcePrivatePointNumbers || !ImpLib.BitMask.allTrueN(n, mask);
    const hr = writeTupleVariationHeader(ctx, tuc, master, embedPointIndex);
    if (hr.fPrivatePoints)
        frBody.push(PointsMask, n, mask);
    for (let dim = 0; dim < source.dimensions; dim++) {
        frBody.push(DeltaRuns, deltas, mask, source.dimensions, dim);
    }
    return {
        embedPointIndex,
        hasNonIntegerDelta,
        bufHeader: bin_util_1.Frag.pack(hr.frag),
        bufBody: bin_util_1.Frag.pack(frBody)
    };
}
function logChoices(dimensions, coords, deltas, mask) {
    let s = "";
    for (let z = 0; z < mask.length; z++) {
        let r = "";
        for (let dim = 0; dim < dimensions; dim++) {
            const delta = deltas[ImpLib.Arith.d2(dimensions, z, dim)];
            r +=
                (dim ? " " : "") +
                    coords[ImpLib.Arith.d2(dimensions, z, dim)] +
                    (delta >= 0 ? "+" : "") +
                    delta;
        }
        if (mask[z]) {
            r = `\u001b[32m${r}\u001b[0m`;
        }
        s += (z ? "," : "") + r;
    }
    process.stderr.write(s + "\n");
}
function decidePointsAndDeltas(source, data, mid, tolerance) {
    const mask = [];
    const deltas = [];
    const coords = [];
    let hasNonIntegerDelta = false;
    for (let cid = 0; cid < data.length; cid++) {
        const contourCoords = [];
        const contourDeltas = [];
        for (const dd of data[cid]) {
            contourCoords.push(dd.origin || 0);
            contourDeltas.push(dd.resolve()[mid] || 0);
        }
        const n = ImpLib.Arith.rowCount(contourCoords, source.dimensions);
        const counterMask = tolerance
            ? (0, iup_optimize_1.iupOptimize)(source.dimensions, n, contourCoords, contourDeltas, tolerance)
            : ImpLib.BitMask.Trues(n);
        for (let zid = 0; zid < n; zid++) {
            mask.push(!!counterMask[zid]);
            if (counterMask[zid]) {
                for (let dim = 0; dim < source.dimensions; dim++) {
                    const delta = contourDeltas[ImpLib.Arith.d2(source.dimensions, zid, dim)];
                    if (Math.round(delta) !== delta)
                        hasNonIntegerDelta = true;
                }
            }
        }
        for (const d of contourDeltas)
            deltas.push(d);
        for (const d of contourCoords)
            coords.push(d);
    }
    // if (hasNonIntegerDelta) {
    //     logChoices(source.dimensions, coords, deltas, mask);
    // }
    return { n: mask.length, mask, deltas, hasNonIntegerDelta };
}
function writeTupleVariationHeader(ctx, tuc, master, embedPointIndex) {
    const frag = new bin_util_1.Frag();
    const { min, peak, max } = tuc.getTuples(master);
    const { fEmbedPeak, peakTupleID } = tryEmbedPeakTuple(ctx, peak);
    const fIntermediate = ctx.forceIntermediate || min || max ? flags_1.TvhFlags.INTERMEDIATE_REGION : 0;
    const fPrivatePoints = embedPointIndex ? flags_1.TvhFlags.PRIVATE_POINT_NUMBERS : 0;
    frag.uint16(fPrivatePoints | fEmbedPeak | fIntermediate | peakTupleID);
    if (fEmbedPeak)
        frag.arrayN(primitive_1.F2D14, ctx.designSpace.length, peak);
    if (fIntermediate)
        frag.arrayN(primitive_1.F2D14, ctx.designSpace.length, min);
    if (fIntermediate)
        frag.arrayN(primitive_1.F2D14, ctx.designSpace.length, max);
    return { frag, fPrivatePoints };
}
function tryEmbedPeakTuple(ctx, peak) {
    let peakTupleID = 0;
    let fEmbedPeak = ctx.forceEmbedPeak ? flags_1.TvhFlags.EMBEDDED_PEAK_TUPLE : 0;
    if (!fEmbedPeak) {
        if (!ctx.tupleAllocator)
            throw errors_1.Errors.Unreachable();
        peakTupleID = ctx.tupleAllocator.allocate(peak).index;
        if (peakTupleID > flags_1.TvhFlags.TUPLE_INDEX_MASK) {
            fEmbedPeak |= flags_1.TvhFlags.EMBEDDED_PEAK_TUPLE;
            peakTupleID = 0;
        }
    }
    return { fEmbedPeak, peakTupleID };
}
const DeltaRuns = (0, bin_util_1.Write)((frag, deltas, mask, dimensions, dim) => {
    const dp = new runs_1.DeltaRunDp.Writer(3);
    for (let ixDelta = dim, zid = 0; ixDelta < deltas.length; ixDelta += dimensions, zid++) {
        const delta = deltas[ixDelta];
        if (mask[zid])
            dp.update(ImpLib.Arith.Round.Coord(delta));
    }
    dp.write(frag);
});
const AllPoints = (0, bin_util_1.Write)(frag => frag.uint8(0));
const PointsMask = (0, bin_util_1.Write)((frag, n, mask) => {
    if (ImpLib.BitMask.allTrueN(n, mask)) {
        frag.uint8(0);
        return;
    }
    else {
        const dp = new runs_1.PointNumberRunDp.Writer(3);
        let points = 0;
        let last = 0;
        for (let zid = 0; zid < n; zid++) {
            if (mask[zid]) {
                dp.update(zid - last);
                points++;
                last = zid;
            }
        }
        frag.push(runs_1.PointCount, points);
        dp.write(frag);
    }
});
//# sourceMappingURL=index.js.map