#include "depth_normal.fsh"
#include "LightsCommon.sh"

#ifdef _SHADERC_PRESET
#define SPOTLIGHT_SHAPE
#endif // _SHADERC_PRESET

//
// Light models
//
struct Light {
    half3 direction;
    half3 color;
    float distanceAttenuation;
};

float3 GetLightPositionWorldSpace(int perObjectLightIndex);

// Attenuation smoothly decreases to light range.
float DistanceAttenuation(float distanceSqr, float2 distanceAttenuation) {
    // We use a shared distance attenuation for additional directional and puctual lights
    // for directional lights attenuation will be 1
    float lightAtten = rcp(distanceSqr);

#if SHADER_HINT_NICE_QUALITY
    // Use the smoothing factor also used in the Unity lightmapper.
    half factor = distanceSqr * distanceAttenuation.x;
    half smoothFactor = saturate(inl_half(1.0) - factor * factor);
    smoothFactor = smoothFactor * smoothFactor;
#else
    // We need to smoothly fade attenuation to light range. We start fading linearly at 80% of light range
    // Therefore:
    // fadeDistance = (0.8 * 0.8 * lightRangeSq)
    // smoothFactor = (lightRangeSqr - distanceSqr) / (lightRangeSqr - fadeDistance)
    // We can rewrite that to fit a MAD by doing
    // distanceSqr * (1.0 / (fadeDistanceSqr - lightRangeSqr)) + (-lightRangeSqr / (fadeDistanceSqr - lightRangeSqr)
    // distanceSqr *        distanceAttenuation.y            +             distanceAttenuation.z
    float smoothFactor = saturate(distanceSqr * distanceAttenuation.x + distanceAttenuation.y);
#endif

    return lightAtten * smoothFactor;
}

half AngleAttenuation(half3 spotDirection, half3 lightDirection, half2 spotAttenuation)
{
    // Spot Attenuation with a linear falloff can be defined as
    // (SdotL - cosOuterAngle) / (cosInnerAngle - cosOuterAngle)
    // This can be rewritten as
    // invAngleRange = 1.0 / (cosInnerAngle - cosOuterAngle)
    // SdotL * invAngleRange + (-cosOuterAngle * invAngleRange)
    // SdotL * spotAttenuation.x + spotAttenuation.y

    // If we precompute the terms in a MAD instruction
    half SdotL = dot(spotDirection, lightDirection);
    half atten = saturate(SdotL * spotAttenuation.x + spotAttenuation.y);
    return atten * atten;
}

Light GetAdditionalPerObjectLight(int perObjectLightIndex, float3 positionWS) {

#if BATCH_LIGHTS_DRAWCALL
    float4x4 data = LightsBuffer.data[perObjectLightIndex];
#else
    float4x4 data = LightsBuffer;
#endif

    float4 lightPositionWS = float4(GetLightPositionWorldSpace(perObjectLightIndex), 1.0);
    half3 color = data[1].xyz;
    float4 distanceAndSpotAttenuation = data[2];
    half4 spotDirection = data[3];

    // Directional lights store direction in lightPosition.xyz and have .w set to 0.0.
    // This way the following code will work for both directional and punctual lights.

    // Note: we scale light and range in world space
    float3 lightVector = (lightPositionWS.xyz - positionWS) * data[0].z;
    float distanceSqr = max(dot(lightVector, lightVector), HALF_MIN);

    half3 lightDirection = lightVector * rsqrt(distanceSqr);
#ifdef SPOTLIGHT_SHAPE
    float attenuation = DistanceAttenuation(distanceSqr, distanceAndSpotAttenuation.xy)
        * AngleAttenuation(spotDirection.xyz, lightDirection, distanceAndSpotAttenuation.zw);
#else
    float attenuation = DistanceAttenuation(distanceSqr, distanceAndSpotAttenuation.xy);
#endif

    Light light;
    light.direction = lightDirection;
    light.distanceAttenuation = attenuation;
    light.color = color;

    return light;
}


//
// Shading models
//
struct PixelParams {
    float3 positionWS;
    half3 normalWS;
    half depthMask; // To exclude pixel on far plane
};

uniform float4 ViewportSizeParams;
uniform float4 ScreenToViewParams; // Pack ruler: u scale, v scale, u offset, v offset
uniform float2 ZDecodeParams; // Pack ruler: z linear scale i.e. far - near, z linear offset i.e. near
uniform float4x4 ViewToWorldMatrix;

const float DepthTestMaxThreshold = 0.9999;

flat in float4 vs_lightInstancePosWorldSpace;

layout(location = 0) out float4 fs_color;

float DepthTest(float z) {
    return step(z, DepthTestMaxThreshold);
}

float2 GetPixelCoord() {
    float2 pc = gl_FragCoord.xy;
    float2 uv = pc * ViewportSizeParams.zw;
    return uv;
}

float3 GetLightPositionWorldSpace(int perObjectLightIndex) {
    return vs_lightInstancePosWorldSpace.xyz;
}

#if BATCH_LIGHTS_DRAWCALL
int GetLightInstanceIndex() {
    return asint(vs_lightInstancePosWorldSpace.w);
}
#endif

PixelParams GetPixelParams(float2 uv) {

    float z;
    half3 normal;

    _SampleDepthNormal(uv, z, normal);
    // Position in view space
    float zViewSpace = z * ZDecodeParams.x + ZDecodeParams.y;
    float4 pos = float4(uv * ScreenToViewParams.xy + ScreenToViewParams.zw, -1.0, 1.0);
    pos.xyz *= zViewSpace;

    // Position and normal in world space
    pos = ViewToWorldMatrix * pos;
    half3x3 ViewToWorldMatrixN = float3x3(ViewToWorldMatrix);
    normal = ViewToWorldMatrixN * normal;

    PixelParams pixel;
    pixel.positionWS = pos.xyz;
    pixel.normalWS = normalize(normal);
    pixel.depthMask = DepthTest(z);

    return pixel;
}

float4 EvalPunctualLighting(PixelParams pixel, Light light) {

    // For now, we only evaluate diffuse term
    float NdotL = dot(pixel.normalWS, light.direction);
    float V = saturate(NdotL);
    float3 diffuse = light.color * (V * pixel.depthMask * light.distanceAttenuation);

    return float4(diffuse, 1.0);
}

void main() {
#if BATCH_LIGHTS_DRAWCALL
    int lightIndex = GetLightInstanceIndex();
#else
    const int lightIndex = 0;
#endif

    PixelParams pixel = GetPixelParams( GetPixelCoord() );

    Light light = GetAdditionalPerObjectLight(lightIndex, pixel.positionWS);

    float4 diffuse = EvalPunctualLighting(pixel, light);

    fs_color = float4(diffuse.rgb, 1.0);
}