import {
    DoubleSide,
    HalfFloatType,
    Mesh,
    MeshNormalMaterial,
    NearestFilter,
    RGBAFormat,
    Scene,
    Vector2,
    WebGLMultisampleRenderTarget
} from "three";
import { StandardFrameBuffers } from "../../../StandardFrameBuffers.js";
import { three_setSceneAutoUpdate } from "../../../three/three_setSceneAutoUpdate.js";
import { ThreeBypassRenderer } from "../../utils/ThreeBypassRenderer.js";
import { FrameBuffer } from "../FrameBuffer.js";

export class NormalFrameBuffer extends FrameBuffer {

    constructor(id) {
        super(id);

        this.dependencies.push(StandardFrameBuffers.ColorAndDepth);
    }

    initialize(renderer) {
        super.initialize(renderer);

        // enable floating texture extension
        renderer.extensions.get('EXT_color_buffer_float');

        const size = new Vector2();

        renderer.getSize(size);

        const target = new WebGLMultisampleRenderTarget(size.x, size.y, {
            minFilter: NearestFilter,
            magFilter: NearestFilter,
            format: RGBAFormat, // note, three.js required RGBA render texture, even though RGB would do here
            type: HalfFloatType,
            depthBuffer: true,
            stencilBuffer: false,
            generateMipmaps: false
        });
        target.texture.internalFormat = 'RGBA16F';

        this.renderTarget = target;

        this.__material_static = new MeshNormalMaterial({ depthWrite: true });
        this.__material_skinned = new MeshNormalMaterial({ depthWrite: true });

        //SET material to double-sided, to support double-sided materials. This is an overkill, and will hurt performance a little. A full-blown material cache might be a better solution in the long run
        this.__material_static.side = DoubleSide;
        this.__material_skinned.side = DoubleSide;


        const scene = new Scene();
        /**
         * @type {Scene}
         * @private
         */
        this.__scene = scene;
        three_setSceneAutoUpdate(scene, false);
        scene.matrixAutoUpdate = false;
        scene.matrixWorldNeedsUpdate = false;
    }


    /**
     *
     * @param {THREE.Mesh|{customNormalMaterial:THREE.Material}} mesh
     * @return {MeshNormalMaterial}
     * @private
     */
    __bindMaterial(mesh) {
        let target_material;

        if (mesh.customNormalMaterial !== undefined) {
            target_material = mesh.customNormalMaterial;
        } else {

            /**
             *
             * @type {Material}
             */
            const source_material = mesh.material;

            // TODO take normal map into account

            if (mesh.isSkinnedMesh) {
                target_material = this.__material_skinned;
            } else {
                target_material = this.__material_static;
            }


            if (source_material !== undefined) {
                // assign normal map and color map

                if (source_material.alphaTest > 0) {
                    target_material.map = source_material.map;
                } else {
                    target_material.map = null;
                }

                target_material.normalMap = source_material.normalMap;
            }
        }

        return target_material;
    }

    /**
     *
     * @see {@link OutlineRenderer} for details on this technique
     * @param renderer
     * @param camera
     * @param scene
     * @param dependencies
     */
    render(renderer, camera, scene, dependencies) {
        this.__renderer = renderer;

        // read current state
        const _state_rt = renderer.getRenderTarget();

        renderer.setRenderTarget(this.renderTarget);

        renderer.clear(true, true, false);

        ThreeBypassRenderer.INSTANCE.build_scene({
            scene: this.__scene,
            input: scene.children,
            input_size: scene.children.length,
            object_filter: object3D => object3D.visible,
            material_extractor: this.__bindMaterial,
            material_extractor_context: this
        });

        renderer.render(this.__scene, camera);

        // restore state
        renderer.setRenderTarget(_state_rt);
    }
}
