import { assert } from "../../assert.js";
import { array_quick_sort_by_lookup_uint } from "../../collection/array/array_quick_sort_by_lookup_uint.js";
import { typed_array_copy } from "../../collection/array/typed/typed_array_copy.js";
import { AABB3 } from "../../geom/3d/aabb/AABB3.js";
import { aabb3_from_v3_array } from "../../geom/3d/aabb/aabb3_from_v3_array.js";
import { aabb3_compute_from_triangle } from "../../geom/3d/triangle/aabb3_compute_from_triangle.js";
import { max2 } from "../../math/max2.js";
import { build_triangle_morton_codes } from "./build_triangle_morton_codes.js";
import { COLUMN_CHILD_1, COLUMN_HEIGHT, COLUMN_USER_DATA, ELEMENT_WORD_COUNT, NULL_NODE } from "./BVH.js";
import { ebvh_build_hierarchy } from "./ebvh_build_hierarchy.js";

/**
 * Build the BVH bottom-up using spatial hash sorting
 * Resulting BVH is produced very quickly, has perfect balance and is of decent quality
 * @param {BVH} bvh
 * @param {number[]|Uint32Array|Uint16Array} index_array
 * @param {number[]|Float32Array|Float64Array} position_array
 * @param {Uint32Array} [morton_codes]
 * @param {AABB3} [bounds] Bounding box of the entire geometry, if not supplied will be computed internally
 * @param {number} [quality] va value from 0 to infinity, controls how many optimization passes are to be done beyond morton sort
 * @returns {void}
 */
export function ebvh_build_for_geometry_morton(
    bvh,
    index_array,
    position_array,
    morton_codes = new Uint32Array(index_array.length / 3),
    bounds,
    quality = 0
) {
    assert.defined(bvh, 'bvh');
    assert.isObject(bvh, 'bvh');
    assert.equal(bvh.isBVH, true, 'bvh.isBVH !== true');

    assert.defined(index_array, 'index_array');
    assert.isArrayLike(index_array, 'index_array');

    assert.defined(position_array, 'position_array');
    assert.isArrayLike(position_array, 'position_array');

    assert.isArrayLike(morton_codes, 'morton_codes');

    // clear out existing BVH
    bvh.release_all();

    // allocate nodes
    const tri_count = index_array.length / 3;

    assert.greaterThanOrEqual(morton_codes.length, tri_count);

    const node_leaf_count = tri_count;
    const node_bin_count = max2(0, node_leaf_count - 1);

    const node_total_count = node_leaf_count + node_bin_count;

    // skip allocation calls, allocate exactly as many nodes as we need
    bvh.allocate_linear(node_total_count);

    // indices of triangles, but sorted by morton codes
    const sorted_triangle_order = new Uint32Array(tri_count);

    for (let i = 0; i < tri_count; i++) {
        sorted_triangle_order[i] = i;
    }

    let _geometry_bounds = bounds;

    if (_geometry_bounds === undefined) {
        // get bounds for the entire geometry
        _geometry_bounds = new AABB3();

        aabb3_from_v3_array(_geometry_bounds, position_array, position_array.length);
    } else {

        assert.greaterThanOrEqual(_geometry_bounds.width, 0, 'x extents');
        assert.greaterThanOrEqual(_geometry_bounds.height, 0, 'y extents');
        assert.greaterThanOrEqual(_geometry_bounds.depth, 0, 'z extents');

    }

    build_triangle_morton_codes(
        morton_codes,
        tri_count,
        index_array,
        position_array,
        _geometry_bounds.x0, _geometry_bounds.y0, _geometry_bounds.z0,
        _geometry_bounds.x1, _geometry_bounds.y1, _geometry_bounds.z1
    );

    // sort leaves by morton codes
    array_quick_sort_by_lookup_uint(sorted_triangle_order, morton_codes, 0, tri_count - 1);


    const bvh_float32 = bvh.__data_float32;
    const bvh_uint32 = bvh.__data_uint32;

    // assign leaves
    for (let i = 0; i < tri_count; i++) {
        const node_id = (node_total_count - 1) - i;

        const triangle_index = sorted_triangle_order[i];

        const triangle_index3 = triangle_index * 3;

        const a_index = index_array[triangle_index3];
        const b_index = index_array[triangle_index3 + 1];
        const c_index = index_array[triangle_index3 + 2];

        const node_address = ELEMENT_WORD_COUNT * node_id;

        aabb3_compute_from_triangle(
            bvh_float32, node_address,
            position_array, a_index, b_index, c_index
        );

        bvh_uint32[node_address + COLUMN_CHILD_1] = NULL_NODE; //mark node as a leaf
        bvh_uint32[node_address + COLUMN_USER_DATA] = triangle_index;
        bvh_uint32[node_address + COLUMN_HEIGHT] = 0;
    }


    const nodes = new Uint32Array(node_total_count);
    for (let i = 0; i < node_total_count; i++) {
        // store nodes in reverse order so that top-level nodes end up on top
        nodes[i] = (node_total_count - 1) - i;
    }

    // record newly generated nodes as "unprocessed"
    const unprocessed_nodes = new Uint32Array(tri_count);

    typed_array_copy(nodes, unprocessed_nodes);


    // assign root
    bvh.root = ebvh_build_hierarchy(bvh, unprocessed_nodes, tri_count, nodes, tri_count, quality);
}
