import { AbstractRenderAdapter } from "./AbstractRenderAdapter.js";
import { HashMap } from "../../../../../../core/collection/map/HashMap.js";
import { InstancedMeshGroup } from "../../../../geometry/instancing/InstancedMeshGroup.js";
import { ShadedGeometryFlags } from "../../ShadedGeometryFlags.js";
import { StreamDrawUsage } from "three";
import { SGCacheKey } from "./SGCacheKey.js";

const INSTANCED_EQUALITY_FLAGS = ShadedGeometryFlags.CastShadow
    | ShadedGeometryFlags.ReceiveShadow
;

/**
 * @readonly
 * @type {SGCacheKey}
 */
const scratch_key = new SGCacheKey();

export class InstancedRendererAdapter extends AbstractRenderAdapter {

    /**
     *
     * @type {HashMap<SGCacheKey, InstancedMeshGroup>}
     * @private
     */
    __instanced_meshes = new HashMap();

    /**
     *
     * @type {Set<InstancedMeshGroup>}
     * @private
     */
    __active_meshes = new Set();


    score(geometry, material, instance_count) {
        if (instance_count > 16) {
            return 1;
        } else if (instance_count < 4) {
            return -1;
        } else {
            return 0;
        }
    }

    /**
     *
     * @param {ShadedGeometry} sg
     * @returns {InstancedMeshGroup}
     * @private
     */
    __get_instanced_mesh(sg) {
        scratch_key.fromSG(sg);

        // mask out some flags
        scratch_key.flags &= INSTANCED_EQUALITY_FLAGS

        let im = this.__instanced_meshes.get(scratch_key);


        if (im !== undefined) {

            return im;

        } else {

            im = new InstancedMeshGroup();
            im.instance_usage = StreamDrawUsage;
            im.shrinkFactor = 0; // prevent shrinking as we're essentially rebuilding this geometry frame

            im.draw_mode = sg.mode;

            im.setMaterial(sg.material);
            im.setGeometry(sg.geometry);

            im.mesh.castShadow = sg.getFlag(ShadedGeometryFlags.CastShadow);
            im.mesh.receiveShadow = sg.getFlag(ShadedGeometryFlags.ReceiveShadow);

            this.__instanced_meshes.set(scratch_key.clone(), im);

            return im;

        }
    }

    add(sg) {
        const mesh = this.__get_instanced_mesh(sg);

        const index = mesh.count;

        const new_size = index + 1;

        if (new_size > mesh.capacity) {
            mesh.ensureCapacity(new_size);
        }

        mesh.setTransformAt(index, sg.transform);

        mesh.count = new_size;

        this.__active_meshes.add(mesh);
    }

    /**
     *
     * @param {THREE.WebGLRenderer} renderer
     * @param {CameraView} view
     */
    build_start(renderer, view) {
        this.clear();
    }

    build_end() {
        const objects = this.__objects;

        for (const activeMesh of this.__active_meshes) {
            // force underlying geometry to update
            activeMesh.setCount(activeMesh.getCount());
            activeMesh.requestAttributeUpdate();

            objects[this.__object_count++] = activeMesh.mesh;
        }

    }

    clear() {
        super.clear();

        const meshes = this.__active_meshes;

        for (const mesh of meshes) {
            mesh.setCount(0);
        }

        meshes.clear();
    }

    dispose() {
        this.clear();

        // release GPU memory
        this.__instanced_meshes.forEach((value, key) => {
            value.dispose();
        });

        this.__instanced_meshes.clear();
    }
}
