import { assert } from "../../assert.js";
import { NULL_NODE } from "./BVH.js";
import { ebvh_nodes_sort_sah_local4 } from "./ebvh_nodes_sort_sah_local4.js";

/**
 * Given a set of leaves, build intermediate node hierarchy on top, all the way up to the root
 * Assumes nodes are spatially sorted, if not - no guarantees can be made about quality
 * NOTE: {@link unprocessed_nodes} will be modified during execution to save memory
 * @param {BVH} bvh
 * @param {number[]|Uint32Array} unprocessed_nodes contains nodes that need to be built into a hierarchy, these must not be present in the BVH hierarchy
 * @param {number} input_node_count
 * @param {number[]|Uint32Array} node_pool Contains node indices that can be used to build ancestry hierarchy, need to be pre-allocated before calling this method
 * @param {number} node_pool_offset
 * @param {number} [sah_optimization_level]
 * @param {number} [sah_optimization_bias] Adds an extra optimization run every X depth levels. Nodes closer to root are generally traversed more, so quality is significantly more important there. Plus, these levels have 1/2 of the nodes progressively, so it's much cheaper to optimize
 * @returns {number} new root
 */
export function ebvh_build_hierarchy(
    bvh,
    unprocessed_nodes,
    input_node_count,
    node_pool,
    node_pool_offset,
    sah_optimization_level = 1,
    sah_optimization_bias = 0.5
) {

    assert.isNonNegativeInteger(input_node_count, 'input_node_count');
    assert.isNonNegativeInteger(node_pool_offset, 'node_pool_offset');

    let used_index = node_pool_offset;

    // Assemble hierarchy
    let unprocessed_node_count = input_node_count;

    let current_construction_depth = 0;

    while (unprocessed_node_count > 1) {

        const sah_optimization_cycle_count = Math.floor(sah_optimization_level + sah_optimization_bias * current_construction_depth);

        // do long-distance swaps, starting high and coming down
        for (let power = 5; power > Math.max(5 - sah_optimization_cycle_count, 1); power--) {

            ebvh_nodes_sort_sah_local4(
                bvh,
                unprocessed_nodes,
                0,
                1 << power,
                unprocessed_node_count
            );

        }

        for (let i = 0; i < sah_optimization_cycle_count; i++) {

            // Sort intermediate nodes using small locality and SAH metric
            // Sorting progressively larger distance pairs yields best results, based on empirical tests (AG - 2024/12/28)
            const swap_distance = (i + 1) * 2;

            ebvh_nodes_sort_sah_local4(
                bvh,
                unprocessed_nodes,
                0,
                swap_distance,
                unprocessed_node_count
            );

        }

        let added_nodes = 0;
        let cursor = 0;

        while (cursor + 1 < unprocessed_node_count) {
            const child_2 = unprocessed_nodes[cursor++];
            const child_1 = unprocessed_nodes[cursor++];

            const parent = node_pool[used_index++];

            bvh.node_assign_children(parent, child_1, child_2);

            unprocessed_nodes[added_nodes++] = parent;
        }

        while (cursor < unprocessed_node_count) {
            // dangling nodes, push them onto the next level
            unprocessed_nodes[added_nodes++] = unprocessed_nodes[cursor++];
        }

        unprocessed_node_count = added_nodes;

        current_construction_depth++;
    }

    // set the root to have no parent
    bvh.node_set_parent(unprocessed_nodes[0], NULL_NODE);

    return unprocessed_nodes[0];
}