#include "depth_normal.fsh"
precision mediump sampler2DShadow;
// #define PCF_USE_BIG_KERNEL_DEPTH_BIAS
#include "ShadowSamplingFilter.fsh"

/**
 * Preset for SSM/LispSM/TSM lighting
 */
///{
// 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 SHADOW_SAMPLING_FILTER_ENUM 2
    #define DEBUG_VISUALIZE_CASCADE_COLOR_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

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

uniform vec4 BorderPaddingBoundsStep;

uniform vec2 VertexBiasFactors;

// matrix that transforms position in view space to UVW in light 'texture space'
uniform mat4 ViewToShadowTextureMatrix;

const vec4 DebugColorsMultiplier[2] = vec4[2](
    vec4( 0.9, 0.6, 0.6, 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;
}

float IsAcceptShadow(float depthNormalized) {
    return step(depthNormalized, 0.9999);
}

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

    normalViewSpace = _SampleNormal(uv);

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


    // For pixel has default depth '1', it is pixel area which do not recieve shadow or skydrome.
    // TODO: if we just revert depth 'depthNormalized' from '1' to '0', the pixel may has no excluder
    // in front of it in light direction, but it may not cover all the cases.

    return IsAcceptShadow(depthNormalized);
}

float TestWithinShadowMap_float(vec4 shadowTexcoord) {

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

bool TestWithinShadowMap_bool(vec4 shadowTexcoord) {
    return all(lessThanEqual(BorderPaddingBoundsStep, 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 ApplyAdaptiveVertexBias(float NdotL, vec3 normalViewSpace, inout vec4 shadowTexcoord) {

#if 1
    float shadowBiasGeometryFactor = 1.0 - NdotL;
    vec2 shadowBiasScale = vec2(VertexBiasFactors.x * shadowBiasGeometryFactor, VertexBiasFactors.y);
    vec4 vertexOffset = shadowBiasScale.x * (ViewToShadowTextureMatrix * vec4(normalViewSpace, 0.0));
    vertexOffset.z -= shadowBiasScale.y;

    shadowTexcoord += vertexOffset;
#else
    // Optimal Depth Bias
    float optimalDepthBias;
    {
        vec2 deltaXY1 = (fract(shadowTexcoord.xy * ShadowMapTexelTextureSize.zw) + 2.f) * ShadowMapTexelTextureSize.xy;
        vec3 ddsP1    = dFdx(shadowTexcoord.xyz);
        vec3 ddtP1    = dFdy(shadowTexcoord.xyz);

        mat2x2 affineMat = mat2x2(
            ddtP1.y, -ddsP1.y, -ddtP1.x, ddsP1.x);
        float detAffineMat = max(abs(determinant(affineMat)), 1.0e-8);
        float rcpDetAffineMat = lerp(0.f, 1.f / detAffineMat, step(1.0e-7, detAffineMat));

        vec2 deltaST     =  (affineMat * deltaXY1) * rcpDetAffineMat;
        vec2 deltaZ      = vec2(ddsP1.z, ddtP1.z) * deltaST;
        optimalDepthBias = abs(deltaZ.x) + abs(deltaZ.y);
    }
    shadowTexcoord.z -= optimalDepthBias;

    shadowTexcoord.z -= VertexBiasFactors.y;
#endif
}

#ifdef DEBUG_VISUALIZE_CASCADE_COLOR_FLAG
int ComputeShadowTexcoord(vec4 posViewSpace, vec3 normalViewSpace, float NdotL, out vec4 shadowTexcoord) {
    shadowTexcoord = ViewToShadowTextureMatrix * posViewSpace;

    ApplyAdaptiveVertexBias(NdotL, normalViewSpace, shadowTexcoord);

    shadowTexcoord /= shadowTexcoord.w;

    float indexf = TestWithinShadowMap_float(shadowTexcoord);
    shadowTexcoord = lerp(vec4(0.0, 0.0, 0.0, 1.0), shadowTexcoord, indexf);
    return int(1.0 - indexf);
}
#else
vec4 ComputeShadowTexcoord(vec4 posViewSpace, vec3 normalViewSpace, float NdotL) {

    vec4 shadowTexcoord = ViewToShadowTextureMatrix * posViewSpace;

    ApplyAdaptiveVertexBias(NdotL, normalViewSpace, shadowTexcoord);

    shadowTexcoord /= shadowTexcoord.w;

    shadowTexcoord = lerp(vec4(0.0, 0.0, -1.0, 1.0), shadowTexcoord,
            TestWithinShadowMap_float(shadowTexcoord));

    return shadowTexcoord;
}
#endif

float2 GetPixelPositionScreenSpace() {
    return gl_FragCoord.xy;
}

float ComputePCFPercentLit(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;
}
///
/// Tune shadow intensity
///
vec4 RemapPercentLit(float light, float NdotL) {

    //
    // Remove weak light, which can be generated by front face;
    // TODO: PCF导致单独为中心像素应用normal bias无法彻底消除错误，从而产生灰色蒙层。
    // 如果能够优化，将会不再需要remap；
    //
    float scale = smoothstep(0.0, 0.4, NdotL) * 3.0;
    light = min(light * scale, 1.0);

    /*
    // 背面不接受阴影，避免角度较小时闪烁，应当令
    // dot = 0.1 -> light = light；
    // dot = .05 -> 1ight = 1.0;
    light = remap(NdotL, 0.05, 0.1, 1.0, light);
    */

    return vec4(light);
}

vec4 ShadowLit() {

    vec4 posViewSpace;
    vec3 normalViewSpace;

    float isAcceptShadow = GetPositionAndNormalViewSpace(UV_f, posViewSpace, normalViewSpace);

    float NdotL = saturate(dot(normalViewSpace, PixelToLightDirViewSpace));

#ifdef DEBUG_VISUALIZE_CASCADE_COLOR_FLAG
    vec4 shadowTexcoord;
    int cascadeIndex;
    cascadeIndex = ComputeShadowTexcoord(posViewSpace, normalViewSpace, NdotL, shadowTexcoord);
#else
    vec4 shadowTexcoord = ComputeShadowTexcoord(posViewSpace, normalViewSpace, NdotL);
#endif

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

    vec4 litColor = RemapPercentLit(percentLit, NdotL);

    litColor = mix(vec4(1.0), litColor, isAcceptShadow);

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

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