import { Scene as ThreeScene } from "three";
import { noop } from "../../../../../core/function/noop.js";
import { max2 } from "../../../../../core/math/max2.js";
import { ObjectPoolFactory } from "../../../../../core/model/object/ObjectPoolFactory.js";
import { ProcessState } from "../../../../../core/process/ProcessState.js";
import { EnginePlugin } from "../../../../plugin/EnginePlugin.js";
import { CompositingStages } from "../../../composit/CompositingStages.js";
import { BlendingType } from "../../../texture/sampler/BlendingType.js";
import { HighlightRenderElement } from "../renderer/HighlightRenderElement.js";
import { HighlightRenderGroup } from "../renderer/HighlightRenderGroup.js";
import { OutlineRenderer } from "../renderer/OutlineRenderer.js";

export class OutlineRenderPlugin extends EnginePlugin {
    constructor() {
        super();

        this.id = "outline-renderer";

        this.__renderer = new OutlineRenderer();

        this.__scene = new ThreeScene();

        /**
         *
         * @type {CompositLayer}
         * @private
         */
        this.__compositing_layer = null;

        /**
         *
         * @type {HighlightRenderGroup}
         * @private
         */
        this.__highlight_render_group = new HighlightRenderGroup();

        /**
         *
         * @type {HighlightRenderElementSource[]}
         * @private
         */
        this.__sources = [];

        /**
         *
         * @type {ObjectPoolFactory<HighlightRenderElement>}
         * @private
         */
        this.__element_pool = new ObjectPoolFactory(
            () => {
                return new HighlightRenderElement();
            },
            noop,
            (element) => {
                element.definitions = [];
                element.object = null;
            }
        );
    }

    /**
     *
     * @param {HighlightRenderElementSource} source
     * @returns {boolean}
     */
    addSource(source) {
        this.__sources.push(source);

        return true;
    }

    /**
     *
     * @param {HighlightRenderElementSource} source
     * @returns {boolean}
     */
    removeSource(source) {
        const i = this.__sources.indexOf(source);

        if (i === -1) {
            return false;
        }

        this.__sources.splice(i, 1);

        return true;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @private
     */
    __set_viewport_size(x, y) {
        // clamp to 1: 0 cascades to several render targets whose clear() then trips GL_INVALID_FRAMEBUFFER_OPERATION
        const width = max2(1, x);
        const height = max2(1, y);

        this.__renderer.resize(width, height);
    }

    /**
     *
     * @private
     */
    __render() {
        const engine = this.engine;
        const graphics = engine.graphics;

        const camera = graphics.camera;

        // collect elements
        const source_count = this.__sources.length;

        for (let i = 0; i < source_count; i++) {
            const source = this.__sources[i];

            source.collect(this.__element_pool, this.__highlight_render_group);
        }

        if (this.__highlight_render_group.isEmpty()) {
            //nothing to render
            this.__renderer.clearRenderTarget();

            // disable rendering of compositing layer
            this.__compositing_layer.disable();
        } else {
            // enabled rendering of the compositing layer
            this.__compositing_layer.enable();

            this.__renderer.setCamera(camera);
            this.__renderer.render(this.__highlight_render_group);

            // release elements back into the object pool
            this.__highlight_render_group.elements.forEach(this.__element_pool.release, this.__element_pool);
            this.__highlight_render_group.clear();
        }
    }

    async startup() {

        if (this.getState().getValue() === ProcessState.Running) {
            //already running
            return;
        }

        const graphics = this.engine.graphics;
        const viewport_size = graphics.viewport.size;

        viewport_size.process(this.__set_viewport_size, this);

        // create compositing layer
        this.__compositing_layer = graphics.layerComposer.createLayer(this.__renderer.__renderTargetFinal, BlendingType.MultiplyAdd);
        this.__compositing_layer.name = "Silhouette Outline";
        this.__compositing_layer.stage = CompositingStages.POST_TRANSPARENT;

        graphics.on.preRender.add(this.__render, this);


        // set rendering resolution
        this.__compositing_layer.setRenderTargetScale(1);

        await super.startup();
    }

    async shutdown() {

        const graphics = this.engine.graphics;

        graphics.on.preRender.remove(this.__render, this);

        graphics.viewport.size.onChanged.remove(this.__set_viewport_size, this);

        graphics.layerComposer.remove(this.__compositing_layer);

        await super.shutdown();
    }

    async initialize(engine) {

        /**
         *
         * @type {GraphicsEngine}
         */
        const graphicsEngine = engine.graphics;

        this.__renderer.setWebGLRenderer(graphicsEngine.renderer);
        this.__renderer.setCamera(graphicsEngine.camera);
        this.__renderer.setScene(this.__scene);

        await super.initialize(engine);
    }
}
