"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GidClassDef = exports.EmptyAsNullPtr16ClassDef = exports.NullablePtr16ClassDef = exports.Ptr16ClassDef = exports.ClassDef = exports.ClassDefUtil = exports.MaxClsDefItemWords = void 0;
const bin_composite_types_1 = require("@ot-builder/bin-composite-types");
const bin_util_1 = require("@ot-builder/bin-util");
const errors_1 = require("@ot-builder/errors");
const cfg_1 = require("../cfg");
exports.MaxClsDefItemWords = 3;
var ClassDefUtil;
(function (ClassDefUtil) {
    function padClass0(cd, gs) {
        for (const g of gs)
            if (!cd.has(g))
                cd.set(g, 0);
    }
    ClassDefUtil.padClass0 = padClass0;
    function limitToCov(cd, cov) {
        for (const g of cov)
            if (!cd.has(g))
                cd.set(g, 0);
        for (const g of cd.keys())
            if (!cov.has(g))
                cd.delete(g);
    }
    ClassDefUtil.limitToCov = limitToCov;
    function SplitClassDef(cd) {
        const ans = [];
        for (const [g, cl] of cd) {
            if (!ans[cl])
                ans[cl] = [];
            ans[cl].push(g);
        }
        return ans;
    }
    ClassDefUtil.SplitClassDef = SplitClassDef;
    function getClassCount(cd) {
        let mc = 1;
        for (const v of cd.values())
            if (v + 1 > mc)
                mc = v + 1;
        return mc;
    }
    ClassDefUtil.getClassCount = getClassCount;
    function select(targetCls, cd, cov) {
        const gs = new Set();
        for (const [g, cls] of cd) {
            if (cls === targetCls && (!cov || cov.has(g))) {
                gs.add(g);
            }
        }
        return gs;
    }
    ClassDefUtil.select = select;
})(ClassDefUtil || (exports.ClassDefUtil = ClassDefUtil = {}));
exports.ClassDef = {
    read(view, gOrd) {
        const gidCD = view.next(exports.GidClassDef);
        const mapping = new Map();
        for (const [gid, cls] of gidCD) {
            mapping.set(gOrd.at(gid), cls);
        }
        return mapping;
    },
    write(frag, cd, gOrd, trick = 0) {
        const gidMap = [];
        for (const [glyph, cls] of cd) {
            if (cls)
                gidMap.push([gOrd.reverse(glyph), cls]);
        }
        gidMap.sort((a, b) => a[0] - b[0]);
        frag.push(exports.GidClassDef, gidMap, trick);
    }
};
exports.Ptr16ClassDef = (0, bin_composite_types_1.NonNullablePtr16)(exports.ClassDef);
exports.NullablePtr16ClassDef = (0, bin_composite_types_1.NullablePtr16)(exports.ClassDef);
exports.EmptyAsNullPtr16ClassDef = {
    read(view, gOrd) {
        const p = view.ptr16Nullable();
        if (!p)
            return new Map();
        else
            return p.next(exports.ClassDef, gOrd);
    },
    write(frag, cd, gOrd, trick = 0) {
        if (!cd.size) {
            frag.ptr16(null);
        }
        else {
            frag.ptr16New().push(exports.ClassDef, cd, gOrd, trick);
        }
    }
};
exports.GidClassDef = {
    ...(0, bin_util_1.Read)(view => {
        const format = view.lift(0).uint16();
        switch (format) {
            case 1:
                return view.next(OtGidClassDefFormat1);
            case 2:
                return view.next(OtGidClassDefFormat2);
            default:
                throw errors_1.Errors.FormatNotSupported("classDef", format);
        }
    }),
    ...(0, bin_util_1.Write)((frag, map, trick = 0) => {
        const mapping = [...map];
        const fragFormat1 = OtGidClassDefFormat1.writeOpt(mapping);
        if (!fragFormat1) {
            frag.push(OtGidClassDefFormat2, mapping);
            return;
        }
        if (trick & cfg_1.LookupWriteTrick.UseFastCoverage) {
            const collector = new ClassRunCollector();
            for (const [gid, cls] of mapping)
                collector.update(gid, cls);
            collector.end();
            if (collector.runs.length < mapping.length) {
                frag.push(OtGidClassDefFormat2FromCollector, collector);
            }
            else {
                frag.embed(fragFormat1);
            }
        }
        else {
            const fragFormat2 = bin_util_1.Frag.from(OtGidClassDefFormat2, mapping);
            if (fragFormat2.size < fragFormat1.size) {
                frag.embed(fragFormat2);
            }
            else {
                frag.embed(fragFormat2);
            }
        }
    })
};
const OtGidClassDefFormat1 = {
    ...(0, bin_util_1.Read)(view => {
        const format = view.uint16();
        if (format !== 1)
            throw errors_1.Errors.Unreachable();
        const cd = new Map();
        const startGlyphID = view.uint16();
        const glyphCount = view.uint16();
        for (let item = 0; item < glyphCount; item++) {
            const classValue = view.uint16();
            cd.set(item + startGlyphID, classValue);
        }
        return cd;
    }),
    ...(0, bin_util_1.WriteOpt)((mapping) => {
        let minGID = null, maxGID = null;
        const clsMap = new Map();
        for (const [gid, cl] of mapping) {
            if (minGID == null || gid < minGID)
                minGID = gid;
            if (maxGID == null || gid > maxGID)
                maxGID = gid;
            clsMap.set(gid, cl);
        }
        if (minGID == null || maxGID == null)
            return null;
        const f = new bin_util_1.Frag();
        f.uint16(1);
        f.uint16(minGID);
        f.uint16(maxGID - minGID + 1);
        for (let gid = minGID; gid <= maxGID; gid++) {
            const cl = clsMap.get(gid);
            if (cl === undefined)
                return null;
            f.uint16(cl);
        }
        return f;
    })
};
class ClassRunCollector {
    constructor() {
        this.runs = [];
        this.last = null;
    }
    start(gid, cls) {
        this.last = { startGlyphID: gid, endGlyphID: gid, class: cls };
    }
    flush() {
        if (this.last)
            this.runs.push(this.last);
    }
    update(gid, cls) {
        if (!this.last)
            this.start(gid, cls);
        else if (gid !== this.last.endGlyphID + 1 || cls !== this.last.class) {
            if (gid < this.last.endGlyphID)
                throw errors_1.Errors.Unreachable();
            this.flush();
            this.start(gid, cls);
        }
        else {
            this.last.endGlyphID = gid;
        }
    }
    end() {
        this.flush();
    }
}
const OtGidClassDefFormat2 = {
    ...(0, bin_util_1.Read)(view => {
        const format = view.uint16();
        if (format !== 2)
            throw errors_1.Errors.Unreachable();
        const cd = new Map();
        const classRangeCount = view.uint16();
        for (let ixRange = 0; ixRange < classRangeCount; ixRange++) {
            const startGlyphID = view.uint16();
            const endGlyphID = view.uint16();
            const cls = view.uint16();
            for (let ixGlyph = startGlyphID; ixGlyph <= endGlyphID; ixGlyph++) {
                cd.set(ixGlyph, cls);
            }
        }
        return cd;
    }),
    ...(0, bin_util_1.Write)((frag, mapping) => {
        const collector = new ClassRunCollector();
        for (const [gid, cls] of mapping) {
            collector.update(gid, cls);
        }
        collector.end();
        frag.push(OtGidClassDefFormat2FromCollector, collector);
    })
};
const OtGidClassDefFormat2FromCollector = (0, bin_util_1.Write)((frag, collector) => {
    frag.uint16(2).uint16(collector.runs.length);
    for (const run of collector.runs) {
        frag.uint16(run.startGlyphID).uint16(run.endGlyphID).uint16(run.class);
    }
});
//# sourceMappingURL=class-def.js.map