"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClassMatrix = exports.AdjStore = void 0;
const ot_layout_1 = require("@ot-builder/ot-layout");
const primitive_1 = require("@ot-builder/primitive");
const class_def_1 = require("../../shared/class-def");
const coverage_1 = require("../../shared/coverage");
const gpos_adjust_1 = require("../../shared/gpos-adjust");
class AdjStore {
    constructor(indexMatrix, adjustments) {
        this.indexMatrix = indexMatrix;
        this.adjustments = adjustments;
    }
}
exports.AdjStore = AdjStore;
class ClassMatrix {
    constructor(cFirst, // Per-class glyph list for first glyph. Class 0 reserved for neutral
    cSecond, // Per-class glyph list for second glyph. Class 0 reserved for neutral
    // cSecond must cover all glyphs present in the font, so when cleaning up zero "columns" we
    // must move then to class 0 rather than simply delete them.
    adjStore // Adjustments matrix
    ) {
        this.cFirst = cFirst;
        this.cSecond = cSecond;
        this.adjStore = adjStore;
    }
    derive() {
        const copy = new ClassMatrix([], [], this.adjStore);
        for (const c of this.cSecond)
            copy.cSecond.push([...c]);
        return copy;
    }
    bisect(allowUneven) {
        return BisectClassMatrixImpl.bisect(this, allowUneven);
    }
    mergeFirstClass(c1p, c1t) {
        for (const e of this.cFirst[c1t])
            this.cFirst[c1p].push(e);
        this.cFirst[c1t].length = 0;
    }
    mergeSecondClass(c2p, c2t) {
        for (const e of this.cSecond[c2t])
            this.cSecond[c2p].push(e);
        this.cSecond[c2t].length = 0;
    }
    firstClassValid(c1) {
        return this.cFirst[c1] && this.cFirst[c1].length;
    }
    secondClassValid(c2) {
        return this.cSecond[c2] && this.cSecond[c2].length;
    }
    getEffectiveFirstClasses() {
        let eff = 0;
        for (let c1 = 0; c1 < this.cFirst.length; c1++)
            if (this.firstClassValid(c1))
                eff++;
        return eff;
    }
    getEffectiveSecondClasses() {
        let eff = 0;
        for (let c2 = 0; c2 < this.cSecond.length; c2++)
            if (this.secondClassValid(c2))
                eff++;
        return eff;
    }
    eliminateZeroClasses() {
        for (let c1 = 0; c1 < this.cFirst.length; c1++) {
            if (!this.firstClassValid(c1))
                continue;
            let nonzero = false;
            for (let c2 = 1; c2 < this.cSecond.length; c2++) {
                if (!this.secondClassValid(c2))
                    continue;
                if (this.adjStore.indexMatrix[c1][c2])
                    nonzero = true;
            }
            if (!nonzero)
                this.cFirst[c1].length = 0;
        }
        for (let c2 = 1; c2 < this.cSecond.length; c2++) {
            if (!this.secondClassValid(c2))
                continue;
            let nonzero = false;
            for (let c1 = 1; c1 < this.cFirst.length; c1++) {
                if (!this.firstClassValid(c1))
                    continue;
                if (this.adjStore.indexMatrix[c1][c2])
                    nonzero = true;
            }
            if (!nonzero)
                this.mergeSecondClass(0, c2);
        }
    }
    get(c1, c2) {
        return (this.adjStore.adjustments[this.adjStore.indexMatrix[c1][c2]] || ot_layout_1.Gpos.ZeroAdjustmentPair);
    }
    static analyze(ds, ctx) {
        return AnalyzeClassMatrixImpl.main(ds, ctx);
    }
    measure() {
        return MeasureClassMatrixImpl.measure(this);
    }
    sort(ord) {
        const [c1Relocation, c2Relocation] = this.getRelocation(ord);
        const c1a = [];
        const c2a = [];
        for (let c1 = 0; c1 < c1Relocation.length; c1++) {
            c1a[c1] = this.cFirst[c1Relocation[c1]];
        }
        for (let c2 = 0; c2 < c2Relocation.length; c2++) {
            c2a[c2] = this.cSecond[c2Relocation[c2]];
        }
        const indexMatrix = [];
        for (let c1 = 0; c1 < c1Relocation.length; c1++) {
            indexMatrix[c1] = [];
            for (let c2 = 0; c2 < c2Relocation.length; c2++) {
                const srcRow = this.adjStore.indexMatrix[c1Relocation[c1]] || [];
                indexMatrix[c1][c2] = srcRow[c2Relocation[c2]] || 0;
            }
        }
        this.cFirst = c1a;
        this.cSecond = c2a;
        this.adjStore = new AdjStore(indexMatrix, this.adjStore.adjustments);
    }
    getRelocation(ord) {
        const c1RelocationRaw = [];
        const c2RelocationRaw = [];
        for (let c1 = 0; c1 < this.cFirst.length; c1++) {
            if (c1 === 0 || !this.firstClassValid(c1))
                continue;
            const gids = [];
            for (const g of this.cFirst[c1])
                gids.push(ord.reverse(g));
            gids.sort((a, b) => a - b);
            c1RelocationRaw.push([c1, gids]);
        }
        for (let c2 = 0; c2 < this.cSecond.length; c2++) {
            if (c2 === 0 || !this.secondClassValid(c2))
                continue;
            const gids = [];
            for (const g of this.cSecond[c2])
                gids.push(ord.reverse(g));
            gids.sort((a, b) => a - b);
            c2RelocationRaw.push([c2, gids]);
        }
        c1RelocationRaw.sort(compareRelocation);
        c2RelocationRaw.sort(compareRelocation);
        const c1Relocation = [0, ...c1RelocationRaw.map(x => x[0])];
        const c2Relocation = [0, ...c2RelocationRaw.map(x => x[0])];
        return [c1Relocation, c2Relocation];
    }
}
exports.ClassMatrix = ClassMatrix;
function compareRelocation(a, b) {
    if (a[1].length > b[1].length)
        return -1;
    if (a[1].length < b[1].length)
        return +1;
    for (let k = 0; k < a[1].length && k < b[1].length; k++) {
        if (a[1][k] < b[1][k])
            return -1;
        if (a[1][k] > b[1][k])
            return +1;
    }
    return 0;
}
var AnalyzeClassMatrixImpl;
(function (AnalyzeClassMatrixImpl) {
    class AdjAllocator {
        constructor() {
            this.adjList = [ot_layout_1.Gpos.ZeroAdjustmentPair];
            this.hCache = new Map();
            this.nn = 1;
        }
        put(adj, ctx) {
            if (!adj)
                return 0;
            const h = gpos_adjust_1.GposAdjustment.hashPair(adj, ctx.ivs);
            if (!h)
                return 0;
            const e = this.hCache.get(h);
            if (e) {
                return e;
            }
            else {
                const result = this.nn;
                this.adjList[this.nn] = adj;
                this.hCache.set(h, this.nn);
                this.nn++;
                return result;
            }
        }
    }
    function analyzeAdjStore(coiFirst, coiSecond, ds, ctx) {
        const cFirst = [];
        const cSecond = [];
        const mat = [];
        const alloc = new AdjAllocator();
        for (const [c1, gs1] of coiFirst)
            cFirst[c1 + 1] = gs1; // +1 since we have "outside" class
        for (const [c2, gs2] of coiSecond)
            cSecond[c2 + 1] = gs2;
        for (const [c1, gs1] of coiFirst) {
            mat[c1 + 1] = [];
            for (const [c2, gs2] of coiSecond) {
                mat[c1 + 1][c2 + 1] = alloc.put(ds.getByClass(c1, c2), ctx);
            }
        }
        return new ClassMatrix(cFirst, cSecond, new AdjStore(mat, alloc.adjList));
    }
    function padNeutrals(cc, ctx) {
        const sCov = new Set();
        for (const gc of cc)
            if (gc)
                for (const x of gc)
                    sCov.add(x);
        const outside = [];
        for (const g of ctx.gOrd)
            if (!sCov.has(g))
                outside.push(g);
        const coi = [[-1, outside]]; // temporary class "-1" for outsiders
        for (let c = 0; c < cc.length; c++) {
            coi.push([c, cc[c]]);
        }
        coi.sort((a, b) => b[1].length - a[1].length);
        return coi;
    }
    function main(ds, ctx) {
        const _cFirst = ds.getXClassDef();
        const _cSecond = ds.getYClassDef();
        const coiFirst = padNeutrals(_cFirst, ctx);
        const coiSecond = padNeutrals(_cSecond, ctx);
        return analyzeAdjStore(coiFirst, coiSecond, ds, ctx);
    }
    AnalyzeClassMatrixImpl.main = main;
})(AnalyzeClassMatrixImpl || (AnalyzeClassMatrixImpl = {}));
var MeasureClassMatrixImpl;
(function (MeasureClassMatrixImpl) {
    function measureEffectCount(cls) {
        let count = 0, // quantity of valid rows
        glyphs = 0; // quantity of glyphs in valid rows
        for (let c = 0; c < cls.length; c++) {
            if (!cls[c].length)
                continue;
            count++;
            glyphs += cls[c].length;
        }
        return { count, glyphs };
    }
    function measure(cm) {
        const effFst = measureEffectCount(cm.cFirst);
        const effSnd = measureEffectCount(cm.cSecond);
        let format1 = 0;
        let format2 = 0;
        for (let c1 = 0; c1 < cm.cFirst.length; c1++) {
            if (!cm.firstClassValid(c1))
                continue;
            for (let c2 = 0; c2 < cm.cSecond.length; c2++) {
                if (!cm.secondClassValid(c2))
                    continue;
                const entryAdj = cm.get(c1, c2);
                const entryFormat1 = gpos_adjust_1.GposAdjustment.decideFormat(entryAdj[0]);
                const entryFormat2 = gpos_adjust_1.GposAdjustment.decideFormat(entryAdj[1]);
                format1 |= entryFormat1;
                format2 |= entryFormat2;
            }
        }
        let dataSize = 0;
        for (let c1 = 0; c1 < cm.cFirst.length; c1++) {
            if (!cm.firstClassValid(c1))
                continue;
            for (let c2 = 0; c2 < cm.cSecond.length; c2++) {
                if (!cm.secondClassValid(c2))
                    continue;
                const cellAdj = cm.get(c1, c2);
                dataSize +=
                    gpos_adjust_1.GposAdjustment.measure(cellAdj[0], format1) +
                        gpos_adjust_1.GposAdjustment.measure(cellAdj[1], format2);
            }
        }
        return {
            effFst,
            effSnd,
            size: primitive_1.UInt16.size *
                (8 +
                    effFst.glyphs * (class_def_1.MaxClsDefItemWords + coverage_1.MaxCovItemWords) + // 1 cov + 1 cls
                    effSnd.glyphs * class_def_1.MaxClsDefItemWords) + // 1 class def
                dataSize // Actual Data
        };
    }
    MeasureClassMatrixImpl.measure = measure;
})(MeasureClassMatrixImpl || (MeasureClassMatrixImpl = {}));
var BisectClassMatrixImpl;
(function (BisectClassMatrixImpl) {
    function computeC1NonzeroCount(cm, c1) {
        let n = 0;
        for (let c2 = 0; c2 < cm.cSecond.length; c2++) {
            if (cm.secondClassValid(c2) && cm.adjStore.indexMatrix[c1][c2])
                n++;
        }
        return n;
    }
    function computeC1Difference(cm, c1p, c1t) {
        let diffCount = 0;
        for (let c2 = 0; c2 < cm.cSecond.length; c2++) {
            if (!cm.secondClassValid(c2))
                continue;
            const presenceDifferent = !!cm.adjStore.indexMatrix[c1p][c2] !== !!cm.adjStore.indexMatrix[c1t][c2];
            const valueDifferent = cm.adjStore.indexMatrix[c1p][c2] !== cm.adjStore.indexMatrix[c1t][c2];
            diffCount += (presenceDifferent ? 4 : 1) * (valueDifferent ? 1 : 0);
        }
        return diffCount;
    }
    function findRowWithMaxNonZero(cm) {
        let c1MaxNonZero = -1, maxNonZeroCount = 0;
        for (let c1 = 0; c1 < cm.cFirst.length; c1++) {
            if (!cm.firstClassValid(c1))
                continue;
            const n = computeC1NonzeroCount(cm, c1);
            if (n > maxNonZeroCount) {
                maxNonZeroCount = n;
                c1MaxNonZero = c1;
            }
        }
        return c1MaxNonZero;
    }
    const UnevenMultiplier = 16;
    function isHighlyUneven(upperHalfClassCount, lowerHalfClassCount) {
        return (upperHalfClassCount * UnevenMultiplier < upperHalfClassCount + lowerHalfClassCount ||
            lowerHalfClassCount * UnevenMultiplier < upperHalfClassCount + lowerHalfClassCount);
    }
    function bisect(cm, allowUneven) {
        const cm1 = cm.derive();
        const cm2 = cm.derive();
        // Leave first class 0 empty
        cm1.cFirst[0] = [];
        cm2.cFirst[0] = [];
        // Pick a row with most non-zero entries -- use it as pivot for bisecting
        const c1MaxNonZero = findRowWithMaxNonZero(cm);
        if (c1MaxNonZero < 0)
            return bisectClassMatrixEvenly(cm);
        const c1DiffArray = [];
        let sumDiff = 0, nDiff = 0;
        for (let c1 = 0; c1 < cm.cFirst.length; c1++) {
            if (!cm.firstClassValid(c1))
                continue;
            const diff = computeC1Difference(cm, c1MaxNonZero, c1);
            sumDiff += diff;
            nDiff += 1;
            c1DiffArray[c1] = diff;
        }
        // We'd like to bisect the subtable by selecting the rows that "not differ too much"
        // with the chosen row
        // Process non-zero classes
        let upperHalfClassCount = 0, lowerHalfClassCount = 0;
        for (let c1 = 1; c1 < cm.cFirst.length; c1++) {
            if (cm.firstClassValid(c1)) {
                const diff = c1DiffArray[c1];
                if (nDiff * diff < sumDiff) {
                    cm1.cFirst.push([...cm.cFirst[c1]]);
                    cm2.cFirst.push([]);
                    upperHalfClassCount++;
                }
                else {
                    cm1.cFirst.push([]);
                    cm2.cFirst.push([...cm.cFirst[c1]]);
                    lowerHalfClassCount++;
                }
            }
            else {
                cm1.cFirst.push([]);
                cm2.cFirst.push([]);
            }
        }
        // Subtable is highly unevenly bisected. Fallback to the even plan
        if (!allowUneven && isHighlyUneven(upperHalfClassCount, lowerHalfClassCount)) {
            return bisectClassMatrixEvenly(cm);
        }
        else {
            return [cm1, cm2];
        }
    }
    BisectClassMatrixImpl.bisect = bisect;
    function bisectClassMatrixEvenly(cm) {
        const upperHalf = cm.derive();
        const lowerHalf = cm.derive();
        const effectiveClasses = cm.getEffectiveFirstClasses();
        // Leave first class 0 empty
        let addedClasses = 0;
        upperHalf.cFirst[0] = [];
        lowerHalf.cFirst[0] = [];
        for (let c1 = 1; c1 < cm.cFirst.length; c1++) {
            if (cm.firstClassValid(c1)) {
                if (addedClasses * 2 < effectiveClasses) {
                    upperHalf.cFirst.push([...cm.cFirst[c1]]);
                    lowerHalf.cFirst.push([]);
                }
                else {
                    upperHalf.cFirst.push([]);
                    lowerHalf.cFirst.push([...cm.cFirst[c1]]);
                }
                addedClasses++;
            }
            else {
                upperHalf.cFirst.push([]);
                lowerHalf.cFirst.push([]);
            }
        }
        return [upperHalf, lowerHalf];
    }
})(BisectClassMatrixImpl || (BisectClassMatrixImpl = {}));
//# sourceMappingURL=class-matrix.js.map