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

const CAME_FROM_TYPE_PARENT = 0;
const CAME_FROM_TYPE_CHILD_1 = 1;
const CAME_FROM_TYPE_CHILD_2 = 2;

/**
 * Traverses the tree bottom-up, updating bounds as it goes
 * @param {BVH} bvh
 * @param {number} root
 * @returns {void}
 */
export function ebvh_update_hierarchy_bounds(bvh, root = bvh.root) {
    assert.isNonNegativeInteger(root, 'root');

    if (root === NULL_NODE) {
        // special case
        return;
    }

    if (bvh.node_is_leaf(root)) {
        // special case
        return;
    }

    // we traverse depth-first using stack-less scheme
    let came_from_node = root;

    let came_from_type = CAME_FROM_TYPE_PARENT;

    let node = bvh.node_get_child1(came_from_node);
    if (node === NULL_NODE) {
        node = bvh.node_get_child2(came_from_node);
    }

    if (node === NULL_NODE) {
        // no children, done
        return;
    }

    // do a stackless traversal
    while (true) {


        if (
            came_from_type === CAME_FROM_TYPE_CHILD_2
        ) {
            // moving up last time, lets calculate
            const child_1 = bvh.node_get_child1(node);
            const child_2 = bvh.node_get_child2(node);

            bvh.node_set_combined_aabb(node, child_1, child_2);
        }

        // only add to treelet when traversing up, not down

        if (bvh.node_is_leaf(node)) {

            assert.equal(came_from_type, CAME_FROM_TYPE_PARENT);

            const parent = came_from_node;
            const parent_child_1 = bvh.node_get_child1(parent);

            if (parent_child_1 === node) {
                came_from_type = CAME_FROM_TYPE_CHILD_1;
            } else {
                came_from_type = CAME_FROM_TYPE_CHILD_2;
            }

            came_from_node = node;

            node = parent;
        } else if (came_from_type === CAME_FROM_TYPE_CHILD_2) {
            // finishing traversal of this branch
            came_from_node = node;
            const parent = bvh.node_get_parent(node);

            if (bvh.node_get_child1(parent) === node) {
                came_from_type = CAME_FROM_TYPE_CHILD_1;
            } else {
                came_from_type = CAME_FROM_TYPE_CHILD_2;
            }

            if (node === root) {
                // traversed all the way back up
                break;
            }

            node = parent;

        } else if (came_from_type === CAME_FROM_TYPE_CHILD_1) {
            // traversing up from left child
            const child2 = bvh.node_get_child2(node);

            came_from_node = node;

            if (child2 === NULL_NODE) {
                // no right child, finish this branch
                came_from_type = CAME_FROM_TYPE_CHILD_2; // indicate end

                node = bvh.node_get_parent(node);
            } else {
                came_from_type = CAME_FROM_TYPE_PARENT;
                node = child2;
            }

        } else if (came_from_type === CAME_FROM_TYPE_PARENT) {
            const child1 = bvh.node_get_child1(node);
            const child2 = bvh.node_get_child2(node);

            if (child1 !== NULL_NODE) {
                came_from_type = CAME_FROM_TYPE_PARENT;
                came_from_node = node;
                node = child1;
            } else if (child2 !== NULL_NODE) {
                came_from_type = CAME_FROM_TYPE_PARENT;
                came_from_node = node;
                node = child2;
            } else {
                // this should not happen, as this would mean that node is empty and completely pointless
                // we remove the node and traverse up


                const parent_child1 = bvh.node_get_child1(came_from_node);

                bvh.release_node(node);

                if (parent_child1 === node) {
                    // we are left child
                    bvh.node_set_child1(came_from_node, NULL_NODE);
                    came_from_node = NULL_NODE;
                    came_from_type = CAME_FROM_TYPE_CHILD_1;
                } else {
                    bvh.node_set_child2(came_from_node, NULL_NODE);
                    came_from_node = NULL_NODE;
                    came_from_type = CAME_FROM_TYPE_CHILD_2;
                }

                node = came_from_node;
            }


        }

    }
}