import { assert } from "../../../../assert.js";
import { array_copy } from "../../../../collection/array/array_copy.js";
import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";

/**
 *
 * @param {TetrahedralMesh} mesh
 * @param {ArrayLike<number>|number[]|Float32Array} points
 * @param {BitSet} visited_flags
 * @param {Cavity} cavity
 * @returns {number} new current tet
 */
export function fill_in_a_cavity(mesh, points, visited_flags, cavity) {

    let clength = cavity.__deleted_size; // cavity size
    const blength = cavity.__boundary_size; // boundary size

    assert.equal(blength & 1, 0, 'there is always a pair number of triangle on the boundary. N points, 2N triangle, 3N edges');

    /*
    Re-use tets that would be deleted, if there are not enough dead tets - allocate new ones as necessary
     */
    const deleted_tets = cavity.__deleted;
    if (blength > clength) {

        for (let i = clength; i < blength; i++) {
            // allocate tets for the cavity filling, remember them inside the "deleted" set for now
            deleted_tets[i] = mesh.allocate();
        }

        clength = blength;
    }

    const boundary = cavity.__boundary;
    const start = clength - blength;

    for (let i = 0; i < blength; i++) {
        // go over the faces in the cavity boundary and matching tet that will be filling the space

        const current_tet_index = deleted_tets[i + start];

        const i5 = i * 5;

        // write filling tet's corners
        mesh.setVertexIndex(current_tet_index, 0, boundary[i5]); // "ball"/cavity center point is written here
        mesh.setVertexIndex(current_tet_index, 1, boundary[i5 + 1]);
        mesh.setVertexIndex(current_tet_index, 2, boundary[i5 + 2]);
        mesh.setVertexIndex(current_tet_index, 3, boundary[i5 + 3]);

        const encoded_boundary_tet = boundary[i5 + 4];

        mesh.setNeighbour(current_tet_index, 0, encoded_boundary_tet); // connect opposing neighbour

        const encoded_current_tet = current_tet_index << 2;

        if (encoded_boundary_tet !== INVALID_NEIGHBOUR) {
            // update neighbour
            mesh.setNeighbour(encoded_boundary_tet >> 2, encoded_boundary_tet & 3, encoded_current_tet);
        }

        // boundary[i5 + 4] = encoded_current_tet;

        visited_flags.set(current_tet_index, false); // clear flag
    }

    computeAdjecencies(mesh, cavity, start, blength);


    for (let i = 0; i < start; i++) {
        // cleanup any orphaned tets that we didn't re-use
        mesh.delete(deleted_tets[i]);
    }

    // debug_validate_mesh(mesh, points);

    cavity.__boundary_size = 0;
    cavity.__deleted_size = 0;

    /*
    Pick a tet from the created ball, it will generally be a good guess for next point
     */
    return deleted_tets[start];

}


/**
 * filling back the cavity
 * N+2 point on the surface of the cavity
 * 2N triangle on the surface of the cavity, x3 (4*0.5+1) data = 6N+9 uint64_t
 * => enough place for the 3N edge x2 data = 6N uint64_t
 * @param {TetrahedralMesh} mesh
 * @param {Cavity} cavity
 * @param {number} start
 * @param {number} boundary_length
 */
function computeAdjecencies(mesh, cavity, start, boundary_length) {
    const boundary = cavity.__boundary;

    let tlength = 0;

    const Tmp = boundary; // we know there is enough space...

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

        const cur_tet = cavity.__deleted[start + i];

        // pointer to the position of Node[0] in the Tmp array

        for (let j = 0; j < 3; j++) {

            // define the edge by the minimum vertex and the other
            const index_0 = (j + 1) % 3 + 1;
            const index_1 = (j + 2) % 3 + 1;

            const n0 = mesh.getVertexIndex(cur_tet, index_0);
            const n1 = mesh.getVertexIndex(cur_tet, index_1);

            const current_tet_neighbour_id = j + 1;
            const encoded_current_tet = (cur_tet << 2) | (current_tet_neighbour_id & 3);

            // linear searching/pushing into Tmp
            let k = 0;
            for (; k < tlength; k++) {
                // this is the only nested loop... the one that cost it all

                if (Tmp[k * 3] === n0 && Tmp[k * 3 + 1] === n1)
                    // found a match, stop here
                    break;
            }

            if (k === tlength) {
                // we did not find it
                const offset = tlength * 3;

                // flip the edge order and store it. Neighbouring tet will have edge order reversed
                Tmp[offset] = n1;
                Tmp[offset + 1] = n0;

                Tmp[offset + 2] = encoded_current_tet;

                tlength++;
            } else {
                // we found the neighbour !
                const encoded_neighbour = Tmp[k * 3 + 2];

                mesh.setNeighbour(cur_tet, current_tet_neighbour_id, encoded_neighbour);
                mesh.setNeighbour(encoded_neighbour >> 2, encoded_neighbour & 3, encoded_current_tet);

                // reduce search space, there can only be one neighbour pair, after it's found we can remove the entry from lookup table
                tlength--;

                if (k < tlength) {
                    // put the last entry in the one we just discovered
                    array_copy(Tmp, tlength * 3, Tmp, k * 3, 3);
                }
            }
        }
    }

    assert.equal(tlength, 0, 'Failed to compute adjacencies');

    // if (!validate_patched_ball_adjacencies(mesh, cavity.__deleted, start, boundary_length, console.error)) {
    //     debugger;
    // }
}
