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


/**
 * Special case of tetrahedral mesh generation using a regular 3d grid, useful for generating light probe volumes
 * This is a special case of tetrahedral tessellation, we can handle it much easier due to regular nature of the point distribution
 * Note that if resolution in every dimension should be >= 2
 * @param {TetrahedralMesh} mesh
 * @param {number[]|Float32Array|Float64Array} points point data is written here
 * @param {AABB3} bounds
 * @param {number} resolution_x how many points to generate on the grid in X axis
 * @param {number} resolution_y how many points to generate on the grid in Y axis
 * @param {number} resolution_z how many points to generate on the grid in Y axis
 * @returns {number} number of added points
 */
export function tetrahedral_mesh_build_from_grid(
    mesh,
    points,
    bounds,
    resolution_x = 2, resolution_y = 2, resolution_z = 2,
) {

    assert.isNonNegativeInteger(resolution_x, 'resolution_x');
    assert.isNonNegativeInteger(resolution_y, 'resolution_y');
    assert.isNonNegativeInteger(resolution_z, 'resolution_z');

    assert.defined(mesh, 'mesh');

    assert.defined(bounds, 'bounds');
    assert.notNull(bounds, 'bounds');
    assert.equal(bounds.isAABB3, true, 'bounds.isAABB3 !== true');

    assert.defined(points, 'points');
    assert.notNull(points, 'points');
    assert.isArrayLike(points, 'points');

    mesh.clear();

    const TETRAHEDRONS_PER_VOXEL = 6;

    const voxel_resolution_x = resolution_x - 1;
    const voxel_resolution_y = resolution_y - 1;
    const voxel_resolution_z = resolution_z - 1;

    const tetrahedron_count = voxel_resolution_x * voxel_resolution_y * voxel_resolution_z * TETRAHEDRONS_PER_VOXEL;

    // we know how many tetrahedrons will be created, so we can avoid some re-allocations by ensuring there's enough capacity
    mesh.ensureCapacity(tetrahedron_count);

    const bounds_extents_x = bounds.getExtentsX();
    const bounds_extents_y = bounds.getExtentsY();
    const bounds_extents_z = bounds.getExtentsZ();

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @return {number}
     */
    function vertex_position_to_index(x, y, z) {

        return z * resolution_y * resolution_x + y * resolution_x + x;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @param {number} index
     * @return {number}
     */
    function voxel_position_to_tetrahedral_index(x, y, z, index) {

        const voxel_index = z * voxel_resolution_y * voxel_resolution_x + y * voxel_resolution_x + x;

        return voxel_index * TETRAHEDRONS_PER_VOXEL + index;

    }

    // generate points
    for (let z = 0; z < resolution_z; z++) {
        for (let y = 0; y < resolution_y; y++) {
            for (let x = 0; x < resolution_x; x++) {

                const index = vertex_position_to_index(x, y, z);

                const index3 = index * 3;

                const x_norm = x / (resolution_x - 1);
                const y_norm = y / (resolution_y - 1);
                const z_norm = z / (resolution_z - 1);

                points[index3] = bounds.x0 + x_norm * bounds_extents_x;
                points[index3 + 1] = bounds.y0 + y_norm * bounds_extents_y;
                points[index3 + 2] = bounds.z0 + z_norm * bounds_extents_z;

            }
        }
    }

    /**
     *
     * @param {number} v0
     * @param {number} v1
     * @param {number} v2
     * @param {number} v3
     * @return {number}
     */
    function make_cell(v0, v1, v2, v3) {

        const tet_0 = mesh.allocate();

        mesh.setVertexIndex(tet_0, 0, v0);
        mesh.setVertexIndex(tet_0, 1, v1);
        mesh.setVertexIndex(tet_0, 2, v2);
        mesh.setVertexIndex(tet_0, 3, v3);

        return tet_0;
    }

    // NOTE: it's possible to tessellate each box into 5 tetrahedrons instead of 6, but orientation becomes inconsistent if we do that
    // reference http://www.iue.tuwien.ac.at/phd/wessner/node32.html
    for (let z = 1; z < resolution_z; z++) {
        for (let y = 1; y < resolution_y; y++) {
            for (let x = 1; x < resolution_x; x++) {

                const A = vertex_position_to_index(x - 1, y - 1, z - 1);
                const B = vertex_position_to_index(x - 1, y - 1, z);
                const C = vertex_position_to_index(x, y - 1, z);
                const D = vertex_position_to_index(x, y - 1, z - 1);
                const E = vertex_position_to_index(x - 1, y, z - 1);
                const F = vertex_position_to_index(x - 1, y, z);
                const G = vertex_position_to_index(x, y, z);
                const H = vertex_position_to_index(x, y, z - 1);

                // Tessellate the grid volume into six tetrahedra per grid cell.

                // prism 1
                const tet_0 = make_cell(B, A, D, H); // purple
                const tet_1 = make_cell(B, A, H, E); // blue
                const tet_2 = make_cell(H, B, E, F); // red

                // prism 2
                const tet_3 = make_cell(C, B, D, H); // orange
                const tet_4 = make_cell(C, B, H, G); // yellow
                const tet_5 = make_cell(B, H, G, F); // green

                // link newly created tetrahedrons

                // purple
                mesh.setNeighbour(tet_0, 1, (tet_3 << 2) | 0); // A - orange C
                mesh.setNeighbour(tet_0, 2, (tet_1 << 2) | 3); // D - blue E

                // blue
                mesh.setNeighbour(tet_1, 1, (tet_2 << 2) | 3); // A - red F
                mesh.setNeighbour(tet_1, 3, (tet_0 << 2) | 2); // E - purple D

                // red
                mesh.setNeighbour(tet_2, 2, (tet_5 << 2) | 2); // E - green G
                mesh.setNeighbour(tet_2, 3, (tet_1 << 2) | 1); // F - blue A

                // orange
                mesh.setNeighbour(tet_3, 0, (tet_0 << 2) | 1); // C - purple A
                mesh.setNeighbour(tet_3, 2, (tet_4 << 2) | 3); // D - yellow G

                // yellow
                mesh.setNeighbour(tet_4, 0, (tet_5 << 2) | 3); // C - green F
                mesh.setNeighbour(tet_4, 3, (tet_3 << 2) | 2); // G - orange D

                // green
                mesh.setNeighbour(tet_5, 2, (tet_2 << 2) | 2); // G - red E
                mesh.setNeighbour(tet_5, 3, (tet_4 << 2) | 0); // F - yellow C

                // links to previous voxel

                // prev x
                if (x > 1) {
                    const tet_3_prev_x = voxel_position_to_tetrahedral_index(x - 2, y - 1, z - 1, 3);// prev x orange
                    const tet_4_prev_x = voxel_position_to_tetrahedral_index(x - 2, y - 1, z - 1, 4); // prev x yellow

                    // blue
                    mesh.setNeighbour(tet_1, 2, (tet_3_prev_x << 2) | 1); // H - prev x orange B
                    // orange prev x
                    mesh.setNeighbour(tet_3_prev_x, 1, (tet_1 << 2) | 2); // B - blue H

                    // red
                    mesh.setNeighbour(tet_2, 0, (tet_4_prev_x << 2) | 1); // H - prev x yellow B
                    // yellow prev x
                    mesh.setNeighbour(tet_4_prev_x, 1, (tet_2 << 2) | 0); // B - red H
                }

                // prev y
                if (y > 1) {
                    const tet_2_prev_y = voxel_position_to_tetrahedral_index(x - 1, y - 2, z - 1, 2); // prev y red
                    const tet_5_prev_y = voxel_position_to_tetrahedral_index(x - 1, y - 2, z - 1, 5); // prev y green

                    // purple
                    mesh.setNeighbour(tet_0, 3, (tet_2_prev_y << 2) | 1); // H - prev y red B
                    // prev y red
                    mesh.setNeighbour(tet_2_prev_y, 1, (tet_0 << 2) | 3); // B - blue H

                    // orange
                    mesh.setNeighbour(tet_3, 3, (tet_5_prev_y << 2) | 0); // H - prev y green B
                    // prev y green
                    mesh.setNeighbour(tet_5_prev_y, 0, (tet_3 << 2) | 3); // B - orange H
                }

                if (z > 1) {
                    const tet_4_prev_z = voxel_position_to_tetrahedral_index(x - 1, y - 1, z - 2, 4); // prev z yellow
                    const tet_5_prev_z = voxel_position_to_tetrahedral_index(x - 1, y - 1, z - 2, 5); // prev z green

                    // purple
                    mesh.setNeighbour(tet_0, 0, (tet_4_prev_z << 2) | 2); // B - prev z yellow H
                    // prev z yellow
                    mesh.setNeighbour(tet_4_prev_z, 2, (tet_0 << 2) | 0); // H - purple B

                    // blue
                    mesh.setNeighbour(tet_1, 0, (tet_5_prev_z << 2) | 1); // B - prev z green H
                    // prev z green
                    mesh.setNeighbour(tet_5_prev_z, 1, (tet_1 << 2) | 0); // H - blue B
                }

            }

        }

    }

    // number of points added
    return resolution_x * resolution_y * resolution_z;
}