#ifndef SVO_GLSL_
#define SVO_GLSL_

#include "common.glsl"
#include "worldFrameData.glsl"
#include "svoNode.glsl"

#define NodeID uint
#define Order int
#define VoxelRef uint

#ifndef SVO_DESCSET
#define SVO_DESCSET GLSL_TERRAIN_SVO_DESCRIPTOR_SET
#endif

layout(
    std430,
    set = SVO_DESCSET,
    binding = 0
) readonly buffer TreeBuffer
{
    Node nodeTable[];
} treeBuffer;

NodeID svoDescend(NodeID parent, Order order, uvec3 voxelPos)
{
    voxelPos >>= (order - 1);
    voxelPos &= 1;
    voxelPos <<= uvec3(0, 1, 2);
    uint index = voxelPos.x | voxelPos.y | voxelPos.z;
    return treeBuffer.nodeTable[parent].childRefs[index];
}

void findNode(inout NodeID nid, inout Order order, uvec3 voxelPos, Order targetOrder)
{
    do
    {
        nid = svoDescend(nid, order, voxelPos);
        order--;
    } while (nid != 0 && order != targetOrder);
}

// TODO: Remove this entire function as soon as possible.
void advanceRay(
    inout uvec3 rayPos,
    inout vec3 rayOffset,
    vec3 rayDir,
    Order order,
    inout float totalDistance,
    inout vec3 normal
)
{
    uint offsetMask = ((1 << order) - 1);

    rayOffset += vec3(rayPos & offsetMask);
    rayPos &= ~offsetMask;

    float nodeSize = float(1 << order);

    vec3 stepSize = vec3(
        rayDir.x > 0 ? nodeSize - rayOffset.x : rayOffset.x,
        rayDir.y > 0 ? nodeSize - rayOffset.y : rayOffset.y,
        rayDir.z > 0 ? nodeSize - rayOffset.z : rayOffset.z
    );

    stepSize /= abs(rayDir);
    float minDistanceToNextNode = min(stepSize.x, min(stepSize.y, stepSize.z));

    uint isX = uint(minDistanceToNextNode == stepSize.x);
    uint isY = uint(minDistanceToNextNode == stepSize.y) * (1 - isX);
    uint isZ = (1 - isX) * (1 - isY);
    normal = -normalize(rayDir * vec3(isX, isY, isZ));

    minDistanceToNextNode += 0.008;

    rayOffset += minDistanceToNextNode * rayDir;

    uvec3 isPositive = uvec3(rayDir.x > 0, rayDir.y > 0, rayDir.z > 0);
    uvec3 prevPos = rayPos;

    rayPos += uvec3(ivec3(floor(rayOffset)));
    rayOffset = fract(rayOffset);

    // TODO: Make the marching exact.
/*
    rayOffset *= vec3(1 - isX, 1 - isY, 1 - isZ);
    rayOffset += vec3(isX, isY, isZ) * vec3(isPositive);
    rayPos *= uvec3(1 - isX, 1 - isY, 1 - isZ);
    rayPos += prevPos * uvec3(isX, isY, isZ);
    rayPos -= uvec3(isX, isY, isZ) * (1 - isPositive);
    rayPos += uvec3(isX, isY, isZ) * isPositive * (1 << order);
*/

    totalDistance += minDistanceToNextNode;
}

#endif