import {
    LinearFilter,
    OrthographicCamera,
    RGBAFormat,
    Scene,
    WebGLMultisampleRenderTarget,
    WebGLRenderTarget
} from 'three';
import { assert } from "../../../core/assert.js";
import Vector2 from "../../../core/geom/Vector2.js";
import { BlendingType } from "../texture/sampler/BlendingType.js";
import { CompositLayer } from "./CompositLayer.js";

class LayerCompositer {
    constructor() {
        /**
         *
         * @type {CompositLayer[]}
         */
        this.layerTargets = [];

        //
        this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
        this.scene = new Scene();

        this.size = new Vector2(0, 0);

        /**
         * @private
         * @type {number}
         */
        this.__pixelRatio = 1;
    }

    /**
     *
     * @param {CompositLayer} layer
     * @returns {boolean}
     */
    remove(layer) {
        const i = this.layerTargets.indexOf(layer);

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

        this.layerTargets.splice(i, 1);

        // cleanup memory
        layer.renderTarget.dispose();

        return true;
    }

    /**
     *
     * @param param
     * @param {BlendingType} blending
     * @return {CompositLayer}
     */
    createLayer(param, blending = BlendingType.Normal) {
        let renderTarget;

        if (param instanceof WebGLRenderTarget || param instanceof WebGLMultisampleRenderTarget) {
            // A render target was specified
            renderTarget = param;
        } else {
            const renderTargetParameters = {
                minFilter: LinearFilter,
                magFilter: LinearFilter,
                format: RGBAFormat,
                depthBuffer: false,
                stencilBuffer: false,
                generateMipmaps: false
            };
            for (let p in param) {
                if (param.hasOwnProperty(p)) {
                    renderTargetParameters[p] = param[p];
                }
            }


            renderTarget = new WebGLMultisampleRenderTarget(1, 1, renderTargetParameters);
        }

        const layer = new CompositLayer({
            renderTarget,
            blending
        });

        this.addLayer(layer);

        return layer;
    }

    /**
     *
     * @param {CompositLayer} layer
     * @returns {boolean}
     */
    addLayer(layer) {
        if (this.layerTargets.includes(layer)) {
            // already added
            return false;
        }

        layer.setSize(this.size.x, this.size.y);

        this.layerTargets.push(layer);

        return true;
    }

    /**
     * @returns {boolean}
     */
    hasActiveLayers() {
        const layerTargets = this.layerTargets;

        const n = layerTargets.length;
        for (let i = 0; i < n; i++) {
            const layer = layerTargets[i];

            if (layer.enabled) {
                return true;
            }
        }

        return false;
    }

    /**
     *
     * @param {number} stage
     * @private
     */
    __prepare_scene_for_stage(stage) {
        const layers = this.layerTargets;
        const layer_count = layers.length;

        const scene = this.scene;

        let child_count = 0;

        for (let i = 0; i < layer_count; i++) {
            const layer = layers[i];

            if (layer.stage !== stage) {
                continue;
            }

            scene.children[child_count++] = layer.object;

            layer.on.preRender.send0();
        }

        scene.children.length = child_count;
    }

    /**
     *
     * @param {THREE.WebGLRenderer} renderer
     * @param {number} stage
     */
    composite(renderer, stage) {
        assert.isNonNegativeInteger(stage, 'stage');

        if (!this.hasActiveLayers()) {
            return;
        }

        this.__prepare_scene_for_stage(stage);

        const camera = this.camera;
        const scene = this.scene;

        renderer.autoClear = false;
        renderer.render(scene, camera);
    }

    /**
     *
     * @param {number} value
     */
    setPixelRatio(value) {
        if (this.__pixelRatio === value) {
            return;
        }

        assert.greaterThanOrEqual(value, 0, 'value');


        this.__pixelRatio = value;

        this.__updateResolution();
    }

    /**
     *
     * @returns {number}
     */
    getPixelRatio() {
        return this.__pixelRatio;
    }

    __updateResolution() {
        const x = this.size.x;
        const y = this.size.y;

        const h = y * this.__pixelRatio;
        const w = x * this.__pixelRatio;

        const layerTargets = this.layerTargets;

        for (let i = 0; i < layerTargets.length; i++) {

            const layerTarget = layerTargets[i];

            layerTarget.setSize(w, h);

        }
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     */
    setSize(x, y) {
        this.size.set(x, y);

        this.__updateResolution(x, y);
    }
}

export default LayerCompositer;
