"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutlierTree = void 0;
exports.shareColumns = shareColumns;
exports.analyzeOutlier = analyzeOutlier;
const errors_1 = require("@ot-builder/errors");
const primitive_1 = require("@ot-builder/primitive");
const coverage_1 = require("../../shared/coverage");
const gpos_adjust_1 = require("../../shared/gpos-adjust");
class OutlierTree {
    constructor() {
        this.mapping = new Map();
    }
    add(fst, snd, adj) {
        let mSnd = this.mapping.get(fst);
        if (!mSnd) {
            mSnd = new Map();
            this.mapping.set(fst, mSnd);
        }
        if (!mSnd.has(snd))
            mSnd.set(snd, adj);
    }
    get size() {
        return this.mapping.size;
    }
    measure() {
        let s = primitive_1.UInt16.size
            * (5 + this.mapping.size * (2 /* ptr + pair value count */ + coverage_1.MaxCovItemWords));
        let format1 = 0;
        let format2 = 0;
        for (const m of this.mapping.values()) {
            for (const cellAdj of m.values()) {
                const entryFormat1 = gpos_adjust_1.GposAdjustment.decideFormat(cellAdj[0]);
                const entryFormat2 = gpos_adjust_1.GposAdjustment.decideFormat(cellAdj[1]);
                format1 |= entryFormat1;
                format2 |= entryFormat2;
            }
        }
        for (const m of this.mapping.values()) {
            for (const entryAdj of m.values()) {
                s +=
                    primitive_1.UInt16.size
                        + gpos_adjust_1.GposAdjustment.measure(entryAdj[0], format1)
                        + gpos_adjust_1.GposAdjustment.measure(entryAdj[1], format2);
            }
        }
        return s;
    }
    bisect() {
        const n = Math.floor(this.mapping.size / 2);
        const dt1 = new OutlierTree();
        const dt2 = new OutlierTree();
        let placed = 0;
        for (const [g, m] of this.mapping) {
            if (placed < n)
                dt1.mapping.set(g, m);
            else
                dt2.mapping.set(g, m);
            placed++;
        }
        return [dt1, dt2];
    }
}
exports.OutlierTree = OutlierTree;
function shareColumns(cm) {
    const outliers = new OutlierTree();
    OutlierAnalyzerImpl.shareColumnsImpl(cm, outliers);
    cm.eliminateZeroClasses();
    if (outliers.size)
        throw errors_1.Errors.Unreachable();
}
function analyzeOutlier(cm) {
    const outliers = new OutlierTree();
    OutlierAnalyzerImpl.analyzeOutlierImpl(cm, outliers);
    cm.eliminateZeroClasses();
    return outliers;
}
var OutlierAnalyzerImpl;
(function (OutlierAnalyzerImpl) {
    // Outlier analyzers are really "symmetric": we find that one "row" (or "column") that is
    // slightly different with somewhere else. After that we will merge them. ClassMatrix' classes
    // are ordered by item count so a linear search would provide acceptable results
    function sinkSum(cc, sink) {
        let s = 0;
        for (const c of sink)
            s += cc[c].length;
        return s;
    }
    let First;
    (function (First) {
        function getDiff(cm, c1p, c1t, sink) {
            for (let c2 = 0; c2 < cm.cSecond.length; c2++) {
                if (!cm.cSecond[c2].length)
                    continue;
                const pivot = cm.adjStore.indexMatrix[c1p][c2], test = cm.adjStore.indexMatrix[c1t][c2];
                if (pivot !== test) {
                    if (test) {
                        sink.push(c2);
                    }
                    else {
                        return true;
                    }
                }
            }
            return false;
        }
        function fetch(outliers, cm, c1p, c1t, sink) {
            let pc = 0;
            for (const c2 of sink) {
                for (const g1 of cm.cFirst[c1t]) {
                    for (const g2 of cm.cSecond[c2]) {
                        outliers.add(g1, g2, cm.get(c1t, c2));
                        pc++;
                    }
                }
            }
            cm.mergeFirstClass(c1p, c1t);
            return pc;
        }
        function analyze(cm, outliers, maxDiff) {
            let pc = 0;
            let columns = 0;
            const sink = [];
            for (const col of cm.cSecond)
                if (col.length)
                    columns++;
            for (let c1p = 0; c1p < cm.cFirst.length; c1p++) {
                if (!cm.firstClassValid(c1p))
                    continue;
                for (let c1t = 0; c1t < cm.cFirst.length; c1t++) {
                    if (c1p === c1t || !cm.firstClassValid(c1t))
                        continue;
                    sink.length = 0;
                    if (getDiff(cm, c1p, c1t, sink))
                        continue;
                    const ss = cm.cFirst[c1t].length * sinkSum(cm.cSecond, sink);
                    if (ss <= maxDiff && ss * 2 < columns) {
                        pc += fetch(outliers, cm, c1p, c1t, sink);
                    }
                }
            }
            return pc;
        }
        First.analyze = analyze;
    })(First || (First = {}));
    let Second;
    (function (Second) {
        function getDiff(cm, c2p, c2t, sink) {
            for (let c1 = 0; c1 < cm.cFirst.length; c1++) {
                if (!cm.cFirst[c1].length)
                    continue;
                const pivot = cm.adjStore.indexMatrix[c1][c2p], test = cm.adjStore.indexMatrix[c1][c2t];
                if (pivot !== test) {
                    if (test) {
                        sink.push(c1);
                    }
                    else {
                        return true;
                    }
                }
            }
            return false;
        }
        function fetch(outliers, cm, c2p, c2t, sink) {
            let pc = 0;
            for (const c1 of sink) {
                for (const g1 of cm.cFirst[c1]) {
                    for (const g2 of cm.cSecond[c2t]) {
                        outliers.add(g1, g2, cm.get(c1, c2t));
                        pc++;
                    }
                }
            }
            cm.mergeSecondClass(c2p, c2t);
            return pc;
        }
        function analyze(cm, outliers, maxDiff) {
            let pc = 0;
            let rows = 0;
            const sink = [];
            for (const row of cm.cFirst)
                if (row.length)
                    rows++;
            for (let c2p = 0; c2p < cm.cSecond.length; c2p++) {
                if (!cm.secondClassValid(c2p))
                    continue;
                for (let c2t = 0; c2t < cm.cSecond.length; c2t++) {
                    if (c2t === c2p || !cm.secondClassValid(c2t))
                        continue;
                    sink.length = 0;
                    if (getDiff(cm, c2p, c2t, sink))
                        continue;
                    const ss = cm.cSecond[c2t].length * sinkSum(cm.cFirst, sink);
                    if (ss <= maxDiff && ss * 2 < rows) {
                        pc += fetch(outliers, cm, c2p, c2t, sink);
                    }
                }
            }
            return pc;
        }
        Second.analyze = analyze;
    })(Second || (Second = {}));
    function shareColumnsImpl(cm, outliers) {
        if (cm.cSecond.length >= cm.cFirst.length) {
            Second.analyze(cm, outliers, 0);
            First.analyze(cm, outliers, 0);
        }
        else {
            First.analyze(cm, outliers, 0);
            Second.analyze(cm, outliers, 0);
        }
    }
    OutlierAnalyzerImpl.shareColumnsImpl = shareColumnsImpl;
    function analyzeOutlierImpl(cm, outliers) {
        let md = 1;
        while (md <= 0xffff) {
            let pc = 0;
            if (cm.cSecond.length >= cm.cFirst.length) {
                pc += Second.analyze(cm, outliers, md);
                pc += First.analyze(cm, outliers, md);
            }
            else {
                pc += First.analyze(cm, outliers, md);
                pc += Second.analyze(cm, outliers, md);
            }
            if (pc <= 0)
                md *= 4;
        }
    }
    OutlierAnalyzerImpl.analyzeOutlierImpl = analyzeOutlierImpl;
})(OutlierAnalyzerImpl || (OutlierAnalyzerImpl = {}));
//# sourceMappingURL=outliers.js.map