"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 IModelConnection
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tiles = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const IModelApp_1 = require("./IModelApp");
const internal_1 = require("./tile/internal");
class TreeOwner {
    _tileTree;
    _loadStatus = internal_1.TileTreeLoadStatus.NotLoaded;
    _supplier;
    _iModel;
    id;
    get tileTree() { return this._tileTree; }
    get loadStatus() { return this._loadStatus; }
    get iModel() { return this._iModel; }
    constructor(id, supplier, iModel) {
        this.id = id;
        this._supplier = supplier;
        this._iModel = iModel;
    }
    load() {
        this._load(); // eslint-disable-line @typescript-eslint/no-floating-promises
        return this.tileTree;
    }
    async loadTree() {
        await this._load();
        return this.tileTree;
    }
    [Symbol.dispose]() {
        this._tileTree = (0, core_bentley_1.dispose)(this._tileTree);
        this._loadStatus = internal_1.TileTreeLoadStatus.NotLoaded;
    }
    async _load() {
        if (internal_1.TileTreeLoadStatus.NotLoaded !== this.loadStatus)
            return;
        this._loadStatus = internal_1.TileTreeLoadStatus.Loading;
        let tree;
        let newStatus;
        try {
            tree = await this._supplier.createTileTree(this.id, this._iModel);
            newStatus = undefined !== tree && !tree.rootTile.contentRange.isNull ? internal_1.TileTreeLoadStatus.Loaded : internal_1.TileTreeLoadStatus.NotFound;
        }
        catch (err) {
            newStatus = (err.errorNumber && err.errorNumber === core_bentley_1.IModelStatus.ServerTimeout) ? internal_1.TileTreeLoadStatus.NotLoaded : internal_1.TileTreeLoadStatus.NotFound;
        }
        if (internal_1.TileTreeLoadStatus.Loading === this._loadStatus) {
            this._tileTree = tree;
            this._loadStatus = newStatus;
            IModelApp_1.IModelApp.tileAdmin.onTileTreeLoad.raiseEvent(this);
        }
    }
}
/** Provides access to [[TileTree]]s associated with an [[IModelConnection]].
 * The tile trees are accessed indirectly via their corresponding [[TileTreeOwner]]s.
 * Loaded tile trees will be discarded after the iModel is closed, after a period of disuse, or when the contents of a [[GeometricModelState]] they represent
 * change.
 * @see [[IModelConnection.tiles]].
 * @public
 * @extensions
 */
class Tiles {
    _iModel;
    _treesBySupplier = new Map();
    _disposed = false;
    /** @internal */
    get isDisposed() { return this._disposed; }
    /** @internal */
    constructor(iModel) {
        this._iModel = iModel;
        iModel.onEcefLocationChanged.addListener(() => {
            for (const supplier of this._treesBySupplier.keys()) {
                if (supplier.isEcefDependent)
                    this.dropSupplier(supplier);
            }
        });
        // When project extents change, purge tile trees for spatial models.
        iModel.onProjectExtentsChanged.addListener(async () => {
            if (!iModel.isBriefcaseConnection() || !iModel.editingScope)
                await this.purgeModelTrees(this.getSpatialModels());
        });
    }
    /** @internal */
    [Symbol.dispose]() {
        this.reset();
        this._disposed = true;
    }
    /** Intended strictly for tests.
     * @internal
     */
    reset() {
        for (const supplier of this._treesBySupplier)
            supplier[1].forEach((_key, value) => value[Symbol.dispose]());
        this._treesBySupplier.clear();
    }
    /** @internal */
    async purgeTileTrees(modelIds) {
        return IModelApp_1.IModelApp.tileAdmin.purgeTileTrees(this._iModel, modelIds);
    }
    getModelsAnimatedByScheduleScript(scriptSourceElementId) {
        const modelIds = new Set();
        for (const supplier of this._treesBySupplier.keys())
            if (supplier.addModelsAnimatedByScript)
                supplier.addModelsAnimatedByScript(modelIds, scriptSourceElementId, this.getTreeOwnersForSupplier(supplier));
        return modelIds;
    }
    /** Update the [[Tile]]s for any [[TileTree]]s that use the [RenderSchedule.Script]($common) hosted by the specified
     * [RenderTimeline]($backend) or [DisplayStyle]($backend) element. This method should be invoked after
     * the host element is updated in the database with a new script, so that any [[Viewport]]s displaying tiles produced
     * based on the previous version of the script are updated to use the new version of the script.
     * @param scriptSourceElementId The Id of the RenderTimeline or DisplayStyle element that hosts the script.
     * @public
     */
    async updateForScheduleScript(scriptSourceElementId) {
        return this.purgeModelTrees(this.getModelsAnimatedByScheduleScript(scriptSourceElementId));
    }
    async purgeModelTrees(modelIds) {
        if (0 === modelIds.size)
            return;
        const ids = Array.from(modelIds);
        await this.purgeTileTrees(ids);
        IModelApp_1.IModelApp.viewManager.refreshForModifiedModels(ids);
    }
    getSpatialModels() {
        const modelIds = new Set();
        for (const supplier of this._treesBySupplier.keys())
            if (supplier.addSpatialModels)
                supplier.addSpatialModels(modelIds, this.getTreeOwnersForSupplier(supplier));
        return modelIds;
    }
    /** Obtain the owner of a TileTree.
     * The `id` is unique within all tile trees associated with `supplier`; its specific structure is an implementation detail known only to the supplier.
     * A [[TileTreeReference]] uses this method to obtain the tile tree to which it refers.
     */
    getTileTreeOwner(id, supplier) {
        let trees = this._treesBySupplier.get(supplier);
        if (undefined === trees) {
            trees = new core_bentley_1.Dictionary((lhs, rhs) => supplier.compareTileTreeIds(lhs, rhs));
            this._treesBySupplier.set(supplier, trees);
        }
        let tree = trees.get(id);
        if (undefined === tree) {
            tree = new TreeOwner(id, supplier, this._iModel);
            trees.set(id, tree);
        }
        return tree;
    }
    /** Remove tile tree matching the provided tile tree supplier/id
     * @internal
     */
    resetTileTreeOwner(id, supplier) {
        const trees = this._treesBySupplier.get(supplier);
        const tree = trees?.get(id);
        if (tree) {
            tree[Symbol.dispose]();
            trees?.delete(id);
        }
    }
    /** Disposes of all [[TileTree]]s belonging to `supplier` and removes `supplier` from the set of known tile tree suppliers. */
    dropSupplier(supplier) {
        const trees = this._treesBySupplier.get(supplier);
        if (undefined === trees)
            return;
        trees.forEach((_key, value) => value[Symbol.dispose]());
        this._treesBySupplier.delete(supplier);
    }
    /** Invokes a function on each extant TileTreeOwner. */
    forEachTreeOwner(func) {
        for (const dict of this._treesBySupplier.values())
            dict.forEach((_key, value) => func(value));
    }
    /** Iterate over all of the TileTreeOwners. */
    *[Symbol.iterator]() {
        for (const [supplier, dict] of this._treesBySupplier) {
            for (const entry of dict)
                yield { supplier, id: entry.key, owner: entry.value };
        }
    }
    /** Obtain the TileTreeOwners supplied by the specified supplier. */
    getTreeOwnersForSupplier(supplier) {
        function* iterator(trees) {
            if (trees)
                for (const entry of trees)
                    yield { id: entry.key, owner: entry.value };
        }
        return {
            [Symbol.iterator]: () => iterator(this._treesBySupplier.get(supplier)),
        };
    }
    /** Unload any tile trees which have not been drawn since at least the specified time, excluding any of the specified TileTrees.
     * @internal
     */
    purge(olderThan, exclude) {
        // NB: It would be nice to be able to detect completely useless leftover Owners or Suppliers, but we can't know if any TileTreeReferences exist pointing to a given Owner.
        for (const entry of this._treesBySupplier) {
            const dict = entry[1];
            dict.forEach((_treeId, owner) => {
                const tree = owner.tileTree;
                if (undefined !== tree && tree.lastSelectedTime.milliseconds < olderThan.milliseconds)
                    if (undefined === exclude || !exclude.has(tree))
                        owner[Symbol.dispose]();
            });
        }
    }
}
exports.Tiles = Tiles;
//# sourceMappingURL=Tiles.js.map