layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;

uint BuildLpvMask(const in uint north, const in uint east, const in uint south, const in uint west, const in uint up, const in uint down)
{
    return east | (west << 1) | (down << 2) | (up << 3) | (south << 4) | (north << 5);
}

const ivec3 workGroups = ivec3(6, 6, 1);

// #define LPV_ENABLED
#ifdef LPV_ENABLED

    #include "/lib/lpv/items.glsl"
    #include "/lib/lpv/blocks.glsl"
    #include "/lib/lpv/entities.glsl"
    #include "/lib/lpv/lpv_blocks.glsl"

// Color Groups in GLSL

const vec3 LightColor_White = vec3(0.9, 0.75, 0.6);
const vec3 LightColor_LightBlue = vec3(0.4, 0.6, 0.7);
const vec3 LightColor_Cyan = vec3(0.35, 0.7, 0.7);
const vec3 LightColor_Blue = vec3(0.2, 0.4, 0.7);
const vec3 LightColor_Magenta = vec3(0.6, 0.4, 0.6);
const vec3 LightColor_Purple = vec3(0.5, 0.3, 0.7);
const vec3 LightColor_Yellow = vec3(0.8, 0.8, 0.4);
const vec3 LightColor_Orange = vec3(0.9, 0.6, 0.4);
const vec3 LightColor_Red = vec3(0.6, 0.3, 0.3);
const vec3 LightColor_Pink = vec3(0.9, 0.6, 0.7);
const vec3 LightColor_Brown = vec3(0.5, 0.4, 0.3);
const vec3 LightColor_Green = vec3(0.4, 0.6, 0.4);
const vec3 LightColor_Lime = vec3(0.5, 0.7, 0.3);
const vec3 LightColor_Black = vec3(0.9, 0.6, 0.4);

mat4 GetSaturationMatrix(const in float saturation)
{
    const vec3 luminance = vec3(0.3086, 0.6094, 0.0820);
    vec3 lumSat = luminance * (1.0 - saturation);
    vec2 satZero = vec2(saturation, 0.0);
    return mat4(
        vec4(lumSat.r + satZero.xyy, 0.0),
        vec4(lumSat.g + satZero.yxy, 0.0),
        vec4(lumSat.b + satZero.yyx, 0.0),
        vec4(0.0, 0.0, 0.0, 1.0));
}
#endif

void main()
{
#ifdef LPV_ENABLED
    int blockId = int(gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * 48);
    if (blockId >= 2048)
        return;

    vec3 lightColor = vec3(0.0);
    float lightRange = 0.0;
    float mixWeight = 0.5; // reduces the "ao"
    uint mixMask = 0xFFFF;
    vec3 tintColor = vec3(1.0);

    // Material / block specific adjustments
    switch (blockId)
    {
    case BLOCK_WATER:
        tintColor = vec3(0.8, 1.0, 0.95);
        mixWeight = 0.8;
        break;

    case BLOOP_BLOCK_OTHER:
        mixWeight = 0.8;
        break;

    case BLOOP_BLOCK_FLOWERS:
    case BLOOP_BLOCK_FLOWERS_TALL_UPPER:
    case BLOOP_BLOCK_FLOWERS_TALL_LOWER:
    case BLOOP_BLOCK_FLOWERS_MODDED:
        mixWeight = 0.85;
        break;

    case BLOOP_BLOCK_LEAVES:
        mixWeight = 0.9;
        break;
    }

    // Optimized light-sources (blockId 100 to 323)
    if (blockId >= 100 && blockId < 324)
    {
        mixWeight = 1.0;

        // Calculate lightRange directly: remainder + 1 (i.e. cases 0->1, 1->2, ..., 15->16)
        lightRange = float(((blockId - 100) % 16) + 1);

        // Use an array lookup for lightColor based on block group
        const vec3 lightColors[14] = vec3[14](
            LightColor_White, LightColor_LightBlue, LightColor_Cyan, LightColor_Blue,
            LightColor_Magenta, LightColor_Purple, LightColor_Yellow, LightColor_Orange,
            LightColor_Red, LightColor_Pink, LightColor_Brown, LightColor_Green,
            LightColor_Lime, LightColor_Black);
        lightColor = lightColors[(blockId - 100) / 16];
    }

    // Reflective translucents / glass
    switch (blockId)
    {
    case 50:
        tintColor = vec3(1.0);
        mixWeight = 1.0;
        break;
    case 51:
        tintColor = vec3(0.3);
        mixWeight = 1.0;
        break;
    case 52:
        tintColor = vec3(0.1, 0.1, 0.98);
        mixWeight = 1.0;
        break;
    case 53:
        tintColor = vec3(0.566, 0.388, 0.148);
        mixWeight = 1.0;
        break;
    case 54:
        tintColor = vec3(0.082, 0.533, 0.763);
        mixWeight = 1.0;
        break;
    case 55:
        tintColor = vec3(0.4, 0.4, 0.4);
        mixWeight = 1.0;
        break;
    case 56:
        tintColor = vec3(0.125, 0.808, 0.081);
        mixWeight = 1.0;
        break;
    case 57:
        tintColor = vec3(0.320, 0.685, 0.955);
        mixWeight = 1.0;
        break;
    case 58:
        tintColor = vec3(0.7);
        mixWeight = 1.0;
        break;
    case 59:
        tintColor = vec3(0.633, 0.924, 0.124);
        mixWeight = 1.0;
        break;
    case 60:
        tintColor = vec3(0.698, 0.298, 0.847);
        mixWeight = 1.0;
        break;
    case 61:
        tintColor = vec3(0.919, 0.586, 0.185);
        mixWeight = 1.0;
        break;
    case 62:
        tintColor = vec3(0.949, 0.274, 0.497);
        mixWeight = 1.0;
        break;
    case 63:
        tintColor = vec3(0.578, 0.170, 0.904);
        mixWeight = 1.0;
        break;
    case 64:
        tintColor = vec3(0.999, 0.188, 0.188);
        mixWeight = 1.0;
        break;
    case 65:
        tintColor = vec3(0.96, 0.96, 0.96);
        mixWeight = 1.0;
        break;
    case 66:
        tintColor = vec3(0.965, 0.965, 0.123);
        mixWeight = 1.0;
        break;
    case 67:
        tintColor = vec3(0.984, 0.733, 0.251);
        mixWeight = 1.0;
        break;
    case 68:
        tintColor = vec3(0.502, 0.165, 0.831);
        mixWeight = 1.0;
        lightColor = LightColor_Magenta;
        lightRange = 11.0;
        break;
    case 69:
        tintColor = vec3(0.408, 0.725, 0.329);
        mixWeight = 1.0;
        break;
    }

    // LPV shapes
    switch (blockId)
    {
    case BLOCK_LPV_IGNORE:
        mixWeight = 1.0;
        break;
    case BLOCK_CARPET:
        mixMask = BuildLpvMask(1u, 1u, 1u, 1u, 1u, 0u);
        mixWeight = 0.9;
        break;
    case BLOCK_DOOR_N:
        mixMask = BuildLpvMask(0u, 1u, 1u, 1u, 1u, 1u);
        mixWeight = 0.8;
        break;
    case BLOCK_DOOR_E:
        mixMask = BuildLpvMask(1u, 0u, 1u, 1u, 1u, 1u);
        mixWeight = 0.8;
        break;
    case BLOCK_DOOR_S:
        mixMask = BuildLpvMask(1u, 1u, 0u, 1u, 1u, 1u);
        mixWeight = 0.8;
        break;
    case BLOCK_DOOR_W:
        mixMask = BuildLpvMask(1u, 1u, 1u, 0u, 1u, 1u);
        mixWeight = 0.8;
        break;
    case BLOCK_FENCE:
    case BLOCK_FENCE_GATE:
        mixWeight = 0.7;
        break;
    case BLOCK_FLOWER_POT:
        mixWeight = 0.7;
        break;
    case BLOCK_IRON_BARS:
        mixWeight = 0.6;
        break;
    case BLOCK_PRESSURE_PLATE:
        mixMask = BuildLpvMask(1u, 1u, 1u, 1u, 1u, 0u);
        mixWeight = 0.9;
        break;
    case BLOCK_SLAB_TOP:
        mixMask = BuildLpvMask(1u, 1u, 1u, 1u, 0u, 1u);
        mixWeight = 0.5;
        break;
    case BLOCK_SLAB_BOTTOM:
    case BLOCK_SNOW_LAYERS:
        mixMask = BuildLpvMask(1u, 1u, 1u, 1u, 1u, 0u);
        mixWeight = 0.5;
        break;
    case BLOCK_TRAPDOOR_BOTTOM:
        mixMask = BuildLpvMask(1u, 1u, 1u, 1u, 1u, 0u);
        mixWeight = 0.8;
        break;
    case BLOCK_TRAPDOOR_TOP:
        mixMask = BuildLpvMask(1u, 1u, 1u, 1u, 0u, 1u);
        mixWeight = 0.8;
        break;
    case BLOCK_TRAPDOOR_N:
        mixMask = BuildLpvMask(0u, 1u, 1u, 1u, 1u, 1u);
        mixWeight = 0.8;
        break;
    case BLOCK_TRAPDOOR_E:
        mixMask = BuildLpvMask(1u, 0u, 1u, 1u, 1u, 1u);
        mixWeight = 0.8;
        break;
    case BLOCK_TRAPDOOR_S:
        mixMask = BuildLpvMask(1u, 1u, 0u, 1u, 1u, 1u);
        mixWeight = 0.8;
        break;
    case BLOCK_TRAPDOOR_W:
        mixMask = BuildLpvMask(1u, 1u, 1u, 0u, 1u, 1u);
        mixWeight = 0.8;
        break;
    }

    // WALL
    if (blockId >= BLOCK_WALL_MIN && blockId <= BLOCK_WALL_MAX)
    {
        mixWeight = 0.25;
        if (blockId == BLOCK_WALL_POST_TALL_ALL ||
            blockId == BLOCK_WALL_TALL_ALL ||
            blockId == BLOCK_WALL_POST_TALL_N_W_S ||
            blockId == BLOCK_WALL_POST_TALL_N_E_S ||
            blockId == BLOCK_WALL_POST_TALL_W_N_E ||
            blockId == BLOCK_WALL_POST_TALL_W_S_E)
        {
            mixMask = BuildLpvMask(0u, 0u, 0u, 0u, 1u, 1u);
            mixWeight = 0.125;
        }
        else if (blockId == BLOCK_WALL_POST_TALL_N_S || blockId == BLOCK_WALL_TALL_N_S)
        {
            mixMask = BuildLpvMask(1u, 0u, 1u, 0u, 1u, 1u);
        }
        else if (blockId == BLOCK_WALL_POST_TALL_W_E || blockId == BLOCK_WALL_TALL_W_E)
        {
            mixMask = BuildLpvMask(0u, 1u, 0u, 1u, 1u, 1u);
        }
        // TODO: more walls
    }

    // Miscellaneous
    if (blockId == BLOCK_SIGN)
    {
        mixWeight = 0.9;
    }

    // Entities
    switch (blockId)
    {
    case ENTITY_BLAZE:
        lightColor = vec3(1.000, 0.592, 0.000);
        lightRange = 8.0;
        break;

    case ENTITY_END_CRYSTAL:
        lightColor = vec3(1.000, 0.000, 1.000);
        lightRange = 8.0;
        break;

    case ENTITY_FIREBALL_SMALL:
        lightColor = vec3(0.000, 1.000, 0.000);
        lightRange = 8.0;
        mixWeight = 1.0;
        break;

    case ENTITY_GLOW_SQUID:
        lightColor = vec3(0.180, 0.675, 0.761);
        lightRange = 6.0;
        mixWeight = 0.5;
        break;

    case ENTITY_MAGMA_CUBE:
        lightColor = vec3(0.747, 0.323, 0.110);
        lightRange = 9.0;
        break;

    case ENTITY_TNT:
        lightColor = vec3(1.0);
        lightRange = 8.0;
        break;

    case ENTITY_SPECTRAL_ARROW:
        lightColor = vec3(0.839, 0.541, 0.2);
        lightRange = 8.0;
        mixWeight = 1.0;
        break;
    }

    // Apply saturation adjustments
    const float saturationF = LPV_SATURATION / 100.0;
    mat4 matSaturation = GetSaturationMatrix(saturationF);
    lightColor = (matSaturation * vec4(lightColor, 1.0)).rgb;

    const float tintSaturationF = LPV_TINT_SATURATION / 100.0;
    mat4 matTintSaturation = GetSaturationMatrix(tintSaturationF);
    tintColor = (matTintSaturation * vec4(tintColor, 1.0)).rgb;

    // Incorporate mix weight into tint color
    tintColor *= mixWeight;

    uint lightColorRange = packUnorm4x8(vec4(lightColor, lightRange / 255.0));
    uint tintColorMask = packUnorm4x8(vec4(tintColor, 0.0));
    tintColorMask |= mixMask << 24;

    imageStore(imgBlockData, blockId, uvec4(lightColorRange, tintColorMask, 0u, 0u));
#endif
}
