import { vec3 } from "gl-matrix";
import { computeHashFloat } from "../../../../core/primitives/numbers/computeHashFloat.js";
import { GetNormal } from "./GetNormal.js";
import { GetPosition } from "./GetPosition.js";
import { GetTexCoord } from "./GetTexCoord.js";

/**
 *
 * @param {number} x
 * @param {number} y
 * @param {number} z
 * @returns {number}
 */
function HASH(x, y, z) {
    return (x * 73856093) ^ (y * 19349663) ^ (z * 83492791);
}

/**
 *
 * @param {number} x
 * @param {number} y
 * @param {number} z
 * @returns {number}
 */
function HASH_F(x, y, z) {
    return HASH(computeHashFloat(x), computeHashFloat(y), computeHashFloat(z));
}

/**
 * Sort comp and data based on comp.
 * comp2 and data2 are used as temporary storage.
 *
 * @param {Uint32Array} comp
 * @param {Int32Array} data
 * @param {Uint32Array} comp2
 * @param {Int32Array} data2
 * @param {number} n
 */
function radixsort_pair(comp, data, comp2, data2, n) {


    let _data_0 = data;
    let _data_1 = data2;

    let _comp_0 = comp;
    let _comp_1 = comp2;

    const data_size = 4; // 4 bytes

    const bins = new Uint32Array(257 * data_size);

    /* Count number of elements per bin. */
    for (let i = 0; i < n; i++) {

        for (let pass = 0; pass < data_size; pass++) {
            const shift = 8 * pass;

            const key = _comp_0[i];

            const index = pass * 257 + ((key >> shift) & 0xff) + 1;

            bins[index]++;

        }

    }

    /* Compute prefix sum to find position of each bin in the sorted array. */
    for (let pass = 0; pass < data_size; pass++) {
        for (let i = 2; i < 256; i++) {
            const offset = pass * 257 + i;
            bins[offset] += bins[offset - 1];
        }
    }

    let shift = 0;
    for (let pass = 0; pass < data_size; pass++, shift += 8) {
        /* Insert the elements in their correct location based on their bin. */
        for (let i = 0; i < n; i++) {
            const bin_index = pass * 257 + (_comp_0[i] >> shift) & 0xff;

            const pos = bins[bin_index]++;

            _comp_1[pos] = _comp_0[i];
            _data_1[pos] = _data_0[i];
        }

        /* Swap arrays. */
        let tmpdata = _data_0;
        _data_0 = _data_1;
        _data_1 = tmpdata;

        let tmpcomp = _comp_0;
        _comp_0 = _comp_1;
        _comp_1 = tmpcomp;

    }

}

/**
 *
 * @param {number[]|Int32Array} piTriList_in_and_out
 * @param {SMikkTSpaceContext} pContext
 * @param {number} iNrTrianglesIn
 * @returns {void}
 */
export function GenerateSharedVerticesIndexList(
    piTriList_in_and_out,
    pContext,
    iNrTrianglesIn
) {
    const numVertices = iNrTrianglesIn * 3;

    const _buffer = new ArrayBuffer(numVertices * 4 * 4);

    const hashes = new Uint32Array(_buffer, 0, numVertices);
    const indices = new Int32Array(_buffer, numVertices * 4, numVertices);
    const temp_hashes = new Uint32Array(_buffer, numVertices * 2 * 4, numVertices);
    const temp_indices = new Int32Array(_buffer, numVertices * 3 * 4, numVertices);

    const v = vec3.create();

    for (let i = 0; i < numVertices; i++) {
        const index = piTriList_in_and_out[i];

        GetPosition(v, 0, pContext, index);
        const hashP = HASH_F(v[0], v[1], v[2]);

        GetNormal(v, pContext, index);
        const hashN = HASH_F(v[0], v[1], v[2]);

        GetTexCoord(v, pContext, index);
        const hashT = HASH_F(v[0], v[1], v[2]);

        hashes[i] = HASH(hashP, hashN, hashT);
        indices[i] = i;
    }

    radixsort_pair(hashes, indices, temp_hashes, temp_indices, numVertices);

    // free(temp_hashes);
    // free(temp_indices);


    const vT = vec3.create();
    const vP = vec3.create();

    const vT2 = vec3.create();
    const vP2 = vec3.create();

    const vN = vec3.create();
    const vN2 = vec3.create();

    /*
     * Process blocks of vertices with the same hash.
     * Vertices in the block might still be separate, but we know for sure that
     * vertices in different blocks will never be identical.
     */
    let blockstart = 0;
    while (blockstart < numVertices) {
        /* Find end of this block (exclusive). */
        const hash = hashes[blockstart];
        let blockend = blockstart + 1;
        for (; blockend < numVertices; blockend++) {
            if (hashes[blockend] !== hash)
                break;
        }

        for (let i = blockstart; i < blockend; i++) {

            const index1 = piTriList_in_and_out[indices[i]];

            GetPosition(vP, 0, pContext, index1);
            GetNormal(vN, pContext, index1);
            GetTexCoord(vT, pContext, index1);

            for (let i2 = i + 1; i2 < blockend; i2++) {
                const index2 = piTriList_in_and_out[indices[i2]];

                if (index1 === index2) {
                    continue;
                }

                GetPosition(vP2, 0, pContext, index2);
                GetNormal(vN2, pContext, index2);
                GetTexCoord(vT2, pContext, index2);

                if (
                    vP[0] === vP2[0] && vP[1] === vP2[1] && vP[2] === vP2[2] &&
                    vN[0] === vN2[0] && vN[1] === vN2[1] && vN[2] === vN2[2] &&
                    vT[0] === vT2[0] && vT[1] === vT2[1] && vT[2] === vT2[2]
                ) {
                    piTriList_in_and_out[indices[i2]] = index1;
                    /* Once i2>i has been identified as a duplicate, we can stop since any
                     * i3>i2>i that is a duplicate of i (and therefore also i2) will also be
                     * compared to i2 and therefore be identified there anyways. */
                    break;
                }
            }
        }

        /* Advance to next block. */
        blockstart = blockend;
    }

    // free(hashes);
    // free(indices);
}
