import { Mesh, SkinnedMesh } from "three";

import { returnTrue } from "../../../../core/function/returnTrue.js";

const stack = [];

export class ThreeBypassRenderer {
    constructor() {

        /**
         *
         * @type {WeakMap<THREE.Object3D, THREE.Object3D>}
         * @private
         */
        this.__object_map = new WeakMap();
    }

    /**
     *
     * @param {Mesh|SkinnedMesh} source
     * @return {Mesh|SkinnedMesh}
     * @private
     */
    __get_object_twin(source) {
        const existing = this.__object_map.get(source);

        if (existing !== undefined) {
            return existing;
        }

        let mesh;
        const geometry = source.geometry;

        if (source.isSkinnedMesh) {
            mesh = new SkinnedMesh(geometry, null);
        } else {
            mesh = new Mesh(geometry, null);
        }

        mesh.matrixAutoUpdate = false;
        mesh.matrixWorldNeedsUpdate = false;
        mesh.frustumCulled = false;

        this.__object_map.set(source, mesh);

        return mesh;
    }

    /**
     *
     * @param {THREE.Object3D} scene
     * @param {THREE.Object3D[]} input
     * @param {number} input_size
     * @param {function(THREE.Object3D):boolean} input_filter
     * @param {function(THREE.Object3D):boolean} object_filter
     * @param {function(THREE.Object3D):THREE.Material} material_extractor
     * @param {*} [material_extractor_context]
     */
    build_scene({
                    scene,
                    input,
                    input_size,
                    input_filter = returnTrue,
                    object_filter,
                    material_extractor,
                    material_extractor_context
                }) {

        // prepare shadow scene
        const scene_children = scene.children;

        scene_children.length = 0;

        let stack_top = 0;
        let j = 0;
        let i = 0;

        for (; i < input_size; i++) {
            const object3D = input[i];

            if (input_filter(object3D)) {
                stack[stack_top++] = object3D;
            }

        }

        while (stack_top > 0) {
            const object3D = stack[--stack_top];

            if (
                object_filter(object3D)
            ) {
                if (object3D.isMesh) {
                    const geometry = object3D.geometry;

                    if (geometry !== null) {

                        const mesh = this.__get_object_twin(object3D);
                        const material = material_extractor.call(material_extractor_context, object3D);

                        mesh.geometry = geometry;
                        mesh.material = material;

                        if (object3D.isSkinnedMesh) {
                            mesh.bindMatrix.copy(object3D.bindMatrix);
                            mesh.bindMatrixInverse.copy(object3D.bindMatrixInverse);
                            mesh.skeleton = object3D.skeleton;
                        }

                        mesh.matrix.copy(object3D.matrix);
                        mesh.matrixWorld.copy(object3D.matrixWorld);
                        mesh.normalMatrix.copy(object3D.normalMatrix);

                        scene_children[j++] = mesh;
                        mesh.parent = scene;

                    }
                }
            }

            const children = object3D.children;
            const child_count = children.length;

            for (i = 0; i < child_count; i++) {
                const child = children[i];

                stack[stack_top++] = child;
            }
        }

        scene_children.length = j;

    }
}


ThreeBypassRenderer.INSTANCE = new ThreeBypassRenderer();
