import { BVH_BINARY_NODE_SIZE, BVH_LEAF_NODE_SIZE } from "../../../../../core/bvh2/binary/2/BinaryUint32BVH.js";
import { SCRATCH_UINT32_TRAVERSAL_STACK } from "../../../../../core/collection/SCRATCH_UINT32_TRAVERSAL_STACK.js";
import {
    aabb3_compute_distance_above_plane_max
} from "../../../../../core/geom/3d/aabb/aabb3_compute_distance_above_plane_max.js";
import { point_light_inside_volume } from "./point_light_inside_volume.js";

const stack = SCRATCH_UINT32_TRAVERSAL_STACK;

/**
 *
 * @param {Float32Array} data
 * @param {number} address
 * @param {ArrayLike<number>|number[]} planes
 * @return {boolean}
 */
function frustum_check(data, address, planes) {
    const n_x0 = data[address];
    const n_y0 = data[address + 1];
    const n_z0 = data[address + 2];

    const n_x1 = data[address + 3];
    const n_y1 = data[address + 4];
    const n_z1 = data[address + 5];

    for (let plane_address = 0; plane_address < 24; plane_address += 4) {

        const plane_normal_x = planes[plane_address];
        const plane_normal_y = planes[plane_address + 1];
        const plane_normal_z = planes[plane_address + 2];
        const plane_constant = planes[plane_address + 3];

        const distanceAbovePlane = aabb3_compute_distance_above_plane_max(
            plane_normal_x, plane_normal_y, plane_normal_z, plane_constant,
            n_x0, n_y0, n_z0,
            n_x1, n_y1, n_z1
        );

        if (distanceAbovePlane <= 0) {
            // node is below the plane

            // query_bvh_frustum_from_objects.rejection_count++;

            return false;
        }

        // TODO under current scheme we can reserve 6 bit in what we push onto the stack for plane mask (allows us to skip some planar tests)
    }

    return true;
}

/**
 * Most of the data is in flat continuous array for cache coherence
 * @param {number[]} destination
 * @param {number} destination_offset
 * @param {BinaryUint32BVH} bvh
 * @param {number[]|Uint8ClampedArray} source_data
 * @param {number[]|Float32Array|Float64Array} planes
 * @param {number[]|Float32Array|Float64Array} points
 * @returns {number} number of items added to the destination, non-negative integer
 */
export function query_bvh_frustum_from_texture(
    destination,
    destination_offset,
    bvh,
    source_data,
    planes, points
) {
    let result = 0;

    const binary_node_count = bvh.binary_node_count;

    if (binary_node_count <= 0) {
        // this should not happen
        return 0;
    }

    /**
     *
     * @type {number}
     */
    const stack_top = stack.pointer++;

    /**
     * After performing empirical tests, stack-based depth-first traversal turns out faster than using a queue
     * @type {number}
     */
    stack[stack_top] = 0;

    const last_valid_index = binary_node_count + bvh.leaf_node_count;

    const float32 = bvh.float32;
    const uint32 = bvh.uint32;

    do {
        stack.pointer--;

        // query_bvh_frustum_from_objects.iteration_count++;
        const node_index = stack[stack.pointer];

        if (node_index < binary_node_count) {
            // is intermediate node
            const node_address = node_index * BVH_BINARY_NODE_SIZE;

            if (!frustum_check(float32, node_address, planes)) {
                continue;
            }

            const left_index = (node_index << 1) + 1;
            const right_index = left_index + 1;

            // left node ends up on top of the stack, which aligns with the desired access sequence
            if (right_index < last_valid_index) {
                stack[stack.pointer++] = right_index;
                // micro-optimization, since we know that right node is valid and left appears before that, left is valid too
                stack[stack.pointer++] = left_index;
            } else if (left_index < last_valid_index) {
                stack[stack.pointer++] = left_index;
            }


        } else {
            // leaf node
            const leaf_index = node_index - binary_node_count;

            const node_address = leaf_index * BVH_LEAF_NODE_SIZE + binary_node_count * BVH_BINARY_NODE_SIZE;

            const light_descriptor = uint32[node_address + 6];

            const light_type = light_descriptor & 3;

            // mask out type bits
            const light_address = light_descriptor & 0xFFFFFFF8;

            if (light_type === 0) {
                // Point light

                const light_x = source_data[light_address];
                const light_y = source_data[light_address + 1];
                const light_z = source_data[light_address + 2];

                const radius = source_data[light_address + 3];

                if (!point_light_inside_volume(light_x, light_y, light_z, radius, points, planes)) {
                    // cull
                    continue;
                }

            } else if (light_type === 3) {
                // decal

                if (!frustum_check(float32, node_address, planes)) {
                    continue;
                }

            } else {
                throw new Error(`Unsupported light type ${light_type}`);
            }

            // write to output
            destination[destination_offset + result] = light_descriptor;

            result++;
        }

    } while (stack.pointer > stack_top)

    return result;
}
