import { EPSILON } from "../../../math/EPSILON.js";
import { epsilonEquals } from "../../../math/epsilonEquals.js";
import v3_morton_encode from "../morton/v3_morton_encode.js";

/**
 *
 * @param {TopoVertex} a
 * @param {TopoVertex} b
 * @param {number} tolerance
 */
function isPositionEquivalent(a, b, tolerance) {
    return epsilonEquals(b.x, a.x, tolerance)
        && epsilonEquals(b.y, a.y, tolerance)
        && epsilonEquals(b.z, a.z, tolerance)
        ;
}

/**
 *
 * @param {Map<TopoVertex,TopoVertex[]>} result
 * @param {TopoVertex} a
 * @param {TopoVertex} b
 */
function recordDuplicate(result, a, b) {
    let vertices = result.get(a);

    if (vertices === undefined) {
        vertices = [];

        result.set(a, vertices);
    }

    vertices.push(b);
}

/**
 *
 * @param {TopoMesh} mesh
 * @param {AABB3} aabb
 * @param {number} tolerance
 * @returns {Map<TopoVertex, TopoVertex[]>}
 */
export function computeTopoMeshVertexDuplicates(mesh, aabb, tolerance = EPSILON) {
    const vertices = mesh.vertices;

    const n = vertices.length;

    // build spatial index for faster duplicate search
    const spatial_hash = [];

    /**
     *
     * @type {Map<TopoVertex, TopoVertex[]>}
     */
    const result = new Map();

    let i, j;

    const x0 = aabb.x0;
    const y0 = aabb.y0;
    const z0 = aabb.z0;

    // 10 bit quantization
    const e_x = 1023 / aabb.getExtentsX();
    const e_y = 1023 / aabb.getExtentsY();
    const e_z = 1023 / aabb.getExtentsZ();

    //
    for (i = 0; i < n; i++) {
        const vertex_a = vertices[i];

        const morton_code = v3_morton_encode(
            Math.round((vertex_a.x - x0) * e_x),
            Math.round((vertex_a.y - y0) * e_y),
            Math.round((vertex_a.z - z0) * e_z)
        );

        let spatial_bucket = spatial_hash[morton_code];

        if (spatial_bucket === undefined) {

            spatial_bucket = [];
            spatial_hash[morton_code] = spatial_bucket;

        } else {

            // look at others in the bucket
            const bucket_size = spatial_bucket.length;

            for (j = 0; j < bucket_size; j++) {
                /**
                 * @type {TopoVertex}
                 */
                const vertex_b = spatial_bucket[j];

                if (isPositionEquivalent(vertex_a, vertex_b, tolerance)) {
                    // same coordinates
                    recordDuplicate(result, vertex_a, vertex_b);
                    recordDuplicate(result, vertex_b, vertex_a);
                }
            }
        }

        spatial_bucket.push(vertex_a);
    }

    return result;
}
