#include "depth_normal.fsh"
precision mediump sampler2DShadow;
#include "ShadowSamplingFilter.fsh"

/**
 * Preset for CSM lighting
 */
///{
// Select layer by view depth internal, not fully tested!
// #define SELECT_CASCADE_BY_INTERVAL_FLAG

// Blend in layer margin area between cascade levels
// #define BLEND_BETWEEN_CASCADE_LAYERS_FLAG

// Use uniform shadow map viewport to reserve frustra proportion
// #define TIGHT_SHADOWMAP_VIEWPORT_FLAG

// Shadow sampling filter, @see C++ sources: ShadowSamplingFilterType
// #define SHADOW_SAMPLING_FILTER_ENUM

// Visualize cascade color
// #define DEBUG_VISUALIZE_CASCADE_COLOR_FLAG
///}

#ifdef _SHADERC_PRESET
    #define NUM_CASCADE_LEVELS 4
    // #define SELECT_CASCADE_BY_INTERVAL_FLAG
    #define BLEND_BETWEEN_CASCADE_LAYERS_FLAG
    #define TIGHT_SHADOWMAP_VIEWPORT_FLAG
    #define SHADOW_SAMPLING_FILTER_ENUM 2
    #define DEBUG_VISUALIZE_CASCADE_COLOR_FLAG
#endif

// Default count of cascade levels
#ifndef NUM_CASCADE_LEVELS
    #define NUM_CASCADE_LEVELS 4
#endif
// Maximum threshold of cascade levels
#if NUM_CASCADE_LEVELS > 4
    #error Count of cascade levels can not exceed 4
#endif

#if (NUM_CASCADE_LEVELS < 2) && defined(BLEND_BETWEEN_CASCADE_LAYERS_FLAG)
#undef BLEND_BETWEEN_CASCADE_LAYERS_FLAG
#endif

// input
in vec2 UV_f;

// output
layout(location = 0) out vec4 fragColor; // shadow intensity, 0(dimmest as dark) to 1(brightest as white)

uniform sampler2DShadow ShadowMapTexture; // shadow depth map atlas

uniform vec4 ShadowMapTexelTextureSize; // shadow map texel and texture size

uniform vec3 PixelToLightDirViewSpace; // Light direction in view space

// Blend area in UV space for SELECT_CASCADE_BY_MAP_FLAG or linear-normalized depth in main camera view space
// for SELECT_CASCADE_BY_INTERVAL_FLAG
uniform float CascadeBlendArea;

uniform mat4 TextureToViewMatrix; // matrix that transforms UV in depth image to position in view space(deferred shading)

//
// Cascade uniforms
//
//{
uniform vec4 BorderPaddingBoundsStep[NUM_CASCADE_LEVELS];

//
// CSM miscellaneous scalar/vector data packed into vec4(float4) for each cascade, packing ruler:
//   + Normal offset and depth bias of shading pixel:
//     - normal offset to keep off self-shadowing(1 component)
//     - depth bias to also keep off self-shadowing(1 component)
//   + PCF uv unit per texel unit for each cascade, only used for uniform shadowmap viewport preset: 
//     - u unit per texel uint (1 component)
//     - v unit per texel unit (1 component)
//
uniform vec4 VertexBaisesAndTexelUnits[NUM_CASCADE_LEVELS];

// matrix that transforms position in view space to UVW in light 'texture space'
uniform mat4 ViewToShadowTextureMatrix[NUM_CASCADE_LEVELS+1];

// the last element only contain far plane depth of the last cascade
#ifdef SELECT_CASCADE_BY_INTERVAL_FLAG
uniform vec4 CascadeFrustumsEyeSpaceDepthsFloat[NUM_CASCADE_LEVELS];
#endif
//}

const vec4 CascadeColorsMultiplier[5] = vec4[5](
    vec4( 0.8, 0.2, 0.2, 1.0 ),
    vec4( 0.2, 0.8, 0.2, 1.0 ),
    vec4( 0.2, 0.2, 0.8, 1.0 ),
    vec4( 0.8, 0.2, 0.8, 1.0 ),
    vec4( 1.0, 1.0, 1.0, 1.0 )
);

//
// implementation
//

// map some HLSL syntax
float saturate(float x) { return clamp(x, 0.0, 1.0); }
float2 saturate(float2 x) { return clamp(x, float2(0.0), float2(1.0)); }
float3 saturate(float3 x) { return clamp(x, float3(0.0), float3(1.0)); }
float4 saturate(float4 x) { return clamp(x, float4(0.0), float4(1.0)); }
#define lerp mix

float remap(float value, float omin, float omax, float nmin, float nmax)
{
    float f = clamp((value - omin) / (omax - omin), 0.0, 1.0);
    return f * (nmax - nmin) + nmin;
}

void GetPositionAndNormalViewSpace(vec2 uv, out vec4 posViewSpace, out vec3 normalViewSpace)
{
    float depthNormalized = _SampleNormalizedDepth(uv);

    normalViewSpace = _SampleNormal(uv);

    posViewSpace = TextureToViewMatrix * vec4(uv, depthNormalized, 1.0);
    posViewSpace.xyz /= posViewSpace.w;
    posViewSpace.w = 1.0;
}

float TestWithinCascadeLevelForMap_float(int cascadeIndex, vec4 shadowTexcoord) {

    return step(4.0, dot(step(BorderPaddingBoundsStep[cascadeIndex], vec4(shadowTexcoord.xy, -shadowTexcoord.xy)),
        vec4(1.0)));
}

bool TestWithinCascadeLevelForMap_bool(int cascadeIndex, vec4 shadowTexcoord) {
    return all(lessThanEqual(BorderPaddingBoundsStep[cascadeIndex], vec4(shadowTexcoord.xy, -shadowTexcoord.xy)));
}

//
// Normal offset and depth bias
// @see https://github.com/wlgys8/SRPLearn/blob/7_shadow_pcf_bias/Assets/XRendererPipeline/ShaderLibrary/ShadowBias.hlsl
//
void ApplyShadowBias(int cascadeIndex, float shadowBiasGeometryFactor, vec3 normalViewSpace, inout vec4 shadowTexcoord) {

    vec2 shadowBiasScale = VertexBaisesAndTexelUnits[cascadeIndex].xy * vec2(shadowBiasGeometryFactor, 1.0);
    vec4 vertexOffset = shadowBiasScale.x * (ViewToShadowTextureMatrix[cascadeIndex] * vec4(normalViewSpace, 0.0));
    vertexOffset.z -= shadowBiasScale.y;

    shadowTexcoord += vertexOffset;
}

//
// Normal offset in viewspace and depth bias in shadow texture space
// @return vec4 with normal offset(3 component) and depth bias(1 component)
//
vec4 ComputeShadowBias(int cascadeIndex, float shadowBiasGeometryFactor, vec3 normalViewSpace) {
    vec2 shadowBiasScale = VertexBaisesAndTexelUnits[cascadeIndex].xy * vec2(shadowBiasGeometryFactor, 1.0);
    vec4 vertexOffset = shadowBiasScale.x * vec4(normalViewSpace, 0.0);
    vertexOffset.w = -shadowBiasScale.y;

    return vertexOffset;
}

float ComputeShadowMapAtlasTexcoord_blend(int cascadeIndex, float shadowBiasGeometryFactor, vec4 posViewSpace, vec3 normalViewSpace, inout vec4 shadowTexcoord) {

    // We do offset vertex as we do for vertex previously
    vec4 shadowBias = ComputeShadowBias(cascadeIndex, shadowBiasGeometryFactor, normalViewSpace);
    posViewSpace.xyz += shadowBias.xyz;
    shadowTexcoord = ViewToShadowTextureMatrix[cascadeIndex] * posViewSpace;
    shadowTexcoord.z += shadowBias.w;

#ifndef SELECT_CASCADE_BY_INTERVAL_FLAG
    float blendMultifier = TestWithinCascadeLevelForMap_float(cascadeIndex, shadowTexcoord);
#else
    float blendMultifier = 1.0;
#endif

    return blendMultifier;
}

float ComputeBlendAmountForMap(vec4 shadowTexcoord) {

    vec2 currPixelsBlendBandLocation = min( shadowTexcoord.xy, vec2(1.0) - shadowTexcoord.xy );
    float blendBandWidth = min(currPixelsBlendBandLocation.x, currPixelsBlendBandLocation.y);

    return blendBandWidth / CascadeBlendArea;
}

#ifdef SELECT_CASCADE_BY_INTERVAL_FLAG
float ComputeBlendAmountForInterval(int cascadeIndex, float pixelDepthViewSpace) {

    int prevCascadeIndex = max(0, cascadeIndex - 1);
    float depthIntervalBegin = CascadeFrustumsEyeSpaceDepthsFloat[prevCascadeIndex].x; // current layer interval begin
    float depthIntervalEnd   = CascadeFrustumsEyeSpaceDepthsFloat[cascadeIndex].x; // next layer interval begin

    float currPixelsBlendBandLocation = (pixelDepthViewSpace - depthIntervalBegin) / (depthIntervalEnd - depthIntervalBegin);
    currPixelsBlendBandLocation = 1.0 - currPixelsBlendBandLocation;
    return currPixelsBlendBandLocation / CascadeBlendArea;
}
#endif

float2 GetPixelPositionScreenSpace() {
    return gl_FragCoord.xy;
}

float ComputePCFPercentLit(int cascadeIndex, vec4 shadowTexcoord) {

    float percentLit;

#if SHADOW_SAMPLING_FILTER_ENUM == 1

    // Use Dithering Filter
    percentLit = SampleShadowwPCF4x4_DitheringFilter4Tap(ShadowMapTexelTextureSize, shadowTexcoord.xyz, ShadowMapTexture,
        GetPixelPositionScreenSpace());

#elif SHADOW_SAMPLING_FILTER_ENUM == 2

    // Use Dithering Filter
    percentLit = SampleShadowPCF9x9_DitheringFilter9Tap(ShadowMapTexelTextureSize, shadowTexcoord.xyz, ShadowMapTexture,
            GetPixelPositionScreenSpace());

#elif SHADOW_SAMPLING_FILTER_ENUM == 3

    percentLit = SampleShadowPCF16x16_DitheringFilter16Tap(ShadowMapTexelTextureSize, shadowTexcoord.xyz, ShadowMapTexture,
            GetPixelPositionScreenSpace());

#elif SHADOW_SAMPLING_FILTER_ENUM == 4

    // Use Linear Tent Filter
    percentLit = SampleShadowPCF3x3_TentFilter4Tap(ShadowMapTexelTextureSize, shadowTexcoord.xyz, ShadowMapTexture);

#endif

    return percentLit;
}

int ComputeCascadeIndex(vec4 posViewSpace, out vec4 shadowTexcoord) {

    int cascadeIndex = NUM_CASCADE_LEVELS;

#ifdef SELECT_CASCADE_BY_INTERVAL_FLAG

    #if NUM_CASCADE_LEVELS == 1
        float cascadeIndexf = step(CascadeFrustumsEyeSpaceDepthsFloat[0].x, -posViewSpace.z);
    #elif NUM_CASCADE_LEVELS == 2
        float cascadeIndexf = dot(
            step(
                vec2(
                    CascadeFrustumsEyeSpaceDepthsFloat[0].x,
                    CascadeFrustumsEyeSpaceDepthsFloat[1].x
                ),
                vec2(-posViewSpace.z)
            ),
            vec2(1.0)
        );
    #elif NUM_CASCADE_LEVELS == 3
        float cascadeIndexf = dot(
            step(
                vec3(
                    CascadeFrustumsEyeSpaceDepthsFloat[0].x,
                    CascadeFrustumsEyeSpaceDepthsFloat[1].x,
                    CascadeFrustumsEyeSpaceDepthsFloat[2].x
                ),
                vec3(-posViewSpace.z)
            ),
            vec3(1.0)
        );
    #else
        float cascadeIndexf = dot(
            step(
                vec4(
                    CascadeFrustumsEyeSpaceDepthsFloat[0].x,
                    CascadeFrustumsEyeSpaceDepthsFloat[1].x,
                    CascadeFrustumsEyeSpaceDepthsFloat[2].x,
                    CascadeFrustumsEyeSpaceDepthsFloat[3].x
                ),
                vec4(-posViewSpace.z)
            ),
            vec4(1.0)
        );
    #endif // NUM_CASCADE_LEVELS

    shadowTexcoord = ViewToShadowTextureMatrix[cascadeIndex] * posViewSpace;
#else

    // #pragma unroll
    // for(int i = 0; i <= NUM_CASCADE_LEVELS; ++i) {
    //     shadowTexcoord = ViewToShadowTextureMatrix[i] * posViewSpace;

    //     if(TestWithinCascadeLevelForMap_bool(i, shadowTexcoord)) {
    //         cascadeIndex = i;
    //         break;
    //     }
    // }

    #if NUM_CASCADE_LEVELS == 1

        float4 pos[2];
        pos[0] = ViewToShadowTextureMatrix[0] * posViewSpace;
        // pos[1] = ViewToShadowTextureMatrix[1] * posViewSpace;
        pos[1] = float4(0.0, 0.0, -1.0, 1.0);

        cascadeIndex = 1 - int(TestWithinCascadeLevelForMap_bool(0, pos[0]));
        shadowTexcoord = pos[cascadeIndex];

    #elif NUM_CASCADE_LEVELS == 2

        float4 pos[3];
        pos[0] = ViewToShadowTextureMatrix[0] * posViewSpace;
        pos[1] = ViewToShadowTextureMatrix[1] * posViewSpace;
        // pos[2] = ViewToShadowTextureMatrix[2] * posViewSpace;
        pos[2] = float4(0.0, 0.0, -1.0, 1.0);

        half2 weights;
        weights[0] = TestWithinCascadeLevelForMap_float(0, pos[0]);
        weights[1] = TestWithinCascadeLevelForMap_float(1, pos[1]);
        weights.y = saturate(weights.y - weights.x);

        cascadeIndex = int( 2.0 - dot(weights, float2(2.0, 1.0)) );
        shadowTexcoord = pos[cascadeIndex];

    #elif NUM_CASCADE_LEVELS == 3

        float4 pos[4];

        pos[0] = ViewToShadowTextureMatrix[0] * posViewSpace;
        pos[1] = ViewToShadowTextureMatrix[1] * posViewSpace;
        pos[2] = ViewToShadowTextureMatrix[2] * posViewSpace;
        // pos[3] = ViewToShadowTextureMatrix[3] * posViewSpace;
        pos[3] = float3(0.0, 0.0, -1.0, 1.0);

        half3 weights;
        weights[0] = TestWithinCascadeLevelForMap_float(0, pos[0]);
        weights[1] = TestWithinCascadeLevelForMap_float(1, pos[1]);
        weights[2] = TestWithinCascadeLevelForMap_float(2, pos[2]);

        weights.yz = saturate(weights.yz - weights.xy);

        cascadeIndex = int(3.0 - dot(weights, float3(3.0, 2.0, 1.0)));
        shadowTexcoord = pos[cascadeIndex];

    #else

        float4 pos[5];

        pos[0] = ViewToShadowTextureMatrix[0] * posViewSpace;
        pos[1] = ViewToShadowTextureMatrix[1] * posViewSpace;
        pos[2] = ViewToShadowTextureMatrix[2] * posViewSpace;
        pos[3] = ViewToShadowTextureMatrix[3] * posViewSpace;
        // pos[4] = ViewToShadowTextureMatrix[4] * posViewSpace;
        pos[4] = float4(0.0, 0.0, -1.0, 1.0);

        half4 weights;
        weights[0] = TestWithinCascadeLevelForMap_float(0, pos[0]);
        weights[1] = TestWithinCascadeLevelForMap_float(1, pos[1]);
        weights[2] = TestWithinCascadeLevelForMap_float(2, pos[2]);
        weights[3] = TestWithinCascadeLevelForMap_float(3, pos[3]);

        weights.yzw = saturate(weights.yzw - weights.xyz);

        cascadeIndex = int(4.0 - dot(weights, float4(4.0, 3.0, 2.0, 1.0)));
        shadowTexcoord = pos[cascadeIndex];

    #endif

#endif
    return cascadeIndex;
}

vec4 RemapPercentLit(float light, float lightDot) {
    // TODO: PCF导致单独为中心像素应用normal bias无法彻底消除错误，从而产生灰色蒙层。
    // 如果能够优化，将会不再需要remap；
    // 目前的一个解放是使用PCF深度偏移方式, 见ShadowSamplingFilter.fsh:SampleShadow_ComputeDepthTexelDeltaFactors

    // Remove weak light, which can be generated by front face;
    float scale = remap(lightDot, 0.8, 0.4, 1.0 / 0.6, 1.0 / 0.3);
    light = min(light * scale, 1.0);
    
    // 背面接受阴影，避免角度较小时闪烁；
    //    if (lightDot < 0.1)
    //        light *= max((lightDot - 0.05) * 20.0, 0.0);
    
    // 背面不接受阴影，避免角度较小时闪烁，应当令
    // dot = 0.1 -> light = light；
    // dot = .05 -> 1ight = 1.0;

    light = remap(lightDot, 0.05, 0.1, 1.0, light);

    return vec4(light);
}

vec4 ShadowLit() {

    vec4 posViewSpace;
    vec3 normalViewSpace;

    GetPositionAndNormalViewSpace(UV_f, posViewSpace, normalViewSpace);

    vec4 shadowTexcoord;

    int cascadeIndex = ComputeCascadeIndex(posViewSpace, shadowTexcoord);

    float NdotL = saturate(dot(normalViewSpace, PixelToLightDirViewSpace));
    float shadowBiasGeometryFactor = 1.0 - NdotL;

    ApplyShadowBias(cascadeIndex, shadowBiasGeometryFactor, normalViewSpace, shadowTexcoord);

    // evaluate PCF
    float percentLit = ComputePCFPercentLit(cascadeIndex, shadowTexcoord);

#ifdef BLEND_BETWEEN_CASCADE_LAYERS_FLAG
    int nextCascadeIndex = min(cascadeIndex + 1, NUM_CASCADE_LEVELS - 1);

    float blendBetweenCascadesAmount;

    #ifdef SELECT_CASCADE_BY_INTERVAL_FLAG
    blendBetweenCascadesAmount = ComputeBlendAmountForInterval(cascadeIndex, -posViewSpace.z);
    #else
    blendBetweenCascadesAmount = ComputeBlendAmountForMap(shadowTexcoord);
    #endif

    if(blendBetweenCascadesAmount < 1.0) {

        vec4 shadowTexcoord_blend;
        float blendMultifier = ComputeShadowMapAtlasTexcoord_blend(nextCascadeIndex, shadowBiasGeometryFactor, posViewSpace, normalViewSpace, shadowTexcoord_blend);
        blendBetweenCascadesAmount = lerp(1.0, blendBetweenCascadesAmount, blendMultifier);

        float percentLit_blend = ComputePCFPercentLit(nextCascadeIndex, shadowTexcoord_blend);

        percentLit = lerp(percentLit_blend, percentLit, blendBetweenCascadesAmount);
    }
#endif // BLEND_BETWEEN_CASCADE_LAYERS_FLAG

    vec4 litColor = RemapPercentLit(percentLit, NdotL);

#ifdef DEBUG_VISUALIZE_CASCADE_COLOR_FLAG
    vec4 cascadeColor = CascadeColorsMultiplier[cascadeIndex];
    return cascadeColor * litColor;
#else
    return vec4(litColor);
#endif
}

void main() {
    fragColor = ShadowLit();
}
