import {
    CubeCamera,
    LinearEncoding,
    LinearFilter,
    MeshStandardMaterial,
    RGBAFormat,
    Scene,
    VSMShadowMap,
    WebGLCubeRenderTarget
} from "three";
import { assert } from "../../../../core/assert.js";
import { array_copy } from "../../../../core/collection/array/array_copy.js";
import { Transform } from "../../../ecs/transform/Transform.js";
import { three_set_shadow_resolution } from "../../ecs/light/binding/three/ThreeLightBinding.js";
import { threeMakeLight } from "../../ecs/light/binding/three/threeMakeLight.js";
import { Light } from "../../ecs/light/Light.js";
import { ThreeLightCache } from "../../ecs/light/three/ThreeLightCache.js";
import { build_three_object } from "../../ecs/mesh-v2/build_three_object.js";
import { ShadedGeometry } from "../../ecs/mesh-v2/ShadedGeometry.js";
import { applyTransformToThreeObject } from "../../ecs/mesh/applyTransformToThreeObject.js";
import { WebGLRendererPool } from "../../render/RendererPool.js";
import { fromCubeRenderTarget } from "../fromCubeRenderTarget.js";
import { ProbeRenderer } from "./ProbeRenderer.js";

export class WebGLCubeProbeRenderer extends ProbeRenderer {
    constructor(resolution = 32) {

        super();

        assert.isNonNegativeInteger(resolution, 'resolution');

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

        this.render_target = new WebGLCubeRenderTarget(resolution, {
            format: RGBAFormat,
            minFilter: LinearFilter,
            magFilter: LinearFilter,
            depthBuffer: true,
            scissorTest: false,
            stencilBuffer: false,
            generateMipmaps: false,
            encoding: LinearEncoding
        });

        /**
         * Used to read out GPU memory for each face
         * @type {Uint8Array}
         * @private
         */
        this.__cube_face_raw = new Uint8Array(this.render_target.width * this.render_target.width * 4);


        this.__cube_camera = new CubeCamera(0.1, 100, this.render_target);

        /**
         *
         * @type {Scene}
         * @private
         */
        this.__scene = new Scene();
    }

    /**
     *
     * @param {EntityComponentDataset} ecd
     */
    build_scene(ecd) {
        this.__scene.children.splice(0, this.__scene.children.length);
        ecd.traverseEntities([ShadedGeometry, Transform], (sg, t, entity) => {
            const object3D = build_three_object(sg);
            const source_material = sg.material;
            //
            if (source_material.isMeshStandardMaterial === true) {
                object3D.material = new MeshStandardMaterial({
                    map: source_material.map,
                });
                object3D.material.color.copy(source_material.color);
            } else {
                object3D.material = source_material.clone();
            }
            applyTransformToThreeObject(object3D, t);
            array_copy(t.matrix, 0, object3D.matrixWorld.elements, 0, 16);
            this.__scene.add(object3D);
        });
        const lightCache = new ThreeLightCache();
        ecd.traverseEntities([Light, Transform], (light, t) => {
            /**
             *
             * @type {DirectionalLight|SpotLight|PointLight|AmbientLight}
             */
            const object = threeMakeLight(light, lightCache);
            applyTransformToThreeObject(object, t);
            const shadow = object.shadow;
            if (shadow !== undefined) {
                shadow.radius = 1;
                shadow.blurSamples = 8;
                three_set_shadow_resolution(shadow, 2048);
            }
            this.__scene.add(object);
        });
        // this.__scene.add(this.__cube_camera);
    }

    bake_start() {
        const renderer = WebGLRendererPool.global.get();
        this.__renderer = renderer;
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = VSMShadowMap;
        // this.__renderer = this.ge.getRenderer();
    }

    bake_end() {
        WebGLRendererPool.global.release(this.__renderer);
        this.__renderer = null;
    }

    /**
     *
     * @param {number[]} position
     * @param {number} position_offset
     * @param {number[]} output
     * @param {number} output_offset
     */
    bake(position, position_offset, output, output_offset) {
        // console.warn(position_offset, output_offset);
        // const ge = this.ge;
        const renderer = this.__renderer;
        const _rt = renderer.getRenderTarget();
        const _rt_active_face = renderer.getActiveCubeFace();
        const _acc = renderer.autoClearColor;
        const _acd = renderer.autoClearDepth;
        const _ac = renderer.autoClear;
        const _outputEncoding = renderer.outputEncoding;
        renderer.outputEncoding = LinearEncoding;
        renderer.autoClearColor = true;
        renderer.autoClearDepth = true;
        renderer.autoClear = true;
        this.__cube_camera.position.fromArray(position, position_offset);
        this.__cube_camera.children.forEach(c => {
            c.updateProjectionMatrix();
            c.updateMatrix();
            c.updateMatrixWorld(true);
        });
        this.__cube_camera.update(renderer, this.__scene);
        // compute coefficients from renderer
        const probe_coefficients = fromCubeRenderTarget(this.__cube_face_raw, renderer, this.render_target);
        array_copy(probe_coefficients, 0, output, output_offset, 27);
        // restore state
        renderer.setRenderTarget(_rt, _rt_active_face);
        renderer.autoClearColor = _acc;
        renderer.autoClearDepth = _acd;
        renderer.autoClear = _ac;
        renderer.outputEncoding = _outputEncoding;
    }
}