import {
    Color,
    LinearFilter,
    NearestFilter,
    RGBAFormat,
    Scene,
    ShaderMaterial,
    Vector2,
    VSMShadowMap,
    WebGLRenderTarget
} from "three";
import Signal from "../../../core/events/signal/Signal.js";
import { fragment as vsm_frag, vertex as vsm_vert } from "./vsm.glsl.js";
import { renderScreenSpace } from "../render/utils/renderScreenSpace.js";

const shadowMaterialVertical = new ShaderMaterial({

    uniforms: {
        shadow_pass: { value: null },
        resolution: { value: new Vector2() },
        radius: { value: 4.0 },
        samples: { value: 8.0 }
    },

    defines: {
        VSM_SAMPLES: 8, // in 'defines' since r134
    },

    vertexShader: vsm_vert,

    fragmentShader: vsm_frag

});

const shadowMaterialHorizontal = shadowMaterialVertical.clone();
shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;

const scratch_color = new Color();

export class ShadowMapRenderer {
    constructor() {

        /**
         *
         * @type {WebGLRenderer|null}
         * @private
         */
        this.__renderer = null;

        /**
         *
         * @type {Signal}
         */
        this.onBeforeRender = new Signal();

    }

    /**
     *
     * @param {THREE.WebGLRenderer} renderer
     * @param {LightShadow} shadow
     * @param {Scene} scene
     * @param {Light} light
     */
    update(renderer, shadow, scene, light) {
        //

        const shadow_camera = shadow.camera;

        // this.__scene = scene;
        this.__renderer = renderer;

        this.__ensure_map(shadow, renderer);

        // draw scene from perspective of shadow camera into the render texture

        const __rt = renderer.getRenderTarget();
        const __acf = renderer.getActiveCubeFace();
        const __aml = renderer.getActiveMipmapLevel();

        const __scissor = renderer.getScissorTest();
        renderer.getClearColor(scratch_color);
        const __clear_alpha = renderer.getClearAlpha();

        const __autoClearColor = renderer.autoClearColor;
        const __autoClearDepth = renderer.autoClearDepth;
        const __autoClearStencil = renderer.autoClearStencil;
        const __sortObjects = renderer.sortObjects;

        const target = shadow.map;

        renderer.setRenderTarget(target);
        renderer.setClearColor(0xFFFFFF, 1);
        renderer.setScissorTest(false);

        renderer.autoClearColor = false;
        renderer.autoClearDepth = false;
        renderer.autoClearStencil = false;

        // disable sorting, unnecessary since fragment cost is very low and sorting is expensive for large number of objects
        renderer.sortObjects = false;

        renderer.clear(true, true, false);

        shadow.updateMatrices(light, 0);

        this.onBeforeRender.send3(renderer, shadow_camera, scene);

        if (target.width <= 0 || target.height <= 0) {
            // no point in rendering
        } else {
            // do the draw
            renderer.render(scene, shadow_camera);

            if (renderer.shadowMap.type === VSMShadowMap) {
                this.__do_vsm_pass(shadow);
            }
        }


        // restore state
        renderer.setRenderTarget(__rt, __acf, __aml);
        renderer.setScissorTest(__scissor);
        renderer.setClearColor(scratch_color, __clear_alpha);

        renderer.autoClearColor = __autoClearColor;
        renderer.autoClearDepth = __autoClearDepth;
        renderer.autoClearStencil = __autoClearStencil;
        renderer.sortObjects = __sortObjects;

        // increment frame index to signal to the renderer that geometries might need to be updated
        renderer.info.render.frame++;
    }

    /**
     *
     * @param {LightShadow} shadow
     * @private
     */
    __do_vsm_pass(shadow) {
        const _renderer = this.__renderer;

        if (shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples) {

            shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples;
            shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples;

            shadowMaterialVertical.needsUpdate = true;
            shadowMaterialHorizontal.needsUpdate = true;

        }

        // vertical pass

        const vertical = shadowMaterialVertical.uniforms;

        vertical.shadow_pass.value = shadow.map.texture;
        vertical.resolution.value = shadow.mapSize;
        vertical.radius.value = shadow.radius;
        vertical.samples.value = shadow.blurSamples;

        _renderer.setRenderTarget(shadow.mapPass);
        _renderer.clear();

        renderScreenSpace(_renderer, shadowMaterialVertical);

        // horizontal pass

        const horizontal = shadowMaterialHorizontal.uniforms;

        horizontal.shadow_pass.value = shadow.mapPass.texture;
        horizontal.resolution.value = shadow.mapSize;
        horizontal.radius.value = shadow.radius;
        horizontal.samples.value = shadow.blurSamples;

        _renderer.setRenderTarget(shadow.map);
        _renderer.clear();

        renderScreenSpace(_renderer, shadowMaterialHorizontal);

    }

    /**
     *
     * @param {LightShadow} shadow
     * @param {THREE.WebGLRenderer} renderer
     * @private
     */
    __ensure_map(shadow, renderer) {
        const _shadowMapSize = shadow.mapSize;

        if (shadow.map === null) {

            const pars = {
                format: RGBAFormat,
                scissorTest: false,
                stencilBuffer: false
            };

            if (!shadow.isPointLightShadow && renderer.shadowMap.type === VSMShadowMap) {
                pars.minFilter = LinearFilter;
                pars.magFilter = LinearFilter;

                shadow.mapPass = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, Object.assign({
                    depthBuffer: false
                }, pars));

            } else {
                pars.minFilter = NearestFilter;
                pars.magFilter = NearestFilter;

            }

            shadow.map = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, pars);
            shadow.map.texture.name = '.shadowMap';

            shadow.camera.updateProjectionMatrix();
        }
    }
}
