import { assert } from "../../../../../core/assert.js";
import {
    bvh_query_user_data_overlaps_frustum
} from "../../../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_frustum.js";
import { ShadedGeometry } from "../ShadedGeometry.js";
import { ShadedGeometryFlags } from "../ShadedGeometryFlags.js";
import { AbstractRenderAdapter } from "./adapters/AbstractRenderAdapter.js";
import { GenericRendererAdapter } from "./adapters/GenericRendererAdapter.js";
import { InstancedRendererAdapter } from "./adapters/InstancedRendererAdapter.js";

/**
 *
 * @type {number[]}
 */
const collection_array = [];

/**
 * 1 - collect visible meshes
 * 2 - figure out how to render a given mesh
 */
export class ShadedGeometryRendererContext {
    constructor() {
        /**
         * @readonly
         * @type {AbstractRenderAdapter[]}
         */
        this.adapters = [
            new GenericRendererAdapter(),
            new InstancedRendererAdapter()
        ];
    }

    /**
     * Performs maintenance on the context, mainly to check the hash tables for changes
     * @return {Generator}
     */
    * maintain() {
        const adapters = this.adapters;
        for (let i = 0; i < adapters.length; i++) {
            const adapter = adapters[i];

            if (adapter === undefined) {
                // adapter may disappear between invocations, since this is a continuation-type function
                continue;
            }

            // delegate to adapter's maintenance
            yield* adapter.maintain();
        }
    }

    /**
     *
     * @param {number} index
     * @param {AbstractRenderAdapter} adapter
     */
    setAdapter(index, adapter) {
        assert.isNonNegativeInteger(index, 'index');
        assert.defined(adapter, 'adapter');
        assert.equal(adapter.isShadedGeometryRenderAdapter, true, 'adapter.isShadedGeometryRenderAdapter !== true');

        const adapters = this.adapters;
        const existing = adapters[index];

        if (existing !== undefined) {
            // dispose of existing adapter
            existing.dispose();
        }

        adapters[index] = adapter;
    }

    /**
     *
     * @param {number} index
     * @returns {boolean}
     */
    removeAdapter(index) {
        assert.isNonNegativeInteger(index, 'index');

        const adapter = this.adapters[index];

        if (adapter === undefined) {
            return false;
        }

        delete this.adapters[index];

        adapter.dispose();

        return true;
    }

    /**
     *
     * @param {THREE.Object3D[]} destination
     * @param {number} destination_offset
     * @param {THREE.WebGLRenderer} renderer
     * @param {CameraView} view
     * @param {BVH} bvh
     * @param {EntityComponentDataset} ecd
     */
    collect(destination, destination_offset, renderer, view, bvh, ecd) {
        const adapters = this.adapters;
        const adapter_count = adapters.length;

        let i = 0, j = 0;
        for (; i < adapter_count; i++) {
            const adapter = adapters[i];

            adapter.build_start(renderer, view);
        }


        // get all meshes
        const intersection_count = bvh_query_user_data_overlaps_frustum(collection_array, 0, bvh, view.frustum);

        const sg_component_index = ecd.computeComponentTypeIndex(ShadedGeometry);

        for (i = 0; i < intersection_count; i++) {
            const entity = collection_array[i];

            /**
             *
             * @type {ShadedGeometry}
             */
            const sg = ecd.getComponentByIndex(entity, sg_component_index);

            if ((sg.flags & ShadedGeometryFlags.Visible) === 0) {
                // not visible
                continue;
            }

            if (!sg.material.visible) {
                // material is not visible, skip
                continue;
            }

            const adapter = adapters[sg.draw_method];

            adapter.add(sg);
        }

        let additions = 0;

        for (i = 0; i < adapter_count; i++) {
            const adapter = adapters[i];

            adapter.build_end();

            const adapter_object_count = adapter.object_count;

            for (j = 0; j < adapter_object_count; j++) {
                const object3D = adapter.objects[j];

                destination[destination_offset + additions] = object3D;

                additions++;
            }

        }

        return additions;
    }

    dispose() {
        for (const adapter of this.adapters) {
            if (adapter !== undefined) {
                adapter.dispose();
            }
        }
    }
}
