"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WriteLookupList = void 0;
const tslib_1 = require("tslib");
const bin_util_1 = require("@ot-builder/bin-util");
const ImpLib = tslib_1.__importStar(require("@ot-builder/common-impl"));
const errors_1 = require("@ot-builder/errors");
const ot_layout_1 = require("@ot-builder/ot-layout");
const primitive_1 = require("@ot-builder/primitive");
const cfg_1 = require("../cfg");
const stat_1 = require("../stat");
const decide_ignore_flags_1 = require("./decide-ignore-flags");
const general_1 = require("./general");
const SizeOfExtSubtable = primitive_1.UInt16.size * 2 + primitive_1.UInt32.size;
class SubtableArranger {
    constructor() {
        this.frags = [];
        this.fragOffsets = [];
        this.relOffset = 0;
        this.size = 0;
        this.buffer = null;
    }
    addSubtableFrag(fr) {
        const n = this.frags.length;
        this.frags[n] = fr;
        return n;
    }
    consolidate() {
        if (!this.buffer) {
            const { buffer, rootOffsets } = bin_util_1.Frag.packMany(this.frags);
            this.buffer = buffer;
            this.size = buffer.byteLength;
            this.fragOffsets = [...rootOffsets];
        }
    }
    getOffset(frIndex) {
        this.consolidate();
        return this.fragOffsets[frIndex];
    }
}
class LookupListWriter {
    constructor() {
        this.lookupHeaders = [];
        this.arrangers = [];
    }
    /** Measure lookup header size without extension -- used when writing headers */
    measureHeaderSize(h) {
        return (primitive_1.UInt16.size * (3 + h.subtableIDs.length)
            + (h.markFilteringSet != null ? primitive_1.UInt16.size : 0));
    }
    /** Measure lookup header size with extension -- used when writing subtable */
    measureHeaderSizeWithExtension(h) {
        return (this.measureHeaderSize(h)
            + (h.useExtension ? SizeOfExtSubtable * h.subtableIDs.length : 0));
    }
    pushLookup(lookup, lookupIsDependency, lwf, gdef, context) {
        const { flags, markFilteringSet } = this.getIgnoreFlags(lookup, gdef);
        for (const writer of lwf.writers()) {
            if (!writer.canBeUsed(lookup))
                continue;
            const header = {
                origLookupType: writer.getLookupTypeSymbol(lookup),
                lookupType: writer.getLookupType(lookup, context),
                flags,
                markFilteringSet,
                rank: this.getLookupRank(writer.getLookupTypeSymbol(lookup), lookupIsDependency, context.trick),
                subtableIDs: [],
                useExtension: false
            };
            this.lookupHeaders.push(header);
            const subtables = writer.createSubtableFragments(lookup, context);
            this.addSubtables(header, subtables, context.trick);
        }
    }
    getLookupRank(origType, isDependency, trick) {
        const rankTrick = 16 * (trick & cfg_1.LookupWriteTrick.AvoidUseExtension ? 1 : 2);
        const rankType = origType === ot_layout_1.Gsub.LookupType.Reverse
            ? 1
            : origType === ot_layout_1.Gsub.LookupType.Chaining || origType === ot_layout_1.Gpos.LookupType.Chaining
                ? 2
                : origType === ot_layout_1.Gsub.LookupType.Single || origType === ot_layout_1.Gpos.LookupType.Single
                    ? 4 + (isDependency ? 0 : 3)
                    : 3 + (isDependency ? 0 : 3);
        return rankTrick + rankType;
    }
    getIgnoreFlags(lookup, gdef) {
        const ignore = (0, decide_ignore_flags_1.decideIgnoreFlags)(lookup.ignoreGlyphs, gdef) || {
            ignoreBaseGlyphs: false,
            ignoreLigatures: false,
            ignoreMarks: false
        };
        let flags = 0;
        let markFilteringSet;
        if (lookup.rightToLeft)
            flags |= general_1.LookupFlag.RightToLeft;
        if (ignore) {
            if (ignore.ignoreBaseGlyphs)
                flags |= general_1.LookupFlag.IgnoreBaseGlyphs;
            if (ignore.ignoreLigatures)
                flags |= general_1.LookupFlag.IgnoreLigatures;
            if (ignore.ignoreMarks)
                flags |= general_1.LookupFlag.IgnoreMarks;
            if (ignore.markAttachmentType != null) {
                flags |= ignore.markAttachmentType << 8;
            }
            if (ignore.markFilteringSet != null) {
                flags |= general_1.LookupFlag.UseMarkFilteringSet;
                markFilteringSet = ignore.markFilteringSet;
            }
        }
        return { flags, markFilteringSet };
    }
    addSubtables(h, sts, trick = 0) {
        const arranger = this.getArrangerOf(h);
        for (const st of sts)
            h.subtableIDs.push(arranger.addSubtableFrag(st));
    }
    getArrangerOf(h) {
        let arr = this.arrangers[h.rank];
        if (!arr) {
            arr = new SubtableArranger();
            this.arrangers[h.rank] = arr;
        }
        return arr;
    }
    allocateOffset() {
        let off = 0;
        for (const arranger of this.arrangers) {
            if (!arranger)
                continue;
            arranger.relOffset = off;
            arranger.consolidate();
            off += arranger.size;
        }
    }
    tryStabilize() {
        const hts = this.getHeaderTotalSize();
        const ho = this.getHeaderOffsets(0);
        let lidConvertible = -1;
        let lidConvertibleRank = 0;
        for (const [h, o, lid] of ImpLib.Iterators.ZipWithIndexReverse(this.lookupHeaders, ho)) {
            const arranger = this.getArrangerOf(h);
            for (const stid of h.subtableIDs) {
                const stOffset = arranger.getOffset(stid);
                if (h.useExtension || arranger.relOffset + stOffset + hts - o <= 0xfffe)
                    continue;
                if (lidConvertible < 0 || h.rank > lidConvertibleRank) {
                    lidConvertible = lid;
                    lidConvertibleRank = h.rank;
                }
            }
        }
        if (lidConvertible >= 0) {
            this.lookupHeaders[lidConvertible].useExtension = true;
            return true;
        }
        else {
            return false;
        }
    }
    stabilize() {
        for (const h of this.lookupHeaders)
            h.useExtension = false;
        this.allocateOffset();
        while (this.tryStabilize())
            ;
    }
    getHeaderPointersSize() {
        return primitive_1.UInt16.size * (1 + this.lookupHeaders.length);
    }
    getHeaderTotalSize() {
        let headerSize = 0;
        for (let lid = 0; lid < this.lookupHeaders.length; lid++) {
            headerSize += this.measureHeaderSizeWithExtension(this.lookupHeaders[lid]);
        }
        return headerSize;
    }
    getHeaderOffsets(hps) {
        let headerOffset = hps;
        const headerOffsets = [];
        for (let lid = 0; lid < this.lookupHeaders.length; lid++) {
            const h = this.lookupHeaders[lid];
            const hs = this.measureHeaderSizeWithExtension(h);
            headerOffsets.push(headerOffset);
            headerOffset += hs;
        }
        return headerOffsets;
    }
    write(frag, lwf) {
        const hps = this.getHeaderPointersSize();
        const hts = this.getHeaderTotalSize();
        const ho = this.getHeaderOffsets(hps);
        // write offset array
        frag.uint16(this.lookupHeaders.length);
        for (const offset of ho)
            frag.uint16(offset);
        errors_1.Assert.SizeMatch("HPS", frag.size, hps);
        // write headers
        for (const [h, o] of ImpLib.Iterators.ZipWithIndex(this.lookupHeaders, ho)) {
            errors_1.Assert.OffsetMatch("header", frag.size, o);
            const hsNonExt = this.measureHeaderSize(h);
            frag.uint16(h.useExtension ? lwf.extendedFormat : h.lookupType);
            frag.uint16(h.flags);
            frag.uint16(h.subtableIDs.length);
            if (h.useExtension) {
                let oExt = hsNonExt;
                for (const stid of h.subtableIDs) {
                    frag.uint16(oExt);
                    oExt += SizeOfExtSubtable;
                }
            }
            else {
                const arranger = this.getArrangerOf(h);
                for (const stid of h.subtableIDs) {
                    const stOffset = arranger.getOffset(stid);
                    frag.uint16(arranger.relOffset + stOffset + hps + hts - o);
                }
            }
            if (h.markFilteringSet != null)
                frag.uint16(h.markFilteringSet);
            if (h.useExtension) {
                let oExt = o + hsNonExt;
                const arranger = this.getArrangerOf(h);
                for (const stid of h.subtableIDs) {
                    const stOffset = arranger.getOffset(stid);
                    frag.uint16(1);
                    frag.uint16(h.lookupType);
                    frag.uint32(arranger.relOffset + stOffset + hps + hts - oExt);
                    oExt += SizeOfExtSubtable;
                }
            }
        }
        errors_1.Assert.SizeMatch("HPS+HTS", frag.size, hps + hts);
        // Write subtable bytes
        for (const arranger of this.arrangers) {
            if (!arranger)
                continue;
            errors_1.Assert.OffsetMatch("Subtable", frag.size, hps + hts + arranger.relOffset);
            if (!arranger.buffer)
                throw errors_1.Errors.Unreachable();
            frag.bytes(arranger.buffer);
        }
    }
}
exports.WriteLookupList = (0, bin_util_1.Write)((frag, lookups, lwf, lwc) => {
    const crossReferences = ImpLib.Order.fromList(`Lookups`, lookups);
    const llw = new LookupListWriter();
    const dependentLookups = new Set();
    for (const lookup of lookups) {
        for (const dep of lwf.queryDependencies(lookup))
            dependentLookups.add(dep);
    }
    for (const lookup of lookups) {
        const trick = lwc.tricks
            ? lwc.tricks.get(lookup) || cfg_1.LookupWriteTrick.None
            : cfg_1.LookupWriteTrick.None;
        llw.pushLookup(lookup, dependentLookups.has(lookup), lwf, lwc.gdef, {
            trick,
            gOrd: lwc.gOrd,
            crossReferences,
            ivs: lwc.ivs,
            stat: lwc.stat || new stat_1.EmptyStat()
        });
    }
    llw.stabilize();
    frag.push(llw, lwf);
});
//# sourceMappingURL=write-lookup-list.js.map