/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module WebGL
 */
import { assert, dispose } from "@itwin/core-bentley";
import { ThematicDisplayMode } from "@itwin/core-common";
import { Range3d, Transform } from "@itwin/core-geometry";
import { FeatureSymbology } from "../../../render/FeatureSymbology";
import { GraphicBranch } from "../../../render/GraphicBranch";
import { RenderGraphic } from "../../../render/RenderGraphic";
import { EdgeSettings } from "./EdgeSettings";
import { FeatureOverrides } from "./FeatureOverrides";
import { PlanarClassifier } from "./PlanarClassifier";
import { TextureDrape } from "./TextureDrape";
import { ThematicSensors } from "./ThematicSensors";
import { Contours } from "./Contours";
/** @internal */
export class Graphic extends RenderGraphic {
    addHiliteCommands(_commands, _pass) { assert(false); }
    toPrimitive() { return undefined; }
}
export class GraphicOwner extends Graphic {
    _graphic;
    constructor(graphic) {
        super();
        this._graphic = graphic;
    }
    get graphic() { return this._graphic; }
    _isDisposed = false;
    get isDisposed() { return this._isDisposed; }
    dispose() { this._isDisposed = true; }
    disposeGraphic() {
        this.graphic[Symbol.dispose]();
    }
    collectStatistics(stats) {
        this.graphic.collectStatistics(stats);
    }
    unionRange(range) {
        this.graphic.unionRange(range);
    }
    addCommands(commands) {
        this._graphic.addCommands(commands);
    }
    get isPickable() {
        return this._graphic.isPickable;
    }
    addHiliteCommands(commands, pass) {
        this._graphic.addHiliteCommands(commands, pass);
    }
    toPrimitive() {
        return this._graphic.toPrimitive();
    }
}
/** @internal exported strictly for tests. */
export class PerTargetBatchData {
    target;
    _featureOverrides = new Map();
    _contours;
    _thematicSensors;
    constructor(target) {
        this.target = target;
    }
    [Symbol.dispose]() {
        this._thematicSensors = dispose(this._thematicSensors);
        this._contours = this._contours?.[Symbol.dispose]();
        for (const value of this._featureOverrides.values())
            dispose(value);
        this._featureOverrides.clear();
    }
    getThematicSensors(batch) {
        if (this._thematicSensors && !this._thematicSensors.matchesTarget(this.target))
            this._thematicSensors = dispose(this._thematicSensors);
        if (!this._thematicSensors)
            this._thematicSensors = ThematicSensors.create(this.target, batch.range);
        this._thematicSensors.update(this.target.uniforms.frustum.viewMatrix);
        return this._thematicSensors;
    }
    getFeatureOverrides(batch, provider) {
        const source = this.target.currentFeatureSymbologyOverrides?.source;
        let ovrs = this._featureOverrides.get(source);
        if (!ovrs) {
            const cleanup = source ? source.onSourceDisposed.addOnce(() => this.onSourceDisposed(source)) : undefined;
            this._featureOverrides.set(source, ovrs = FeatureOverrides.createFromTarget(this.target, batch.options, cleanup));
            ovrs.initFromMap(batch.featureTable, provider);
        }
        ovrs.update(batch.featureTable, provider);
        return ovrs;
    }
    getContours(batch) {
        if (this._contours && !this._contours.matchesTargetAndFeatureCount(this.target, batch.featureTable))
            this._contours = this._contours[Symbol.dispose]();
        if (!this._contours) {
            this._contours = Contours.createFromTarget(this.target, batch.options);
            this._contours.initFromMap(batch.featureTable);
        }
        else {
            this._contours.update(batch.featureTable);
        }
        return this._contours;
    }
    collectStatistics(stats) {
        if (this._thematicSensors)
            stats.addThematicTexture(this._thematicSensors.bytesUsed);
        for (const ovrs of this._featureOverrides.values())
            stats.addFeatureOverrides(ovrs.byteLength);
        if (this._contours)
            stats.addContours(this._contours.byteLength);
    }
    /** Exposed strictly for tests. */
    get featureOverrides() { return this._featureOverrides; }
    onSourceDisposed(source) {
        const ovrs = this._featureOverrides.get(source);
        if (ovrs) {
            this._featureOverrides.delete(source);
            ovrs[Symbol.dispose]();
        }
    }
}
/** @internal exported strictly for tests. */
export class PerTargetData {
    _batch;
    _data = [];
    constructor(batch) {
        this._batch = batch;
    }
    [Symbol.dispose]() {
        for (const data of this._data) {
            data.target.onBatchDisposed(this._batch);
            data[Symbol.dispose]();
        }
        this._data.length = 0;
    }
    get isDisposed() {
        return this._data.length === 0;
    }
    /** Exposed strictly for tests. */
    get data() { return this._data; }
    onTargetDisposed(target) {
        const index = this._data.findIndex((x) => x.target === target);
        if (-1 === index)
            return;
        const data = this._data[index];
        data[Symbol.dispose]();
        this._data.splice(index, 1);
    }
    collectStatistics(stats) {
        for (const data of this._data)
            data.collectStatistics(stats);
    }
    getThematicSensors(target) {
        return this.getBatchData(target).getThematicSensors(this._batch);
    }
    getFeatureOverrides(target, provider) {
        return this.getBatchData(target).getFeatureOverrides(this._batch, provider);
    }
    getContours(target) {
        return this.getBatchData(target).getContours(this._batch);
    }
    getBatchData(target) {
        let data = this._data.find((x) => x.target === target);
        if (!data) {
            this._data.push(data = new PerTargetBatchData(target));
            target.addBatch(this._batch);
        }
        return data;
    }
}
/** @internal */
export class Batch extends Graphic {
    graphic;
    featureTable;
    range;
    _context = { batchId: 0 };
    /** Public strictly for tests. */
    perTargetData = new PerTargetData(this);
    options;
    // Chiefly for debugging.
    get tileId() {
        return this.options.tileId;
    }
    get locateOnly() {
        return true === this.options.locateOnly;
    }
    /** The following are valid only during a draw and reset afterward. */
    get batchId() { return this._context.batchId; }
    get batchIModel() { return this._context.iModel; }
    get transformFromBatchIModel() { return this._context.transformFromIModel; }
    get viewAttachmentId() { return this._context.viewAttachmentId; }
    get inSectionDrawingAttachment() { return this._context.inSectionDrawingAttachment; }
    setContext(batchId, branch) {
        this._context.batchId = batchId;
        this._context.iModel = branch.iModel;
        this._context.transformFromIModel = branch.transformFromIModel;
        this._context.viewAttachmentId = branch.viewAttachmentId;
        this._context.inSectionDrawingAttachment = branch.inSectionDrawingAttachment;
    }
    resetContext() {
        this._context.batchId = 0;
        this._context.iModel = undefined;
        this._context.transformFromIModel = undefined;
        this._context.viewAttachmentId = undefined;
        this._context.inSectionDrawingAttachment = undefined;
    }
    constructor(graphic, features, range, options) {
        super();
        this.graphic = graphic;
        this.featureTable = features;
        this.range = range;
        this.options = options ?? {};
    }
    _isDisposed = false;
    get isDisposed() {
        return this._isDisposed && this.perTargetData.isDisposed;
    }
    // Note: This does not remove FeatureOverrides from the array, but rather disposes of the WebGL resources they contain
    dispose() {
        dispose(this.graphic);
        this.perTargetData[Symbol.dispose]();
        this._isDisposed = true;
    }
    collectStatistics(stats) {
        this.graphic.collectStatistics(stats);
        stats.addFeatureTable(this.featureTable.byteLength);
        this.perTargetData.collectStatistics(stats);
    }
    unionRange(range) {
        range.extendRange(this.range);
    }
    addCommands(commands) {
        commands.addBatch(this);
    }
    get isPickable() {
        return true;
    }
    getThematicSensors(target) {
        assert(target.plan.thematic !== undefined, "thematic display settings must exist");
        assert(target.plan.thematic.displayMode === ThematicDisplayMode.InverseDistanceWeightedSensors, "thematic display mode must be sensor-based");
        assert(target.plan.thematic.sensorSettings.sensors.length > 0, "must have at least one sensor to process");
        return this.perTargetData.getThematicSensors(target);
    }
    getOverrides(target, provider) {
        return this.perTargetData.getFeatureOverrides(target, provider);
    }
    getContours(target) {
        return this.perTargetData.getContours(target);
    }
    onTargetDisposed(target) {
        this.perTargetData.onTargetDisposed(target);
    }
}
/** @internal */
export class Branch extends Graphic {
    branch;
    localToWorldTransform;
    clips;
    planarClassifier;
    textureDrape;
    layerClassifiers;
    edgeSettings;
    iModel; // used chiefly for readPixels to identify context of picked Ids.
    frustum;
    appearanceProvider;
    secondaryClassifiers;
    viewAttachmentId;
    inSectionDrawingAttachment;
    disableClipStyle;
    transformFromExternalIModel;
    contourLine;
    constructor(branch, localToWorld, viewFlags, opts) {
        super();
        this.branch = branch;
        this.localToWorldTransform = localToWorld;
        if (undefined !== viewFlags)
            branch.setViewFlags(viewFlags);
        if (!opts)
            return;
        this.appearanceProvider = opts.appearanceProvider;
        this.clips = opts.clipVolume;
        this.iModel = opts.iModel;
        this.frustum = opts.frustum;
        this.viewAttachmentId = opts.viewAttachmentId;
        this.inSectionDrawingAttachment = opts.inSectionDrawingAttachment;
        this.disableClipStyle = opts.disableClipStyle;
        this.transformFromExternalIModel = opts.transformFromIModel;
        this.contourLine = opts.contours;
        if (opts.hline)
            this.edgeSettings = EdgeSettings.create(opts.hline);
        if (opts.classifierOrDrape instanceof PlanarClassifier)
            this.planarClassifier = opts.classifierOrDrape;
        else if (opts.classifierOrDrape instanceof TextureDrape)
            this.textureDrape = opts.classifierOrDrape;
        if (opts.secondaryClassifiers) {
            this.secondaryClassifiers = new Array();
            opts.secondaryClassifiers.forEach((classifier) => {
                if (classifier instanceof PlanarClassifier)
                    this.secondaryClassifiers?.push(classifier);
            });
        }
    }
    get isDisposed() {
        return 0 === this.branch.entries.length;
    }
    dispose() {
        this.branch[Symbol.dispose]();
    }
    get isPickable() {
        return this.branch.entries.some((gf) => gf.isPickable);
    }
    collectStatistics(stats) {
        this.branch.collectStatistics(stats);
    }
    unionRange(range) {
        const thisRange = new Range3d();
        for (const graphic of this.branch.entries)
            graphic.unionRange(thisRange);
        this.localToWorldTransform.multiplyRange(thisRange, thisRange);
        range.extendRange(thisRange);
    }
    shouldAddCommands(commands) {
        const group = commands.target.currentBranch.groupNodeId;
        if (undefined !== group && undefined !== this.branch.groupNodeId && this.branch.groupNodeId !== group)
            return false;
        const nodeId = commands.target.getAnimationTransformNodeId(this.branch.animationNodeId);
        return undefined === nodeId || nodeId === commands.target.currentAnimationTransformNodeId;
    }
    addCommands(commands) {
        if (this.shouldAddCommands(commands))
            commands.addBranch(this);
    }
    addHiliteCommands(commands, pass) {
        if (this.shouldAddCommands(commands))
            commands.addHiliteBranch(this, pass);
    }
}
/** @internal */
export class AnimationTransformBranch extends Graphic {
    nodeId;
    graphic;
    constructor(graphic, nodeId) {
        super();
        assert(graphic instanceof Graphic);
        this.graphic = graphic;
        this.nodeId = nodeId;
    }
    dispose() {
        this.graphic[Symbol.dispose]();
    }
    get isDisposed() {
        return this.graphic.isDisposed;
    }
    get isPickable() {
        return this.graphic.isPickable;
    }
    collectStatistics(stats) {
        this.graphic.collectStatistics(stats);
    }
    unionRange(range) {
        this.graphic.unionRange(range);
    }
    addCommands(commands) {
        commands.target.currentAnimationTransformNodeId = this.nodeId;
        this.graphic.addCommands(commands);
        commands.target.currentAnimationTransformNodeId = undefined;
    }
    addHiliteCommands(commands, pass) {
        commands.target.currentAnimationTransformNodeId = this.nodeId;
        this.graphic.addHiliteCommands(commands, pass);
        commands.target.currentAnimationTransformNodeId = undefined;
    }
}
/** @internal */
export class WorldDecorations extends Branch {
    constructor(viewFlags) {
        super(new GraphicBranch(), Transform.identity, viewFlags);
        // World decorations ignore all the symbology overrides for the "scene" geometry...
        this.branch.symbologyOverrides = new FeatureSymbology.Overrides();
        // Make all subcategories visible.
        this.branch.symbologyOverrides.ignoreSubCategory = true;
    }
    init(decs) {
        this.branch.clear();
        for (const dec of decs) {
            this.branch.add(dec);
        }
    }
}
/** @internal */
export class GraphicsArray extends Graphic {
    graphics;
    // Note: We assume the graphics array we get contains undisposed graphics to start
    constructor(graphics) {
        super();
        this.graphics = graphics;
    }
    get isDisposed() { return 0 === this.graphics.length; }
    get isPickable() {
        return this.graphics.some((x) => x.isPickable);
    }
    dispose() {
        for (const graphic of this.graphics)
            dispose(graphic);
        this.graphics.length = 0;
    }
    addCommands(commands) {
        for (const graphic of this.graphics) {
            graphic.addCommands(commands);
        }
    }
    addHiliteCommands(commands, pass) {
        for (const graphic of this.graphics) {
            graphic.addHiliteCommands(commands, pass);
        }
    }
    collectStatistics(stats) {
        for (const graphic of this.graphics)
            graphic.collectStatistics(stats);
    }
    unionRange(range) {
        for (const graphic of this.graphics)
            graphic.unionRange(range);
    }
}
//# sourceMappingURL=Graphic.js.map