import { DataTexture, NearestFilter, RedIntegerFormat, RepeatWrapping, UnsignedIntType } from "three";

/**
 * Hilbert-curve index lookup texture (ported from Intel's XeGTAO).
 *
 * Each texel holds the Hilbert-curve index of its (x, y) position. Sampling it gives a spatially
 * well-distributed permutation index which, fed into a low-discrepancy sequence (Marty R2), yields a
 * blue-noise-like per-pixel dither. Stored as a single-channel unsigned-integer texture (R32UI), so
 * it is read with a `usampler2D` + `texelFetch`.
 *
 * @param {number} size edge length in texels; must be a power of two
 * @returns {DataTexture}
 */
export function generateHilbertNoiseTexture(size = 64) {
    const data = new Uint32Array(size * size);

    for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
            data[y * size + x] = hilbert_index_r2(x, y, size);
        }
    }

    const texture = new DataTexture(data, size, size);

    texture.type = UnsignedIntType;
    texture.format = RedIntegerFormat;
    texture.internalFormat = "R32UI";

    texture.magFilter = NearestFilter;
    texture.minFilter = NearestFilter;

    texture.wrapS = RepeatWrapping;
    texture.wrapT = RepeatWrapping;

    texture.flipY = false;
    texture.generateMipmaps = false;
    texture.unpackAlignment = 4;

    texture.needsUpdate = true;

    return texture;
}

/**
 * @param {number} x
 * @param {number} y
 * @param {number} width must be a power of two
 * @returns {number}
 */
function hilbert_index_r2(x, y, width) {
    let index = 0;

    let posX = x;
    let posY = y;

    for (let curLevel = width >>> 1; curLevel >= 1; curLevel >>>= 1) {
        const regionX = (posX & curLevel) > 0 ? 1 : 0;
        const regionY = (posY & curLevel) > 0 ? 1 : 0;

        index += curLevel * curLevel * ((3 * regionX) ^ regionY);

        if (regionY === 0) {

            if (regionX === 1) {
                posX = (width - 1) - posX;
                posY = (width - 1) - posY;
            }

            // swap
            const temp = posX;

            posX = posY;
            posY = temp;
        }
    }

    return index;
}
