#ifndef SURFACE_CACHE_COMMON
#define SURFACE_CACHE_COMMON

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Macros.hlsl"

static const uint patchCapacity = 65536; // Must match C# side.
static const uint cascadeMax = 8; // Must match C# side.
#if UNITY_REVERSED_Z
static const float invalidNdcDepth = 0.0f;
#else
static const float invalidNdcDepth = 1.0f;
#endif

static const uint lowResScreenScaling = 4; // must match cpu side.

float LoadNdcDepth(Texture2D<float> depthBuffer, uint2 pos)
{
    const float depthBufferDepth = depthBuffer[pos];
    #if UNITY_REVERSED_Z
    return depthBufferDepth;
    #else
    if(depthBufferDepth == 1.0f)
        return invalidNdcDepth;
    else
        return depthBufferDepth * 2.0f - 1.0f;
    #endif
}

float2 PixelPosToUvPos(uint2 pixelPos, float2 reciprocalScreenSize)
{
    return (float2(pixelPos) + 0.5f) * reciprocalScreenSize;
}

namespace SphericalHarmonics
{
    static const float y0 = sqrt(1.0f / PI) * 0.5f;
    static const float y1Constant = sqrt(3.0f / PI) * 0.5f;
    static const float y20Constant = sqrt(15.0f / PI) * 0.5f;
    static const float y21Constant = y20Constant;
    static const float y22Constant = sqrt(5.0f / PI) * 0.25f;
    static const float y23Constant = y20Constant;
    static const float y24Constant = sqrt(15.0f / PI) * 0.25f;

    struct RGBL1
    {
        float3 l0;
        float3 l1s[3];
    };

    float3 Eval(in RGBL1 f, float3 direction)
    {
        return f.l0 * y0 + y1Constant * (f.l1s[0] * direction.y + f.l1s[1] * direction.z + f.l1s[2] * direction.x);
    }

    RGBL1 MulPure(RGBL1 f, float multiplier)
    {
        RGBL1 result;
        result.l0 = f.l0 * multiplier;
        result.l1s[0] = f.l1s[0] * multiplier;
        result.l1s[1] = f.l1s[1] * multiplier;
        result.l1s[2] = f.l1s[2] * multiplier;
        return result;
    }

    RGBL1 AddPure(RGBL1 a, RGBL1 b)
    {
        RGBL1 result;
        result.l0 = a.l0 + b.l0;
        result.l1s[0] = a.l1s[0] + b.l1s[0];
        result.l1s[1] = a.l1s[1] + b.l1s[1];
        result.l1s[2] = a.l1s[2] + b.l1s[2];
        return result;
    }

    // Multiply-add: a = a + b * c.
    void AddMut(inout RGBL1 a, RGBL1 b)
    {
        a.l0 += b.l0;
        a.l1s[0] += b.l1s[0];
        a.l1s[1] += b.l1s[1];
        a.l1s[2] += b.l1s[2];
    }

    // Multiply-add: a = a + b * c.
    void MulAddMut(inout RGBL1 a, float b, RGBL1 c)
    {
        a.l0 += b * c.l0;
        a.l1s[0] += b * c.l1s[0];
        a.l1s[1] += b * c.l1s[1];
        a.l1s[2] += b * c.l1s[2];
    }

    void MulMut(inout RGBL1 f, float multiplier)
    {
        f.l0 *= multiplier;
        f.l1s[0] *= multiplier;
        f.l1s[1] *= multiplier;
        f.l1s[2] *= multiplier;
    }

    RGBL1 Lerp(in RGBL1 a, in RGBL1 b, float3 s)
    {
        RGBL1 output;
        output.l0 = lerp(a.l0, b.l0, s);
        output.l1s[0] = lerp(a.l1s[0], b.l1s[0], s);
        output.l1s[1] = lerp(a.l1s[1], b.l1s[1], s);
        output.l1s[2] = lerp(a.l1s[2], b.l1s[2], s);
        return output;
    }

    // Convolve with cos(theta). https://cseweb.ucsd.edu/~ravir/papers/envmap/envmap.pdf
    void CosineConvolve(inout RGBL1 f)
    {
        const float aHat0 = PI;
        const float aHat1 = (2.0f * PI) / 3.0f;
        f.l0 *= aHat0;
        f.l1s[0] *= aHat1;
        f.l1s[1] *= aHat1;
        f.l1s[2] *= aHat1;
    }

    struct ScalarL2
    {
        float l0;
        float l1s[3];
        float l2s[5];
    };

    float Eval(in ScalarL2 f, float3 direction)
    {
        return f.l0 * y0 +
            f.l1s[0] * y1Constant * direction.y +
            f.l1s[1] * y1Constant * direction.z +
            f.l1s[2] * y1Constant * direction.x +
            f.l2s[0] * y20Constant * direction.x * direction.y +
            f.l2s[1] * y21Constant * direction.y * direction.z +
            f.l2s[2] * y22Constant * (3.0f * direction.z * direction.z - 1.0f) +
            f.l2s[3] * y23Constant * direction.x * direction.z +
            f.l2s[4] * y24Constant * (direction.x * direction.x - direction.y * direction.y);
    }

    ScalarL2 Lerp(ScalarL2 a, ScalarL2 b, float s)
    {
        SphericalHarmonics::ScalarL2 output;
        output.l0 = lerp(a.l0, b.l0, s);
        output.l1s[0] = lerp(a.l1s[0], b.l1s[0], s);
        output.l1s[1] = lerp(a.l1s[1], b.l1s[1], s);
        output.l1s[2] = lerp(a.l1s[2], b.l1s[2], s);
        output.l2s[0] = lerp(a.l2s[0], b.l2s[0], s);
        output.l2s[1] = lerp(a.l2s[1], b.l2s[1], s);
        output.l2s[2] = lerp(a.l2s[2], b.l2s[2], s);
        output.l2s[3] = lerp(a.l2s[3], b.l2s[3], s);
        output.l2s[4] = lerp(a.l2s[4], b.l2s[4], s);
        return output;
    }
}

#endif