#ifndef _MATERIAL_POOL_HLSL_
#define _MATERIAL_POOL_HLSL_

namespace MaterialPool
{
    struct MaterialEntry
    {
        int albedoTextureIndex;
        int emissionTextureIndex;
        int transmissionTextureIndex;
        uint flags;
        float2 albedoScale;
        float2 albedoOffset;
        float2 emissionScale;
        float2 emissionOffset;
        float2 transmissionScale;
        float2 transmissionOffset;
        float3 emissionColor;
        uint albedoAndEmissionUVChannel;
    };

    struct MaterialProperties
    {
        float3 baseColor;
        float metalness;

        float3 emissive;
        float roughness;

        float3 transmission;
        uint isTransmissive;

        uint doubleSidedGI;
    };

    float4 SampleAtlas(Texture2DArray<float4> atlas, SamplerState atlasSampler, float atlasTexelSize, uint index, float2 uv, float2 scale, float2 offset, bool pointFilterMode)
    {
        // Apply the scale and offset to access to desired atlas entry
        float2 localUV = uv * scale + offset;

        // To prevent sampling part of the neighbor due to bilinear filtering, we need to clamp the sample
        // position to an 'inner rectangle' of the atlas entry, which is shrunk by half a texel on each side.
        float2 innerRectMin = offset + (atlasTexelSize * 0.5f);
        // Calculating the rectangle extent is a bit tricky, because the size of the entry (scale)
        // is not necessarily a multiple of the atlas texel size. We need to round it up to the next texel first,
        // then subtract the half texel.
        float2 innerRectMax = ceil((offset + scale) / atlasTexelSize) * atlasTexelSize - (atlasTexelSize * 0.5f);
        float2 clampedUV = clamp(localUV, innerRectMin, innerRectMax);

        [branch] if (pointFilterMode)
            return atlas.Load(int4(clampedUV * rcp(atlasTexelSize), index, 0));
        else
            return atlas.SampleLevel(atlasSampler, float3(clampedUV, index), 0);
    }

    MaterialProperties LoadMaterialProperties(
        StructuredBuffer<MaterialEntry> materialList,
        Texture2DArray<float4> albedoTextures,
        SamplerState albedoSamplerState,
        Texture2DArray<float4> transmissionTextures,
        SamplerState transmissionSamplerState,
        Texture2DArray<float4> emissionTextures,
        SamplerState emissionSamplerState,
        float albedoBoost,
        float atlasTexelSize,
        uint materialIndex,
        float2 uv0,
        float2 uv1)
    {
        const MaterialEntry matEntry = materialList[materialIndex];

        MaterialProperties material = (MaterialProperties)0;
        material.baseColor = float3(0.75, 0.75, 0.75);
        material.transmission = float3(1.0, 1.0, 1.0);
        material.isTransmissive = matEntry.flags & 1;

        if (matEntry.albedoTextureIndex != -1)
        {
            float2 textureUV = matEntry.albedoAndEmissionUVChannel == 1 ? uv1 : uv0;
            float4 texColor = SampleAtlas(albedoTextures, albedoSamplerState, atlasTexelSize, matEntry.albedoTextureIndex, textureUV, matEntry.albedoScale, matEntry.albedoOffset, false);
            material.baseColor = texColor.rgb;
            // apply albedo boost, but still keep the reflectance at maximum 100%
            material.baseColor = min(albedoBoost * material.baseColor, float3(1.0, 1.0, 1.0));
            material.transmission = 1.0f - float3(texColor.a, texColor.a, texColor.a);
        }

        if (matEntry.emissionTextureIndex != -1)
        {
            float2 textureUV = matEntry.albedoAndEmissionUVChannel == 1 ? uv1 : uv0;
            material.emissive = SampleAtlas(emissionTextures, emissionSamplerState, atlasTexelSize, matEntry.emissionTextureIndex, textureUV, matEntry.emissionScale, matEntry.emissionOffset, false).rgb;
        }
        else
        {
            material.emissive = matEntry.emissionColor;
        }

        if (matEntry.transmissionTextureIndex != -1)
        {
            float2 uv = uv0;
            bool pointSampleTransmission = (matEntry.flags & 4) != 0;
            material.transmission = saturate(SampleAtlas(transmissionTextures, transmissionSamplerState, atlasTexelSize, matEntry.transmissionTextureIndex, uv, matEntry.transmissionScale, matEntry.transmissionOffset, pointSampleTransmission).rgb);
        }

        // unused for now, we need these for specular support
        material.roughness = 1.0;
        material.metalness = 0;
        material.doubleSidedGI = matEntry.flags & 2;

        return material;
    }
}

#endif