/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Tiles
 */
import { assert, compareStrings, SortedArray } from "@itwin/core-bentley";
import { IModelApp } from "../../IModelApp";
import { IpcApp } from "../../IpcApp";
import { IModelTile, TileRequestChannel } from "../../tile/internal";
/** Handles requests to the cloud storage tile cache, if one is configured. If a tile's content is not found in the cache, subsequent requests for the same tile will
 * use the IModelTileChannel instead.
 */
class CloudStorageCacheChannel extends TileRequestChannel {
    async requestContent(tile) {
        assert(tile instanceof IModelTile);
        return IModelApp.tileAdmin.requestCachedTileContent(tile);
    }
    onNoContent(request) {
        assert(request.tile instanceof IModelTile);
        request.tile.requestChannel = IModelApp.tileAdmin.channels.iModelChannels.rpc;
        ++this._statistics.totalCacheMisses;
        return true;
    }
}
/** For an [[IpcApp]], allows backend tile generation requests in progress to be canceled. */
class IModelTileChannel extends TileRequestChannel {
    _canceled = new Map();
    onActiveRequestCanceled(request) {
        const tree = request.tile.tree;
        let entry = this._canceled.get(tree.iModel);
        if (!entry)
            this._canceled.set(tree.iModel, entry = new Map());
        let ids = entry.get(tree.id);
        if (!ids)
            entry.set(tree.id, ids = new Set());
        ids.add(request.tile.contentId);
    }
    processCancellations() {
        for (const [imodel, entries] of this._canceled) {
            const treeContentIds = [];
            for (const [treeId, tileIds] of entries) {
                const contentIds = Array.from(tileIds);
                treeContentIds.push({ treeId, contentIds });
                this._statistics.totalAbortedRequests += contentIds.length;
            }
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
            IpcApp.appFunctionIpc.cancelTileContentRequests(imodel.getRpcProps(), treeContentIds);
        }
        this._canceled.clear();
    }
    onIModelClosed(imodel) {
        this._canceled.delete(imodel);
    }
}
/** If TileAdmin.Props.cacheTileMetadata is true, then this is the first channel through which we request content for an IModelTile.
 * It serves a niche purpose: a tile pre-generation agent that wants to ensure that every tile selected during interaction with the application
 * has its tile generated and cached in cloud storage. This agent might request thousands of tiles in sequence, causing a given tile to be discarded
 * and reloaded many times. To avoid pointlessly reloading tiles whose contents have already been generated, this channel caches the metadata for each tile;
 * on subsequent requests for the same tile, it produces the metadata and an empty RenderGraphic.
 */
class IModelTileMetadataCacheChannel extends TileRequestChannel {
    _cacheByIModel = new Map();
    constructor() {
        super("itwinjs-imodel-metadata-cache", 100);
    }
    onNoContent(request) {
        assert(request.tile instanceof IModelTile);
        const channels = IModelApp.tileAdmin.channels.iModelChannels;
        request.tile.requestChannel = channels.cloudStorage ?? channels.rpc;
        return true;
    }
    async requestContent(tile) {
        assert(tile instanceof IModelTile);
        const content = this.getCachedContent(tile);
        return content ? { content } : undefined;
    }
    getCachedContent(tile) {
        const cached = this._cacheByIModel.get(tile.iModel)?.get(tile.tree)?.findEquivalent((x) => compareStrings(x.contentId, tile.contentId));
        if (!cached)
            return undefined;
        const content = {
            ...cached,
            graphic: cached.hasGraphic ? IModelApp.renderSystem.createGraphicList([]) : undefined,
            contentRange: cached.contentRange?.clone(),
        };
        return content;
    }
    onIModelClosed(imodel) {
        this._cacheByIModel.delete(imodel);
    }
    registerChannel(channel) {
        channel.contentCallback = (tile, content) => this.cache(tile, content);
    }
    cache(tile, content) {
        assert(tile instanceof IModelTile);
        let trees = this._cacheByIModel.get(tile.iModel);
        if (!trees)
            this._cacheByIModel.set(tile.iModel, trees = new Map());
        let list = trees.get(tile.tree);
        if (!list)
            trees.set(tile.tree, list = new SortedArray((lhs, rhs) => compareStrings(lhs.contentId, rhs.contentId)));
        assert(undefined === list.findEquivalent((x) => compareStrings(x.contentId, tile.contentId)));
        list.insert({
            contentId: tile.contentId,
            hasGraphic: undefined !== content.graphic,
            contentRange: content.contentRange?.clone(),
            isLeaf: content.isLeaf,
            sizeMultiplier: content.sizeMultiplier,
            emptySubRangeMask: content.emptySubRangeMask,
        });
    }
}
/** TileRequestChannels used for requesting content for IModelTiles.
 */
export class IModelTileRequestChannels {
    _cloudStorage;
    _contentCache;
    rpc;
    constructor(args) {
        const channelName = "itwinjs-tile-rpc";
        this.rpc = args.usesHttp ? new TileRequestChannel(channelName, args.concurrency) : new IModelTileChannel(channelName, args.concurrency);
        if (args.cacheMetadata) {
            this._contentCache = new IModelTileMetadataCacheChannel();
            this._contentCache.registerChannel(this.rpc);
        }
        this._cloudStorage = new CloudStorageCacheChannel("itwinjs-cloud-cache", args.cacheConcurrency);
        this._contentCache?.registerChannel(this._cloudStorage);
    }
    get cloudStorage() {
        return this._cloudStorage;
    }
    [Symbol.iterator]() {
        const channels = [this.rpc];
        if (this._cloudStorage)
            channels.push(this._cloudStorage);
        if (this._contentCache)
            channels.push(this._contentCache);
        return channels[Symbol.iterator]();
    }
    setRpcConcurrency(concurrency) {
        this.rpc.concurrency = concurrency;
    }
    getChannelForTile(tile) {
        return tile.requestChannel || this._contentCache || this._cloudStorage || this.rpc;
    }
    /** Strictly for tests. */
    getCachedContent(tile) {
        return this._contentCache?.getCachedContent(tile);
    }
}
//# sourceMappingURL=IModelTileRequestChannels.js.map