//

import { assert } from "../../../../assert.js";
import { BitSet } from "../../../../binary/BitSet.js";
import { typed_array_copy } from "../../../../collection/array/typed/typed_array_copy.js";
import { compute_bounding_simplex_3d } from "../compute_bounding_simplex_3d.js";
import { TetrahedralMesh } from "../TetrahedralMesh.js";
import { Cavity } from "./Cavity.js";
import { fill_in_a_cavity } from "./fill_in_a_cavity.js";
import { tetrahedral_mesh_compute_cavity } from "./tetrahedral_mesh_compute_cavity.js";
import {
    tetrahedral_mesh_walk_towards_containing_tetrahedron
} from "./tetrahedral_mesh_walk_towards_containing_tetrahedron.js";


/**
 * @see https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm
 * @see [1] "One machine, one minute, three billion tetrahedra" by Célestin Marot et al
 * @param {TetrahedralMesh} mesh
 * @param {number[]} input serialized set of 3d points (x,y,z)
 * @param {number} [n] number of points in the input
 * @returns {boolean} whether mesh was created or not. Returns false on failure
 */
export function compute_delaunay_tetrahedral_mesh(mesh, input, n = input.length / 3) {
    assert.notNull(mesh, 'mesh');
    assert.defined(mesh, 'mesh');
    assert.equal(mesh.isTetrahedralMesh, true, 'mesh.isTetrahedralMesh !== true');

    assert.isNonNegativeInteger(n, 'n');

    if (n < 4) {
        // no mesh can be created, too few points
        return false;
    }

    /**
     * According to [1] number of tetrahedra is around 6 times greater than number of input vertices, we use that fact to pre-allocate memory
     * From the book of Cheng et al. 'Delaunay Mesh Generation' A triangulation of n vertices in R^3 has at most `ntet=(n^2 − 3n − 2)/2` tetrahedra,
     * at most nedg=(n^2 − n)/2` edges (this is simply the 2-combination of n)
     * and at most `ntri=n^2 − 3n = 2*ntet+2` triangles
     *
     * The +7 is for the initial "super" tetrahedron and 6 tetras for intermediate triangulation
     */

    // start with a fairly low capacity for best-case scenario, rely on internal resizing after that

    mesh.ensureCapacity(n + 1);

    const CAVITY = new Cavity();

    // TODO apply spatial sort to point, to get better cache locality

    const build_flags = BitSet.fixedSize(n);

    const points = new Float32Array((n + 4) * 3);

    typed_array_copy(input,points);

    /*
     create bounding volume tetrahedron over input set
     */
    compute_bounding_simplex_3d(points, n * 3, points, n, 10);

    const super_tet = mesh.allocate();

    mesh.setVertexIndex(super_tet, 0, n);
    mesh.setVertexIndex(super_tet, 1, n + 1);
    mesh.setVertexIndex(super_tet, 2, n + 2);
    mesh.setVertexIndex(super_tet, 3, n + 3);

    // debug_validate_mesh(mesh, points);

    // add all points to the mesh
    let current_tet = 0;

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

        current_tet = tetrahedral_mesh_walk_towards_containing_tetrahedron(mesh, points, current_tet, i);

        tetrahedral_mesh_compute_cavity(mesh, points, build_flags, CAVITY, current_tet, i);

        current_tet = fill_in_a_cavity(mesh, points, build_flags, CAVITY);

    }

    // remove tetras formed by with the super bounding volume
    mesh.removeTetrasConnectedToPoints(n, n + 3);


    // debug_validate_mesh(mesh, points);

    return true;
}

