"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 WebGL
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.LayerCommandLists = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const DrawCommand_1 = require("./DrawCommand");
class State {
    constructor(map) {
        this.map = map;
    }
    executeTransition(newState, func) {
        this.map.state = newState;
        func();
        newState.exit();
        this.map.state = this;
    }
    exit() { }
    throwStateError(operation) {
        // Using assert because these are intended for developers.
        (0, core_bentley_1.assert)(false, `Invalid layer command: operation '${operation}' unimplemented for state '${this.opcode}'`);
    }
    processLayers(_container, _func) {
        this.throwStateError("processLayers");
    }
    pushAndPop(push, _pop, func) {
        if ("pushBatch" === push.opcode)
            this.processBatch(push, func);
        else if ("pushBranch" === push.opcode)
            this.processBranch(push, func);
        else
            this.throwStateError("unhandled push command");
    }
    processBranch(_push, _func) {
        this.throwStateError("processBranch");
    }
    processBatch(_push, _func) {
        this.throwStateError("processBatch");
    }
    set currentLayer(_layer) {
        this.throwStateError("setCurrentLayer");
    }
    addCommands(_commands) {
        this.throwStateError("addCommands");
    }
}
class IdleState extends State {
    get opcode() { return "Idle"; }
    constructor(map) {
        super(map);
    }
    processLayers(container, func) {
        this.executeTransition(new ContainerState(this, container), func);
    }
}
class ContainerState extends State {
    get opcode() { return "Container"; }
    constructor(idle, container) {
        super(idle.map);
        this.elevation = container.elevation;
    }
    processBranch(push, func) {
        this.executeTransition(new BranchState(this, push), func);
    }
}
class BranchState extends State {
    get opcode() { return "Branch"; }
    constructor(containerState, pushCommand) {
        super(containerState.map);
        this._layerCommands = new Set();
        this.containerState = containerState;
        this.pushCommand = pushCommand;
    }
    processBatch(push, func) {
        this.executeTransition(new BatchState(this, push), func);
    }
    markLayer(cmds) {
        if (!this._layerCommands.has(cmds)) {
            cmds.commands.push(this.pushCommand);
            this._layerCommands.add(cmds);
        }
    }
    exit() {
        for (const cmds of this._layerCommands)
            cmds.commands.push(DrawCommand_1.PopBranchCommand.instance);
    }
}
class BatchState extends State {
    get opcode() { return "Batch"; }
    constructor(branchState, pushCommand) {
        super(branchState.map);
        this.branchState = branchState;
        this.pushCommand = pushCommand;
    }
    set currentLayer(layer) {
        if (undefined === layer)
            this.throwStateError("currentLayer = undefined");
        else
            this.map.state = new LayerState(this, layer);
    }
}
class LayerState extends State {
    get opcode() { return "Layer"; }
    constructor(batchState, layer) {
        super(batchState.map);
        this._batchState = batchState;
        this.commands = this.map.getCommands(layer, batchState.branchState.containerState.elevation);
        this._batchState.branchState.markLayer(this.commands);
        this.commands.commands.push(batchState.pushCommand);
    }
    set currentLayer(layer) {
        if (undefined === layer) {
            this.commands.commands.push(DrawCommand_1.PopBatchCommand.instance);
            this.map.state = this._batchState;
        }
        else {
            this.throwStateError("currentLayer != undefined");
        }
    }
    pushAndPop(push, pop, func) {
        this.commands.commands.push(push);
        func();
        this.commands.commands.push(pop);
    }
    addCommands(commands) {
        for (const command of commands)
            this.commands.commands.push(command);
    }
}
/** DrawCommands associated with one Layer, drawn during the Layers render pass. */
class LayerCommands {
    constructor(layerId, priority, elevation) {
        this.commands = [];
        this.layerId = layerId;
        this.priority = priority;
        this.elevation = elevation;
    }
}
class LayerCommandMap extends core_bentley_1.SortedArray {
    constructor(target) {
        // Layers with different view Z draw in ascending order by Z.
        // Layers with same elevation draw in ascending order by priority.
        // Layers with same elevation and priority draw in indeterminate order.
        super((lhs, rhs) => {
            let cmp = (0, core_bentley_1.compareNumbers)(lhs.elevation, rhs.elevation);
            if (0 === cmp) {
                cmp = (0, core_bentley_1.compareNumbers)(lhs.priority, rhs.priority);
                if (0 === cmp)
                    cmp = (0, core_bentley_1.compareStrings)(lhs.layerId, rhs.layerId);
            }
            return cmp;
        });
        this.target = target;
        this.state = new IdleState(this);
    }
    clear() {
        super.clear();
        (0, core_bentley_1.assert)(this.state instanceof IdleState);
    }
    getCommands(layer, elevation) {
        for (const entry of this._array)
            if (entry.layerId === layer.layerId && entry.elevation === elevation)
                return entry;
        const cmds = new LayerCommands(layer.layerId, layer.getPriority(this.target), elevation);
        this.insert(cmds);
        return cmds;
    }
    outputCommands(cmds) {
        for (const entry of this._array)
            for (const cmd of entry.commands)
                cmds.push(cmd);
    }
}
/** @internal */
class LayerCommandLists {
    constructor(cmds) {
        this._maps = [];
        this._renderCommands = cmds;
    }
    clear() {
        this._maps.length = 0;
    }
    processLayers(container, func) {
        (0, core_bentley_1.assert)(undefined === this._activeMap);
        const pass = container.renderPass;
        this._activeMap = this._maps[pass];
        if (undefined === this._activeMap) {
            this._activeMap = new LayerCommandMap(this._renderCommands.target);
            this._maps[pass] = this._activeMap;
        }
        this._activeMap.state.processLayers(container, func);
        this._activeMap = undefined;
    }
    set currentLayer(layer) {
        (0, core_bentley_1.assert)(undefined !== this._activeMap);
        this._activeMap.state.currentLayer = layer;
    }
    addCommands(cmds) {
        (0, core_bentley_1.assert)(undefined !== this._activeMap);
        this._activeMap.state.addCommands(cmds);
    }
    pushAndPop(push, pop, func) {
        (0, core_bentley_1.assert)(undefined !== this._activeMap);
        this._activeMap.state.pushAndPop(push, pop, func);
    }
    outputCommands() {
        this.outputCommandsForPass(1 /* RenderPass.OpaqueLayers */);
        this.outputCommandsForPass(7 /* RenderPass.TranslucentLayers */);
        this.outputCommandsForPass(11 /* RenderPass.OverlayLayers */);
    }
    outputCommandsForPass(pass) {
        const map = this._maps[pass];
        if (undefined !== map)
            map.outputCommands(this._renderCommands.getCommands(pass));
    }
}
exports.LayerCommandLists = LayerCommandLists;
//# sourceMappingURL=LayerCommands.js.map