"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module iModels
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.createIModelDbFonts = createIModelDbFonts;
const core_common_1 = require("@itwin/core-common");
const Symbols_1 = require("./Symbols");
const FontFileImpl_1 = require("./FontFileImpl");
const core_bentley_1 = require("@itwin/core-bentley");
const BackendLoggerCategory_1 = require("../BackendLoggerCategory");
class IModelDbFontsImpl {
    [Symbols_1._implementationProhibited] = undefined;
    #db;
    constructor(iModel) {
        this.#db = iModel;
    }
    queryMappedFamilies(args) {
        const fontProps = this.#queryFontTable();
        if (args?.includeNonEmbedded) {
            return fontProps;
        }
        const fontNames = this.#getEmbeddedFontNames();
        return fontProps.filter((x) => fontNames.includes(x.name));
    }
    queryEmbeddedFontFiles() {
        const files = [];
        this.#db.withSqliteStatement(`SELECT Id,StrData FROM be_Prop WHERE Namespace="dgn_Font" AND Name="EmbeddedFaceData"`, (stmt) => {
            while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
                let faces;
                try {
                    faces = JSON.parse(stmt.getValueString(1));
                }
                catch (e) {
                    core_bentley_1.Logger.logException(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, e);
                }
                if (!Array.isArray(faces) || faces.length === 0) {
                    continue;
                }
                let type = faces[0].type;
                if (type !== core_common_1.FontType.Rsc && type !== core_common_1.FontType.Shx) {
                    type = core_common_1.FontType.TrueType;
                }
                files.push(new FontFileImpl_1.EmbeddedFontFile(this.#db, stmt.getValueInteger(0), type, faces));
            }
        });
        return files;
    }
    async embedFontFile(args) {
        this.#requireWritable();
        if (!args.file.isEmbeddable) {
            throw new Error("Font does not permit embedding");
        }
        const file = args.file;
        for (const existing of this.queryEmbeddedFontFiles()) {
            if (existing[Symbols_1._key] === file[Symbols_1._key]) {
                // Already embedded - it's a no-op.
                return;
            }
        }
        let id = 0;
        const codes = this.#db.codeService?.internalCodes;
        if (codes) {
            id = await codes.writeLocker.reserveEmbeddedFaceDataId(args.file[Symbols_1._key]);
        }
        else {
            // CodeService not configured - schema lock required to prevent conflicting Ids in be_Prop table.
            await this.#db.acquireSchemaLock();
            const sql = `SELECT MAX(Id) FROM be_Prop WHERE Namespace="dgn_Font" AND Name="EmbeddedFaceData"`;
            id = this.#db.withSqliteStatement(sql, (stmt) => stmt.nextRow() ? stmt.getValueInteger(0) + 1 : 1);
        }
        (0, core_bentley_1.assert)(id > 0);
        const data = file[Symbols_1._getData]();
        this.#db[Symbols_1._nativeDb].embedFontFile(id, file[Symbols_1._faceProps], data, true);
        if (!args.skipFontIdAllocation) {
            const familyNames = new Set(args.file.faces.map((x) => x.familyName));
            const acquireIds = Array.from(familyNames).map(async (x) => this.#acquireId({ name: x, type: args.file.type }, true).catch());
            await Promise.allSettled(acquireIds);
        }
        this.#db.clearFontMap();
    }
    findId(selector) {
        let id;
        const sqlPostlude = undefined === selector.type ? " ORDER BY Type ASC" : " AND Type=?";
        const sql = `SELECT Id FROM dgn_Font WHERE Name=?${sqlPostlude}`;
        this.#db.withPreparedSqliteStatement(sql, (stmt) => {
            stmt.bindString(1, selector.name);
            if (undefined !== selector.type) {
                stmt.bindInteger(2, selector.type);
            }
            if (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
                id = stmt.getValueInteger(0);
            }
        });
        return id;
    }
    findDescriptor(id) {
        let name, type;
        this.#db.withPreparedSqliteStatement("SELECT Name,Type FROM dgn_Font WHERE Id=?", (stmt) => {
            stmt.bindInteger(1, id);
            if (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
                const stmtName = stmt.getValueString(0);
                if (stmtName.length > 0) {
                    name = stmtName;
                    const typeCode = stmt.getValueInteger(1);
                    type = (typeCode === core_common_1.FontType.Shx || typeCode === core_common_1.FontType.Rsc) ? typeCode : core_common_1.FontType.TrueType;
                }
            }
        });
        return undefined !== name && undefined !== type ? { name, type } : undefined;
    }
    async acquireId(descriptor) {
        this.#requireWritable();
        return this.#acquireId(descriptor, false);
    }
    async #acquireId(descriptor, embeddingFaceData) {
        let id = this.findId(descriptor);
        if (undefined !== id) {
            return id;
        }
        const codes = this.#db.codeService?.internalCodes;
        if (codes) {
            id = await codes.writeLocker.reserveFontId({ fontName: descriptor.name, fontType: descriptor.type });
        }
        else {
            // If we're being called from `embedFontFile` then the schema lock is already held, don't bother re-acquiring it.
            if (!embeddingFaceData) {
                // No CodeService configured. We must obtain the schema lock and use the next available Id.
                await this.#db.acquireSchemaLock();
            }
            id = this.#db.withSqliteStatement(`SELECT MAX(Id) FROM dgn_Font`, (stmt) => stmt.nextRow() ? stmt.getValueInteger(0) + 1 : 1);
        }
        this.#db.withSqliteStatement(`INSERT INTO dgn_Font (Id,Type,Name) VALUES (?,?,?)`, (stmt) => {
            stmt.bindInteger(1, id);
            stmt.bindInteger(2, descriptor.type);
            stmt.bindString(3, descriptor.name);
            if (core_bentley_1.DbResult.BE_SQLITE_DONE !== stmt.step()) {
                throw new Error("Failed to insert font Id mapping");
            }
        });
        this.#db.clearFontMap();
        return id;
    }
    #requireWritable() {
        if (this.#db.isReadonly) {
            throw new Error("iModel is read-only");
        }
    }
    #getEmbeddedFontNames() {
        const names = [];
        const sql = `select DISTINCT json_extract(face.value, '$.familyName') from be_Prop, json_each(be_Prop.StrData) as face where namespace="dgn_Font" and name="EmbeddedFaceData"`;
        this.#db.withPreparedSqliteStatement(sql, (stmt) => {
            while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
                names.push(stmt.getValueString(0));
            }
        });
        return names;
    }
    #queryFontTable() {
        const fonts = [];
        const sql = `SELECT Id,Name,Type FROM dgn_Font`;
        this.#db.withPreparedSqliteStatement(sql, (stmt) => {
            while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
                const name = stmt.getValueString(1);
                const typeCode = stmt.getValueInteger(2);
                const type = (typeCode === core_common_1.FontType.Shx || typeCode === core_common_1.FontType.Rsc) ? typeCode : core_common_1.FontType.TrueType;
                if (name.length > 0) {
                    fonts.push({
                        name,
                        type,
                        id: stmt.getValueInteger(0),
                    });
                }
            }
        });
        return fonts;
    }
}
function createIModelDbFonts(db) {
    return new IModelDbFontsImpl(db);
}
//# sourceMappingURL=IModelDbFontsImpl.js.map