import { assert } from "../../../assert.js";
import { array_copy } from "../../../collection/array/array_copy.js";
import { SCRATCH_UINT32_TRAVERSAL_STACK } from "../../../collection/SCRATCH_UINT32_TRAVERSAL_STACK.js";
import { v3_distance_above_plane } from "../../../geom/vec3/v3_distance_above_plane.js";
import { NULL_NODE } from "../BVH.js";

const stack = SCRATCH_UINT32_TRAVERSAL_STACK;

const scratch_aabb = [];

const scratch_frustum = new Float32Array(24);

/**
 * @param {number[]} result
 * @param {number} result_offset
 * @param {number} plane_mask if bit is set to 0, that plane is not allowed to move
 * @param {BVH} bvh
 * @param {number[]|ArrayLike<number>|Float32Array} frustum
 * @returns {boolean} true iff planes were updated, false otherwise. If false is returned - result is not written
 */
export function compute_tight_near_far_clipping_planes(
    result,
    result_offset,
    plane_mask,
    bvh,
    frustum
) {
    assert.isNonNegativeInteger(result_offset, 'result_offset');
    assert.isNonNegativeInteger(plane_mask, 'plane_mask');

    // initialize result
    array_copy(frustum, 0, scratch_frustum, 0, 24);

    // set output planes to infinity so we can slowly bring them in
    for (let i = 0; i < 6; i++) {
        if (((plane_mask >> i) & 1) === 1) {
            scratch_frustum[i * 4 + 3] = Infinity;
        }
    }

    const root = bvh.root;

    if (root === NULL_NODE) {
        return false;
    }

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

    stack[stack_top] = root;

    let result_cursor = result_offset;

    // 0 is the nearest point along the normal and 1 is the furthest
    let x0,
        y0,
        z0,
        x1,
        y1,
        z1;

    node_loop: while (stack.pointer > stack_top) {
        stack.pointer--;

        /**
         *
         * @type {number}
         */
        const node = stack[stack.pointer];

        // test node against the ray
        bvh.node_get_aabb(node, scratch_aabb);

        const _x0 = scratch_aabb[0];
        const _y0 = scratch_aabb[1];
        const _z0 = scratch_aabb[2];
        const _x1 = scratch_aabb[3];
        const _y1 = scratch_aabb[4];
        const _z1 = scratch_aabb[5];

        // bitset of relaxed planes affected by this node
        // lets us discard intermediate nodes that would not affect any planes
        let collision_mask = 0;

        for (let plane_index = 0; plane_index < 6; plane_index++) {


            const plane_address = plane_index * 4;

            //read out plane
            const plane_normal_x = scratch_frustum[plane_address];
            const plane_normal_y = scratch_frustum[plane_address + 1];
            const plane_normal_z = scratch_frustum[plane_address + 2];
            const plane_offset = scratch_frustum[plane_address + 3];


            // construct nearest and farthest corners along the plane normal
            if (plane_normal_x > 0) {
                x0 = _x0;
                x1 = _x1;
            } else {
                x0 = _x1;
                x1 = _x0;
            }

            if (plane_normal_y > 0) {
                y0 = _y0;
                y1 = _y1;
            } else {
                y0 = _y1;
                y1 = _y0;
            }

            if (plane_normal_z > 0) {
                z0 = _z0;
                z1 = _z1;
            } else {
                z0 = _z1;
                z1 = _z0;
            }

            if (((plane_mask >> plane_index) & 1) === 1) {
                // plane is not fixed, we're done here

                const distance_above_plane = v3_distance_above_plane(
                    x0, y0, z0,
                    plane_normal_x, plane_normal_y, plane_normal_z, 0
                );

                if (distance_above_plane < plane_offset) {
                    collision_mask |= (1 << plane_index);
                }

            } else {

                // fixed plane
                const distance_above_plane = v3_distance_above_plane(
                    x1, y1, z1,
                    plane_normal_x, plane_normal_y, plane_normal_z, plane_offset
                );

                if (distance_above_plane < 0) {
                    // below plane, reject this branch
                    continue node_loop;
                }

            }
        }


        const node_is_leaf = bvh.node_is_leaf(node);

        if (!node_is_leaf) {

            if (collision_mask === 0) {
                // does not affect any planes, skip
                continue;
            }

            // inside
            // read in-order
            const child1 = bvh.node_get_child1(node);
            const child2 = bvh.node_get_child2(node);

            // write to stack in reverse order, so that fist child ends up being visited first
            stack[stack.pointer++] = child1;
            stack[stack.pointer++] = child2;

        } else {

            // leaf node

            // expand planes
            for (let i = 0; i < 6; i++) {
                if (((collision_mask >> i) & 1) === 1) {

                    const plane_address = i * 4;

                    //read out plane
                    const plane_normal_x = scratch_frustum[plane_address];
                    const plane_normal_y = scratch_frustum[plane_address + 1];
                    const plane_normal_z = scratch_frustum[plane_address + 2];

                    // get the nearest corner along the plane normal
                    if (plane_normal_x > 0) {
                        x0 = _x0;
                    } else {
                        x0 = _x1;
                    }

                    if (plane_normal_y > 0) {
                        y0 = _y0;
                    } else {
                        y0 = _y1;
                    }

                    if (plane_normal_z > 0) {
                        z0 = _z0;
                    } else {
                        z0 = _z1;
                    }

                    // move plane
                    scratch_frustum[plane_address + 3] = v3_distance_above_plane(
                        x0, y0, z0,
                        plane_normal_x, plane_normal_y, plane_normal_z, 0
                    );

                }
            }

        }
    }

    // TODO check if results are still at infinity

    array_copy(scratch_frustum, 0, result, result_offset, 24);

    return true;
}
