import { query_bvh_frustum_from_texture } from "./query/query_bvh_frustum_from_texture.js";
import { compute_light_data_hash_0 } from "./cluster/compute_light_data_hash_0.js";
import { testClusterEquality } from "./testClusterEquality.js";

const LOOKUP_CACHE_SIZE = 1024; // must be power of two
/**
 * Using continuous buffer for related attributes to get better cache utilization
 * @type {ArrayBuffer}
 * @private
 */
const _scratch_array_buffer = new ArrayBuffer(
    24 * 4
    + 24 * 4
    + LOOKUP_CACHE_SIZE * 4
);
/**
 *
 * @type {number[]|Float32Array|Float64Array}
 */
export const scratch_corners = new Float32Array(_scratch_array_buffer, 0, 24);
/**
 * Linear access data structure with parameters of each plane of the frustum
 * 6 planes, 4 values per plane
 * Layout: [normal.x, normal.y, normal.z, constant]
 * @type {number[]|Float32Array|Float64Array}
 */
export const scratch_frustum_planes = new Float32Array(_scratch_array_buffer, 24 * 4, 24);
/**
 * Cache used to identify duplicate cells. Helps reduce memory usage and just point to equivalent existing cells instead of allocating new ones.
 * @type {Uint16Array|Uint32Array}
 */
export const LOOKUP_CACHE = new Uint32Array(_scratch_array_buffer, (24 + 24) * 4, LOOKUP_CACHE_SIZE);
/**
 *
 * @type {number[]}
 */
const scratch_light_nodes = [];

/**
 *
 * @param {number} tile_texture_data_offset
 * @param {BinaryUint32BVH} bvh
 * @param {number} lookup_address_offset
 * @param {number[]|ArrayLike<number>|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array} lookup_data
 * @param {number[]|ArrayLike<number>|Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array} cluster_data
 * @param {number[]|ArrayLike<number>|Float32Array} source_data Light/Decal source information is stored in here, such as position, color etc.
 * @return {number}
 */
export function assign_cluster(
    tile_texture_data_offset,
    bvh,
    lookup_address_offset,
    lookup_data,
    cluster_data,
    source_data
) {

    // at this point we have a fully constructed cluster, we can query lights within the cluster
    const match_count = query_bvh_frustum_from_texture(
        scratch_light_nodes,
        0,
        bvh,
        source_data,
        scratch_frustum_planes,
        scratch_corners
    );

    // construct tile data

    // write number of light entries
    cluster_data[tile_texture_data_offset + 1] = match_count;

    if (match_count === 0) {
        // don't waste time on assignment, there are no lights in the cluster
        return 0;
    }

    /*
     We can sort lights by index to get higher cache utilization, but it turns out quite costly.
     Through experimentation, sorting was taking around 20% of the whole assignment time
     while improving number of cache hits by around 40% (8000 without sorting -> 11000 with sorting)
     */
    const cluster_data_hash = compute_light_data_hash_0(scratch_light_nodes, match_count) & (LOOKUP_CACHE_SIZE - 1);

    const existing_lookup_address = LOOKUP_CACHE[cluster_data_hash];

    if (existing_lookup_address !== 0xFFFFFFFF) {
        // found a hash match, validate equality
        const existing_lookup_entry_match = testClusterEquality(
            scratch_light_nodes,
            match_count,
            lookup_data,
            existing_lookup_address
        );

        if (existing_lookup_entry_match) {
            cluster_data[tile_texture_data_offset] = existing_lookup_address;

            //hash_reuse_count++;

            return 0;
        }
    } else {
        // no hash entry exists, create one
        LOOKUP_CACHE[cluster_data_hash] = lookup_address_offset;
    }

    // if we get to this point, that means no hash match was found, or equality check was failed

    // write address of the lookup table
    cluster_data[tile_texture_data_offset] = lookup_address_offset;

    for (let i = 0; i < match_count; i++) {
        /**
         *
         * @type {number}
         */
        const light_descriptor = scratch_light_nodes[i];

        // construct lookup table
        lookup_data[lookup_address_offset + i] = light_descriptor;
    }

    return match_count;
}
