"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GposMarkToMarkWriter = exports.GposMarkToLigatureWriter = exports.GposMarkToBaseWriter = void 0;
const bin_util_1 = require("@ot-builder/bin-util");
const ot_layout_1 = require("@ot-builder/ot-layout");
const primitive_1 = require("@ot-builder/primitive");
const general_1 = require("../gsub-gpos-shared/general");
const coverage_1 = require("../shared/coverage");
const gpos_anchor_1 = require("../shared/gpos-anchor");
const MarkArray = {
    write(frag, axm, relocation, ctx) {
        frag.uint16(axm.length); // markCount
        for (const [gid, smr] of axm) {
            frag.uint16(relocation.forward[smr.class]) // markClass
                .push(gpos_anchor_1.Ptr16GposAnchor, smr.anchor, ctx.ivs); // markAnchorOffset
        }
    }
};
function getBaseAnchor(clsSubtable, record, relocation) {
    return record.baseAnchors[relocation.reward[clsSubtable]];
}
const BaseArray = {
    write(frag, axm, relocation, ctx) {
        frag.uint16(axm.length);
        for (const [gid, br] of axm) {
            for (let mc = 0; mc < relocation.reward.length; mc++) {
                const anchor = br.baseAnchors[relocation.reward[mc]];
                frag.push(gpos_anchor_1.NullablePtr16GposAnchor, anchor, ctx.ivs);
            }
        }
    }
};
function getLigatureAnchor(clsSubtable, component, record, relocation) {
    return (record.baseAnchors[component] || [])[relocation.reward[clsSubtable]];
}
const LigatureArray = {
    write(frag, axm, relocation, ctx) {
        frag.uint16(axm.length);
        for (const [gid, br] of axm) {
            const fLigatureAttach = frag.ptr16New();
            fLigatureAttach.uint16(br.baseAnchors.length);
            for (let component = 0; component < br.baseAnchors.length; component++) {
                for (let mc = 0; mc < relocation.reward.length; mc++) {
                    const anchor = getLigatureAnchor(mc, component, br, relocation);
                    fLigatureAttach.push(gpos_anchor_1.NullablePtr16GposAnchor, anchor, ctx.ivs);
                }
            }
        }
    }
};
class MarkWritePlan {
    constructor(marks, rawBases) {
        this.marks = marks;
        this.relocation = this.getMarkPlanRelocation(marks);
        this.bases = new Map();
        for (const [g, br] of rawBases) {
            if (this.baseIsSubstantial(br))
                this.bases.set(g, br);
        }
    }
    isEmpty() {
        return !this.marks.length || !this.bases.size;
    }
    getMarkPlanRelocation(plan) {
        const hasCls = [];
        for (const record of plan) {
            hasCls[record.class] = (hasCls[record.class] || 0) + 1;
        }
        let newCls = 0;
        const relocation = [];
        const revRelocation = [];
        for (let cls = 0; cls < hasCls.length; cls++) {
            if (hasCls[cls]) {
                relocation[cls] = newCls;
                revRelocation[newCls] = cls;
                newCls++;
            }
            else {
                relocation[cls] = -1;
            }
        }
        return { forward: relocation, reward: revRelocation };
    }
    autoBisect(ivs, limit, d = 0) {
        if (this.measure(ivs) < limit) {
            return [this];
        }
        else {
            const plan = this.bisect(ivs);
            if (!plan)
                return [this];
            const [lower, upper] = plan;
            return [
                ...lower.autoBisect(ivs, limit, d + 1),
                ...upper.autoBisect(ivs, limit, d + 1)
            ];
        }
    }
    bisect(ivs) {
        let planMark = null;
        let planBase = null;
        if (this.marks.length > 1)
            planMark = this.bisectImplByMarks();
        if (this.bases.size > 1)
            planBase = this.bisectImplByBases();
        if (!planBase)
            return planMark;
        if (planMark
            && planMark[0].measure(ivs) + planMark[1].measure(ivs)
                < planBase[0].measure(ivs) + planBase[1].measure(ivs)) {
            return planMark;
        }
        else {
            return planBase;
        }
    }
    bisectImplByMarks() {
        const n = Math.floor(this.marks.length / 2);
        return [
            this.sub(this.marks.slice(0, n), this.bases),
            this.sub(this.marks.slice(n), this.bases)
        ];
    }
    bisectImplByBases() {
        const basesLower = new Map(), basesUpper = new Map();
        let nth = 0;
        for (const [g, br] of this.bases) {
            if (nth * 2 < this.bases.size) {
                basesLower.set(g, br);
            }
            else {
                basesUpper.set(g, br);
            }
            nth++;
        }
        return [this.sub(this.marks, basesLower), this.sub(this.marks, basesUpper)];
    }
    getMarkAxm(gOrd) {
        return coverage_1.CovUtils.auxMapFromExtractor(this.marks, gOrd, r => r.glyph);
    }
}
class MarkBaseWritePlan extends MarkWritePlan {
    baseIsSubstantial(br) {
        for (const bc of this.relocation.reward)
            if (MarkBaseWritePlan.baseCoversMarkClass(bc, br))
                return true;
        return false;
    }
    measure(ivs) {
        const anchorSet = new Set();
        let size = primitive_1.UInt16.size * 8;
        for (const rec of this.marks) {
            size +=
                primitive_1.UInt16.size * (2 + coverage_1.MaxCovItemWords) // 1 cov item + 1 mark class id + 1 ptr
                    + gpos_anchor_1.GposAnchor.hashMeasure(anchorSet, ivs, rec.anchor);
        }
        for (const [g, br] of this.bases) {
            size += primitive_1.UInt16.size * (coverage_1.MaxCovItemWords + this.relocation.reward.length); // cov + ptr arr
            for (let clsAnchor = 0; clsAnchor < this.relocation.reward.length; clsAnchor++) {
                const anchor = getBaseAnchor(clsAnchor, br, this.relocation);
                size += gpos_anchor_1.GposAnchor.hashMeasure(anchorSet, ivs, anchor);
            }
        }
        return size;
    }
    sub(marks, bases) {
        return new MarkBaseWritePlan(marks, bases);
    }
    write(frag, ctx) {
        const axmMarks = this.getMarkAxm(ctx.gOrd);
        const axmBases = coverage_1.CovUtils.auxMapFromMap(this.bases, ctx.gOrd);
        frag.uint16(1)
            .push(coverage_1.Ptr16GidCoverage, coverage_1.CovUtils.gidListFromAuxMap(axmMarks), ctx.trick)
            .push(coverage_1.Ptr16GidCoverage, coverage_1.CovUtils.gidListFromAuxMap(axmBases), ctx.trick)
            .uint16(this.relocation.reward.length)
            .ptr16(bin_util_1.Frag.from(MarkArray, axmMarks, this.relocation, ctx))
            .ptr16(bin_util_1.Frag.from(BaseArray, axmBases, this.relocation, ctx));
    }
    static baseCoversMarkClass(mc, br) {
        return br && !!br.baseAnchors[mc];
    }
}
class MarkLigatureWritePlan extends MarkWritePlan {
    baseIsSubstantial(br) {
        for (const bc of this.relocation.reward) {
            if (MarkLigatureWritePlan.baseCoversMarkClass(bc, br))
                return true;
        }
        return false;
    }
    measure(ivs) {
        const anchorSet = new Set();
        let size = primitive_1.UInt16.size * 8;
        for (const rec of this.marks) {
            size +=
                primitive_1.UInt16.size * (2 + coverage_1.MaxCovItemWords)
                    + gpos_anchor_1.GposAnchor.hashMeasure(anchorSet, ivs, rec.anchor);
        }
        for (const [g, br] of this.bases) {
            size +=
                primitive_1.UInt16.size
                    * (2
                        + coverage_1.MaxCovItemWords //1 cov + 1 ptr + 1 component count
                        + br.baseAnchors.length * this.relocation.reward.length);
            for (let component = 0; component < br.baseAnchors.length; component++) {
                for (let clsAnchor = 0; clsAnchor < this.relocation.reward.length; clsAnchor++) {
                    const anchor = getLigatureAnchor(clsAnchor, component, br, this.relocation);
                    size += gpos_anchor_1.GposAnchor.hashMeasure(anchorSet, ivs, anchor);
                }
            }
        }
        return size;
    }
    sub(marks, bases) {
        return new MarkLigatureWritePlan(marks, bases);
    }
    write(frag, ctx) {
        const axmMarks = this.getMarkAxm(ctx.gOrd);
        const axmBases = coverage_1.CovUtils.auxMapFromMap(this.bases, ctx.gOrd);
        frag.uint16(1)
            .push(coverage_1.Ptr16GidCoverage, coverage_1.CovUtils.gidListFromAuxMap(axmMarks), ctx.trick)
            .push(coverage_1.Ptr16GidCoverage, coverage_1.CovUtils.gidListFromAuxMap(axmBases), ctx.trick)
            .uint16(this.relocation.reward.length)
            .ptr16(bin_util_1.Frag.from(MarkArray, axmMarks, this.relocation, ctx))
            .ptr16(bin_util_1.Frag.from(LigatureArray, axmBases, this.relocation, ctx));
    }
    static baseCoversMarkClass(mc, br) {
        for (const component of br.baseAnchors)
            if (component[mc])
                return true;
        return false;
    }
}
class GposMarkWriterBase {
    createSubtableFragmentsImpl(cls, marks, bases, ctx) {
        const frags = [];
        for (const stpStart of this.getInitialPlans(cls, marks, bases)) {
            const stPlans = stpStart.autoBisect(ctx.ivs, general_1.SubtableSizeLimit);
            for (const stp of stPlans) {
                if (stp.isEmpty())
                    continue;
                frags.push(bin_util_1.Frag.from(stp, ctx));
            }
        }
        return frags;
    }
    getInitialPlans(cls, marks, bases) {
        const maxCls = this.getMaxAnchorClass(marks);
        const clsSetAdded = new Set();
        const plans = [];
        for (;;) {
            const plan = this.fetchValidPlan(cls, marks, bases, maxCls, clsSetAdded);
            if (plan)
                plans.push(plan);
            else
                break;
        }
        return plans;
    }
    getMaxAnchorClass(marks) {
        let mc = 0;
        for (const [g, ma] of marks) {
            for (let cls = 0; cls < ma.markAnchors.length; cls++) {
                if (ma.markAnchors[cls] && cls + 1 > mc)
                    mc = cls + 1;
            }
        }
        return mc;
    }
    fetchValidPlan(cls, marks, bases, maxCls, clsSetAdded) {
        let firstClass = true;
        const planBases = new Map();
        const planMarks = new Map();
        loopCls: for (let c = 0; c < maxCls; c++) {
            if (clsSetAdded.has(c))
                continue;
            // Detect that we have at least one base that contains this class
            // If not, just treat this class processed and move on
            if (!this.basesHasAtLeastOneBaseInThisClass(cls, c, bases)) {
                clsSetAdded.add(c);
                continue;
            }
            let fConflict = false;
            const currentClassMarks = new Map();
            // Process mark list: ensure this class doesn't conflict with existing marks
            for (const [g, ma] of marks) {
                const anchor = ma.markAnchors[c];
                if (!anchor)
                    continue;
                currentClassMarks.set(g, { glyph: g, class: c, anchor });
                if (planMarks.has(g))
                    fConflict = true;
            }
            if (fConflict)
                break;
            // Ensure the base array is a rectangular matrix
            if (firstClass) {
                firstClass = false;
                for (const [g, br] of bases) {
                    if (cls.baseCoversMarkClass(c, br))
                        planBases.set(g, br);
                }
            }
            else {
                for (const [g, br] of bases) {
                    if (planBases.has(g) !== cls.baseCoversMarkClass(c, br))
                        break loopCls;
                }
            }
            // Copy
            for (const [g, mr] of currentClassMarks)
                planMarks.set(g, mr);
            clsSetAdded.add(c);
        }
        if (planMarks.size && planBases.size) {
            return new cls(Array.from(planMarks.values()), planBases);
        }
        else {
            return null;
        }
    }
    basesHasAtLeastOneBaseInThisClass(cls, c, bases) {
        for (const [g, br] of bases) {
            if (cls.baseCoversMarkClass(c, br)) {
                return true;
            }
        }
        return false;
    }
}
class GposMarkToBaseWriter extends GposMarkWriterBase {
    canBeUsed(l) {
        return l.type === ot_layout_1.Gpos.LookupType.MarkToBase;
    }
    getLookupType() {
        return 4;
    }
    getLookupTypeSymbol() {
        return ot_layout_1.Gpos.LookupType.MarkToBase;
    }
    createSubtableFragments(lookup, ctx) {
        return this.createSubtableFragmentsImpl(MarkBaseWritePlan, lookup.marks, lookup.bases, ctx);
    }
}
exports.GposMarkToBaseWriter = GposMarkToBaseWriter;
class GposMarkToLigatureWriter extends GposMarkWriterBase {
    canBeUsed(l) {
        return l.type === ot_layout_1.Gpos.LookupType.MarkToLigature;
    }
    getLookupType() {
        return 5;
    }
    getLookupTypeSymbol() {
        return ot_layout_1.Gpos.LookupType.MarkToLigature;
    }
    createSubtableFragments(lookup, ctx) {
        return this.createSubtableFragmentsImpl(MarkLigatureWritePlan, lookup.marks, lookup.bases, ctx);
    }
}
exports.GposMarkToLigatureWriter = GposMarkToLigatureWriter;
class GposMarkToMarkWriter extends GposMarkWriterBase {
    baseCoversMarkClass(mc, br) {
        return br && !!br.baseAnchors[mc];
    }
    canBeUsed(l) {
        return l.type === ot_layout_1.Gpos.LookupType.MarkToMark;
    }
    getLookupType() {
        return 6;
    }
    getLookupTypeSymbol() {
        return ot_layout_1.Gpos.LookupType.MarkToMark;
    }
    createSubtableFragments(lookup, ctx) {
        return this.createSubtableFragmentsImpl(MarkBaseWritePlan, lookup.marks, lookup.baseMarks, ctx);
    }
}
exports.GposMarkToMarkWriter = GposMarkToMarkWriter;
//# sourceMappingURL=gpos-mark-write.js.map