/***********************************************/
/*           Copyright (c) 2025 Belmu          */
/*             All Rights Reserved             */
/***********************************************/

#include "/include/voxels/common.glsl"

#define MAX_PROBING_ATTEMPTS_LOD1  16u
#define MAX_PROBING_ATTEMPTS_LOD4  8u
#define MAX_PROBING_ATTEMPTS_LOD16 8u

const uint FIBONACCI_PHI = 0x9e3779b9u;

// Bounds permitted by integers of 11, 10 and 11 bits respectively.
const ivec3 lowerSignedBound   = ivec3(-1024, -512, -1024);
const ivec3 upperSignedBound   = ivec3( 1023,  511,  1023);
const ivec3 upperUnsignedBound = ivec3( 2047, 1023,  2047);

ivec3 cameraVoxelPosition = ivec3(floor(cameraPosition));

bool isInVoxelBounds(ivec3 position) {
    position -= cameraVoxelPosition;
    return clamp(position, lowerSignedBound, upperSignedBound) == position;
}

uint packPosition(ivec3 position, int multiple) {
    position -= cameraVoxelPosition & -multiple;
    position &= upperUnsignedBound;
    return position.x | position.y << 11u | position.z << 21u;
}

int unpackId(uint packedData) {
    return int(packedData >> 17u & 0xfffu);
}

vec3 unpackTint(uint packedData) {
    return (uvec3(packedData) >> uvec3(0, 6, 11) & uvec3(63, 31, 63)) / vec3(63.0, 31.0, 63.0);
}

uint moduloArraySize(uint index, uint size) {
    return index & (size - 1u);
}

void incrementHash(inout uint hash, uint size) {
    hash = moduloArraySize(hash + 1u, size);
}

uint knuthHash(uint x) {
    return x * FIBONACCI_PHI;
}

uint tridimensionalHash(ivec3 position) {
    uint hash = knuthHash(       position.x);
         hash = knuthHash(hash + position.y);
         hash = knuthHash(hash + position.z);
    return hash;
}

void writeVoxel(ivec3 position, vec3 tint, uint id, uint facePriority, int tileSize, ivec2 tileIndex) {
    uint hash = moduloArraySize(tridimensionalHash(position), VOXEL_ARRAY_SIZE);

    //vec3 color = vec3((uvec3(hash) >> uvec3(0, 8, 16) & 255u) / 255.0);

    uvec3 packedTint = uvec3(round(tint * vec3(63.0, 31.0, 63.0))) << uvec3(0, 6, 11);

    uint packedPosition = packPosition(position, 1);
    uint packedData     = packedTint.r | packedTint.g | packedTint.b | id << 17u | facePriority << 29u;
    uint packedTileData = uint(log2(tileSize) - 4) | tileIndex.x << 4u | tileIndex.y << 16u | facePriority << 28u;

    for (uint attempt = 0; attempt < MAX_PROBING_ATTEMPTS_LOD1; attempt++, incrementHash(hash, VOXEL_ARRAY_SIZE)) {
        uint previous = atomicCompSwap(voxelArray[hash].packedPosition, 0u, packedPosition);
        if (previous == 0u || previous == packedPosition) {
            atomicMax(voxelArray[hash].packedData, packedData);
            atomicMax(voxelArray[hash].packedTileData, packedTileData);
            break;
        }
    }

    ivec3 lod4Posisition     = position & -4;
    uint  lod4Hash           = moduloArraySize(tridimensionalHash(lod4Posisition), VOXEL_LOD_4_ARRAY_SIZE);
    uint  lod4PackedPosition = packPosition(lod4Posisition, 4);

    for (uint attempt = 0; attempt < MAX_PROBING_ATTEMPTS_LOD4; attempt++, incrementHash(lod4Hash, VOXEL_LOD_4_ARRAY_SIZE)) {
        uint previous = atomicCompSwap(lodArray[lod4Hash], ~0u, lod4PackedPosition);
        if (previous == ~0u || previous == lod4PackedPosition) break;
    }

    ivec3 lod16Posisition     = position & -16;
    uint  lod16Hash           = moduloArraySize(tridimensionalHash(lod16Posisition), VOXEL_LOD_16_ARRAY_SIZE);
    uint  lod16PackedPosition = packPosition(lod16Posisition, 16);

    for (uint attempt = 0; attempt < MAX_PROBING_ATTEMPTS_LOD16; attempt++, incrementHash(lod16Hash, VOXEL_LOD_16_ARRAY_SIZE)) {
        uint previous = atomicCompSwap(lodArray[lod16Hash], ~0u, lod16PackedPosition);
        if (previous ==  ~0u || previous == lod16PackedPosition) break;
    }
}

bool isInLod4(ivec3 position) {
    ivec3 lod4Posisition     = position & -4;
    uint  lod4Hash           = moduloArraySize(tridimensionalHash(lod4Posisition), VOXEL_LOD_4_ARRAY_SIZE);
    uint  lod4PackedPosition = packPosition(lod4Posisition, 4);

    for (uint attempt = 0; attempt < MAX_PROBING_ATTEMPTS_LOD4; attempt++, incrementHash(lod4Hash, VOXEL_LOD_4_ARRAY_SIZE)) {
        uint previous = lodArray[lod4Hash];

        if (previous == ~0u)
            return false;

        if (previous == lod4PackedPosition)
            return true;
    }
    return false;
}

bool isInLod16(ivec3 position) {
    ivec3 lod16Posisition     = position & -16;
    uint  lod16Hash           = moduloArraySize(tridimensionalHash(lod16Posisition), VOXEL_LOD_16_ARRAY_SIZE);
    uint  lod16PackedPosition = packPosition(lod16Posisition, 16);

    for (uint attempt = 0; attempt < MAX_PROBING_ATTEMPTS_LOD16; attempt++, incrementHash(lod16Hash, VOXEL_LOD_16_ARRAY_SIZE)) {
        uint previous = lodArray[lod16Hash];

        if (previous == ~0u)
            return false;

        if (previous == lod16PackedPosition)
            return true;
    }
    return false;
}

Voxel readVoxel(ivec3 position) {
    uint hash = moduloArraySize(tridimensionalHash(position), VOXEL_ARRAY_SIZE);

    uint packedPosition = packPosition(position, 1);

    for (uint attempt = 0; attempt < MAX_PROBING_ATTEMPTS_LOD1; attempt++, incrementHash(hash, VOXEL_ARRAY_SIZE)) {
        Voxel voxel = voxelArray[hash];

        if (unpackId(voxel.packedData) == 0) 
            return emptyVoxel;

        if (voxel.packedPosition == packedPosition) 
            return voxel;
    }
    return emptyVoxel;
}
