"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GposChainingReader = exports.GposContextualReader = exports.GsubChainingReader = exports.GsubContextualReader = void 0;
const errors_1 = require("@ot-builder/errors");
const ot_layout_1 = require("@ot-builder/ot-layout");
const class_def_1 = require("../shared/class-def");
const coverage_1 = require("../shared/coverage");
const shared_types_1 = require("./shared-types");
class IndividualResolver {
    constructor(ctx) {
        this.ctx = ctx;
    }
    toGlyphSet(id) {
        return new Set([this.ctx.gOrd.at(id)]);
    }
}
class ClassResolver {
    constructor(cd, startCoverageSet) {
        this.cd = cd;
        this.startCoverageSet = startCoverageSet;
    }
    toGlyphSet(id, start) {
        return class_def_1.ClassDefUtil.select(id, this.cd, start ? this.startCoverageSet : null);
    }
}
// Readers
class CApplication {
    read(view, siblings) {
        const glyphSequenceIndex = view.uint16();
        const lookupListIndex = view.uint16();
        const lookup = siblings.at(lookupListIndex);
        return { at: glyphSequenceIndex, apply: lookup };
    }
}
class CIndividualClassRule {
    constructor() {
        this.rApplication = new CApplication();
    }
    read(view, isChaining, startGlyphs, srBacktrack, srInput, srLookAhead, siblings) {
        let gssBacktrack = [];
        const inputSequence = [startGlyphs];
        let lookAheadSequence = [];
        let applicationCount = 0;
        if (isChaining) {
            const backtrackIDs = view.next(shared_types_1.SimpleClassIdArray).reverse();
            gssBacktrack = backtrackIDs.map(n => srBacktrack.toGlyphSet(n, false));
        }
        const glyphCount = view.uint16();
        if (!isChaining)
            applicationCount = view.uint16();
        for (let n = 1; n < glyphCount; n++) {
            //       ^ Start from 1 here
            inputSequence[n] = srInput.toGlyphSet(view.uint16(), false);
        }
        if (isChaining) {
            const lookAheadIDs = view.next(shared_types_1.SimpleClassIdArray);
            lookAheadSequence = lookAheadIDs.map(n => srLookAhead.toGlyphSet(n, false));
            applicationCount = view.uint16();
        }
        const rule = {
            match: [...gssBacktrack, ...inputSequence, ...lookAheadSequence],
            inputBegins: gssBacktrack.length,
            inputEnds: gssBacktrack.length + inputSequence.length,
            applications: []
        };
        for (let a = 0; a < applicationCount; a++) {
            rule.applications[a] = view.next(this.rApplication, siblings);
        }
        return rule;
    }
}
class IndividualClassRuleSet {
    constructor() {
        this.rIndividualClassRule = new CIndividualClassRule();
    }
    read(view, isChaining, lookup, startGlyphs, srBacktrack, srInput, srLookAhead, siblings) {
        const apRules = view.next(shared_types_1.SimpleOffsetArray);
        for (const vRule of apRules) {
            lookup.rules.push(vRule.next(this.rIndividualClassRule, isChaining, startGlyphs, srBacktrack, srInput, srLookAhead, siblings));
        }
    }
}
class SubtableFormat1 {
    constructor() {
        this.rIndividualClassRuleSet = new IndividualClassRuleSet();
    }
    read(view, isChaining, lookup, ctx) {
        const format = view.uint16();
        errors_1.Assert.FormatSupported(`[Chain]ContextSubstFormat1`, format, 1);
        const cov = view.ptr16().next(coverage_1.GidCoverage);
        const solver = new IndividualResolver(ctx);
        const chainSubRuleCount = view.uint16();
        for (let rid = 0; rid < chainSubRuleCount; rid++) {
            const startGlyph = ctx.gOrd.at(cov[rid]);
            const pRuleSet = view.ptr16Nullable();
            if (!pRuleSet)
                continue; //skip nullptr (though it is not allowed in spec)
            const startGlyphSet = new Set([startGlyph]);
            pRuleSet.next(this.rIndividualClassRuleSet, isChaining, lookup, startGlyphSet, solver, solver, solver, ctx.crossReferences);
        }
    }
}
class SubtableFormat2 {
    constructor() {
        this.rIndividualClassRuleSet = new IndividualClassRuleSet();
    }
    read(view, isChaining, lookup, ctx) {
        const format = view.uint16();
        errors_1.Assert.FormatSupported(`[Chain]ContextSubstFormat2`, format, 2);
        const cov = view.ptr16().next(coverage_1.GidCoverage);
        const cdBacktrack = isChaining ? view.next(class_def_1.EmptyAsNullPtr16ClassDef, ctx.gOrd) : new Map();
        const cdInput = view.ptr16().next(class_def_1.ClassDef, ctx.gOrd);
        const cdLookAhead = isChaining ? view.next(class_def_1.EmptyAsNullPtr16ClassDef, ctx.gOrd) : new Map();
        const covGlyphSet = coverage_1.CovUtils.glyphSetFromGidList(cov, ctx.gOrd);
        class_def_1.ClassDefUtil.padClass0(cdBacktrack, ctx.gOrd);
        class_def_1.ClassDefUtil.padClass0(cdInput, ctx.gOrd);
        class_def_1.ClassDefUtil.padClass0(cdLookAhead, ctx.gOrd);
        const srBacktrack = new ClassResolver(cdBacktrack, covGlyphSet);
        const srInput = new ClassResolver(cdInput, covGlyphSet);
        const srLookAhead = new ClassResolver(cdLookAhead, covGlyphSet);
        for (const [pp, index] of view.repeat(view.uint16())) {
            const pRuleSet = pp.ptr16Nullable();
            if (!pRuleSet)
                continue; // skip nullptr
            const startGlyphSet = srInput.toGlyphSet(index, true);
            pRuleSet.next(this.rIndividualClassRuleSet, isChaining, lookup, startGlyphSet, srBacktrack, srInput, srLookAhead, ctx.crossReferences);
        }
    }
}
class SubtableFormat3 {
    constructor() {
        this.rApplication = new CApplication();
    }
    read(view, isChaining, lookup, ctx) {
        const format = view.uint16();
        errors_1.Assert.FormatSupported(`[Chain]ContextSubstFormat3`, format, 3);
        let gssBacktrack;
        const gssInput = [];
        let gssLookAhead;
        let applicationCount = 0;
        if (isChaining) {
            gssBacktrack = view.next(shared_types_1.SimpleCoverageArray, ctx.gOrd).reverse();
        }
        else {
            gssBacktrack = [];
        }
        const glyphCount = view.uint16();
        if (!isChaining)
            applicationCount = view.uint16();
        for (let n = 0; n < glyphCount; n++) {
            //       ^ Start from 0 here
            gssInput[n] = view.next(coverage_1.Ptr16GlyphCoverage, ctx.gOrd);
        }
        if (isChaining) {
            gssLookAhead = view.next(shared_types_1.SimpleCoverageArray, ctx.gOrd);
            applicationCount = view.uint16();
        }
        else {
            gssLookAhead = [];
        }
        const rule = {
            match: [...gssBacktrack, ...gssInput, ...gssLookAhead],
            inputBegins: gssBacktrack.length,
            inputEnds: gssBacktrack.length + gssInput.length,
            applications: []
        };
        for (let a = 0; a < applicationCount; a++) {
            rule.applications[a] = view.next(this.rApplication, ctx.crossReferences);
        }
        lookup.rules.push(rule);
    }
}
class ChainingContextualReader {
    constructor(chaining) {
        this.chaining = chaining;
    }
    parseSubtable(view, lookup, ctx) {
        const format = view.lift(0).uint16();
        switch (format) {
            case 1:
                view.next(new SubtableFormat1(), this.chaining, lookup, ctx);
                break;
            case 2:
                view.next(new SubtableFormat2(), this.chaining, lookup, ctx);
                break;
            case 3:
                view.next(new SubtableFormat3(), this.chaining, lookup, ctx);
                break;
            default:
                throw errors_1.Errors.FormatNotSupported(`chaining subtable`, format);
        }
    }
}
class GsubContextualReader extends ChainingContextualReader {
    constructor() {
        super(false);
    }
    createLookup() {
        return new ot_layout_1.Gsub.Chaining();
    }
}
exports.GsubContextualReader = GsubContextualReader;
class GsubChainingReader extends ChainingContextualReader {
    constructor() {
        super(true);
    }
    createLookup() {
        return new ot_layout_1.Gsub.Chaining();
    }
}
exports.GsubChainingReader = GsubChainingReader;
class GposContextualReader extends ChainingContextualReader {
    constructor() {
        super(false);
    }
    createLookup() {
        return new ot_layout_1.Gpos.Chaining();
    }
}
exports.GposContextualReader = GposContextualReader;
class GposChainingReader extends ChainingContextualReader {
    constructor() {
        super(true);
    }
    createLookup() {
        return new ot_layout_1.Gpos.Chaining();
    }
}
exports.GposChainingReader = GposChainingReader;
//# sourceMappingURL=contextual-read.js.map