"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GposChainingContextualWriter = exports.GsubChainingContextualWriter = 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 cfg_1 = require("../cfg");
const class_def_1 = require("../shared/class-def");
const coverage_1 = require("../shared/coverage");
class ClassDefsAnalyzeState {
    constructor() {
        this.firstGlyphSet = new Set();
        this.cdBacktrack = new Map();
        this.cdInput = new Map();
        this.cdLookAhead = new Map();
        this.rules = [];
        this.classRules = new Map();
        this.lastFirstClass = 0;
        this.maxFirstClass = 0;
        this.ruleComplexity = 0; // Quantity of UInt16s of ChainSubClassRule's
    }
    glyphSetCompatibleWithExistingClassDef(gs, cd) {
        if (!(gs === null || gs === void 0 ? void 0 : gs.size))
            return undefined;
        let firstClass;
        for (const g of gs) {
            const gk = cd.get(g);
            if (gk == null)
                return undefined;
            if (firstClass == null)
                firstClass = gk;
            else if (gk !== firstClass)
                return undefined;
        }
        if (firstClass == null)
            return undefined;
        for (const [g, cls] of cd)
            if (cls === firstClass && !gs.has(g))
                return undefined;
        return firstClass;
    }
    glyphSetCompatibleWithNewClassDef(gs, cd) {
        if (!(gs === null || gs === void 0 ? void 0 : gs.size))
            return undefined;
        for (const g of gs) {
            const gk = cd.get(g);
            if (gk != null)
                return undefined;
        }
        let introClass = 1;
        for (const [g, cls] of cd)
            if (cls + 1 > introClass)
                introClass = cls + 1;
        for (const g of gs)
            cd.set(g, introClass);
        return introClass;
    }
    glyphSetCompatibleWithClassDef(gs, cd) {
        const kExisting = this.glyphSetCompatibleWithExistingClassDef(gs, cd);
        if (kExisting != null)
            return kExisting;
        else
            return this.glyphSetCompatibleWithNewClassDef(gs, cd);
    }
    checkRuleCompatibility(rule) {
        const cdBacktrack = new Map(this.cdBacktrack);
        const cdInput = new Map(this.cdInput);
        const cdLookAhead = new Map(this.cdLookAhead);
        const cr = {
            match: [],
            inputBegins: rule.inputBegins,
            inputEnds: rule.inputEnds,
            applications: rule.applications
        };
        const firstGlyphSet = rule.match[rule.inputBegins];
        const firstGlyphClass = this.glyphSetCompatibleWithClassDef(firstGlyphSet, cdInput);
        if (!firstGlyphClass || firstGlyphClass < this.lastFirstClass)
            return null;
        cr.match[cr.inputBegins] = firstGlyphClass;
        for (let matchId = 0; matchId < rule.match.length; matchId++) {
            if (matchId === rule.inputBegins)
                continue;
            const cd = matchId < rule.inputBegins
                ? cdBacktrack
                : matchId < rule.inputEnds
                    ? cdInput
                    : cdLookAhead;
            const kMatch = this.glyphSetCompatibleWithClassDef(rule.match[matchId], cd);
            if (!kMatch)
                return null;
            cr.match[matchId] = kMatch;
        }
        return {
            cdBacktrack,
            cdInput,
            cdLookAhead,
            cr,
            firstGlyphClass,
            firstGlyphSet
        };
    }
    estimateCurrentSize() {
        return (primitive_1.UInt16.size
            * (8
                + class_def_1.MaxClsDefItemWords
                    * (this.cdBacktrack.size + this.cdInput.size + this.cdLookAhead.size)
                + this.ruleComplexity));
    }
    compatibleRuleComplexity(comp) {
        return 8 + comp.cr.match.length + 2 * comp.cr.applications.length;
    }
    estimateUpdatedSubtableSize(comp) {
        return this.estimateCurrentSize() + primitive_1.UInt16.size * this.compatibleRuleComplexity(comp);
    }
    addCompatibleRule(rule, comp) {
        this.cdBacktrack = comp.cdBacktrack;
        this.cdInput = comp.cdInput;
        this.cdLookAhead = comp.cdLookAhead;
        this.rules.push(rule);
        const a = this.classRules.get(comp.firstGlyphClass);
        if (a)
            a.push(comp.cr);
        else
            this.classRules.set(comp.firstGlyphClass, [comp.cr]);
        this.lastFirstClass = comp.firstGlyphClass;
        if (comp.firstGlyphClass > this.maxFirstClass)
            this.maxFirstClass = comp.firstGlyphClass;
        if (comp.firstGlyphSet)
            for (const g of comp.firstGlyphSet)
                this.firstGlyphSet.add(g);
        this.ruleComplexity += this.compatibleRuleComplexity(comp);
    }
    tryAddRule(rule) {
        const comp = this.checkRuleCompatibility(rule);
        if (!comp)
            return false;
        if (0x8000 < this.estimateUpdatedSubtableSize(comp))
            return false;
        this.addCompatibleRule(rule, comp);
        return true;
    }
}
class CApplication {
    write(frag, apps, crossRef) {
        for (const app of apps) {
            frag.uint16(app.at).uint16(crossRef.reverse(app.apply));
        }
    }
}
class CCoverageRule {
    constructor() {
        this.rApplication = new CApplication();
    }
    write(frag, isChaining, rule, ctx) {
        frag.uint16(3);
        const backtrackSets = rule.match.slice(0, rule.inputBegins).reverse();
        const inputSets = rule.match.slice(rule.inputBegins, rule.inputEnds);
        const lookAheadSets = rule.match.slice(rule.inputEnds);
        if (isChaining) {
            frag.uint16(backtrackSets.length);
            for (const set of backtrackSets)
                frag.push(coverage_1.Ptr16GlyphCoverage, set, ctx.gOrd, ctx.trick);
        }
        frag.uint16(inputSets.length);
        if (!isChaining)
            frag.uint16(rule.applications.length);
        for (const set of inputSets)
            frag.push(coverage_1.Ptr16GlyphCoverage, set, ctx.gOrd, ctx.trick);
        if (isChaining) {
            frag.uint16(lookAheadSets.length);
            for (const set of lookAheadSets)
                frag.push(coverage_1.Ptr16GlyphCoverage, set, ctx.gOrd, ctx.trick);
            frag.uint16(rule.applications.length);
        }
        frag.push(this.rApplication, rule.applications, ctx.crossReferences);
    }
}
class CClassRule {
    constructor() {
        this.rApplication = new CApplication();
    }
    write(frag, isChaining, cr, ctx) {
        const backtrack = cr.match.slice(0, cr.inputBegins).reverse();
        const input = cr.match.slice(cr.inputBegins, cr.inputEnds);
        const lookAhead = cr.match.slice(cr.inputEnds);
        if (isChaining) {
            frag.uint16(backtrack.length);
            for (const c of backtrack)
                frag.uint16(c);
        }
        frag.uint16(input.length);
        if (!isChaining)
            frag.uint16(cr.applications.length);
        for (let iInput = 1; iInput < input.length; iInput++)
            frag.uint16(input[iInput]);
        if (isChaining) {
            frag.uint16(lookAhead.length);
            for (const c of lookAhead)
                frag.uint16(c);
            frag.uint16(cr.applications.length);
        }
        frag.push(this.rApplication, cr.applications, ctx.crossReferences);
    }
}
class CClassRuleSet {
    constructor() {
        this.wClassRule = new CClassRule();
    }
    write(frag, isChaining, s, ctx) {
        frag.uint16(2);
        frag.push(coverage_1.Ptr16GlyphCoverage, s.firstGlyphSet, ctx.gOrd, ctx.trick);
        if (isChaining)
            frag.push(class_def_1.Ptr16ClassDef, s.cdBacktrack, ctx.gOrd, ctx.trick);
        frag.push(class_def_1.Ptr16ClassDef, s.cdInput, ctx.gOrd, ctx.trick);
        if (isChaining)
            frag.push(class_def_1.Ptr16ClassDef, s.cdLookAhead, ctx.gOrd, ctx.trick);
        frag.uint16(s.maxFirstClass + 1);
        for (let c = 0; c <= s.maxFirstClass; c++) {
            const a = s.classRules.get(c);
            if (!(a === null || a === void 0 ? void 0 : a.length)) {
                frag.ptr16(null);
            }
            else {
                const bRuleSet = frag.ptr16New();
                bRuleSet.uint16(a.length);
                for (const cr of a) {
                    bRuleSet.ptr16New().push(this.wClassRule, isChaining, cr, ctx);
                }
            }
        }
    }
}
class ChainingContextualWriter {
    constructor() {
        this.wCoverageRule = new CCoverageRule();
        this.wClassRuleSet = new CClassRuleSet();
    }
    useChainingLookup(lookup, ctx) {
        if (ctx.trick & cfg_1.LookupWriteTrick.AvoidUsingContextualLookup)
            return true;
        let chain = false;
        for (const rule of lookup.rules) {
            if (rule.inputBegins > 0 || rule.inputEnds < rule.match.length) {
                chain = true;
            }
        }
        return chain;
    }
    estimateCovRuleSize(rule) {
        let s = primitive_1.UInt16.size * (8 + rule.match.length + 2 * rule.applications.length);
        for (let idGs = 0; idGs < rule.match.length; idGs++) {
            const gs = rule.match[idGs];
            s += primitive_1.UInt16.size * (2 + gs.size);
        }
        return s;
    }
    covSubtable(rule, isChaining, ctx) {
        return bin_util_1.Frag.from(this.wCoverageRule, isChaining, rule, ctx);
    }
    clsSubtable(s, isChaining, ctx) {
        return bin_util_1.Frag.from(this.wClassRuleSet, isChaining, s, ctx);
    }
    createSubtableFragments(lookup, ctx) {
        const isChaining = this.useChainingLookup(lookup, ctx);
        const covLookups = [];
        const covLookupSizes = [];
        for (const rule of lookup.rules) {
            covLookups.push(this.covSubtable(rule, isChaining, ctx));
            covLookupSizes.push(this.estimateCovRuleSize(rule));
        }
        if (ctx.trick & cfg_1.LookupWriteTrick.ContextualForceFormat3)
            return covLookups;
        // Do dynamic programming to find out an optimal arrangement
        const bestResults = [];
        bestResults[lookup.rules.length] = [0, []];
        for (let iRule = lookup.rules.length; iRule-- > 0;) {
            const bestResult = [
                covLookupSizes[iRule] + bestResults[iRule + 1][0],
                [covLookups[iRule], ...bestResults[iRule + 1][1]]
            ];
            const state = new ClassDefsAnalyzeState();
            for (let jRule = iRule; jRule < lookup.rules.length; jRule += 1) {
                if (!state.tryAddRule(lookup.rules[jRule]))
                    break;
                const sizeUsingClassDef = state.estimateCurrentSize() + bestResults[jRule + 1][0];
                if (sizeUsingClassDef < bestResult[0]) {
                    bestResult[0] = sizeUsingClassDef;
                    bestResult[1] = [
                        this.clsSubtable(state, isChaining, ctx),
                        ...bestResults[jRule + 1][1]
                    ];
                }
            }
            bestResults[iRule] = bestResult;
        }
        return bestResults[0][1];
    }
}
class GsubChainingContextualWriter extends ChainingContextualWriter {
    getLookupType(lookup, ctx) {
        return this.useChainingLookup(lookup, ctx) ? 6 : 5;
    }
    getLookupTypeSymbol() {
        return ot_layout_1.Gsub.LookupType.Chaining;
    }
    canBeUsed(l) {
        return l.type === ot_layout_1.Gsub.LookupType.Chaining;
    }
}
exports.GsubChainingContextualWriter = GsubChainingContextualWriter;
class GposChainingContextualWriter extends ChainingContextualWriter {
    getLookupType(lookup, ctx) {
        return this.useChainingLookup(lookup, ctx) ? 8 : 7;
    }
    getLookupTypeSymbol() {
        return ot_layout_1.Gpos.LookupType.Chaining;
    }
    canBeUsed(l) {
        return l.type === ot_layout_1.Gpos.LookupType.Chaining;
    }
}
exports.GposChainingContextualWriter = GposChainingContextualWriter;
//# sourceMappingURL=contextual-write.js.map