#include "common.h"

#define LQMAT_FADE_FACTOR (1.0f/300.0f)

struct Appdata
{
    float4 Position     : POSITION;
    float3 Normal       : NORMAL;
    float2 Uv           : TEXCOORD0;
    float2 UvStuds      : TEXCOORD1;

    float4 Color : COLOR0;
    ATTR_INT4 Extra : COLOR1;

#ifdef PIN_SURFACE
    float3 Tangent       : TEXCOORD2;
#endif
    float4 EdgeDistances : TEXCOORD3;
};

struct VertexOutput
{
    float4 Uv_EdgeDistance1 : TEXCOORD0;
	float4 UvStuds_EdgeDistance2 : TEXCOORD1;

    float4 Color             : COLOR0;
    float4 LightPosition_Fog : TEXCOORD2;

    float4 View_Depth : TEXCOORD3;
	#if defined(PIN_HQ) || defined(PIN_REFLECTION)
        float4 Normal_SpecPower  : TEXCOORD4;
    #endif
  
    #ifdef PIN_SURFACE
        float3 Tangent  : TEXCOORD5;
    #else
        float4 Diffuse_Specular  : COLOR1;
    #endif

    float4 PosLightSpace_Reflectance: TEXCOORD6;
};

#ifdef PIN_SKINNED
    WORLD_MATRIX_ARRAY(WorldMatrixArray, MAX_BONE_COUNT * 3);
#endif

#ifdef PIN_DEBUG
uniform float4 DebugColor;
#endif

VertexOutput DefaultVS(Appdata IN, out float4 HPosition: POSITION)
{
    VertexOutput OUT = (VertexOutput)0;

    // Transform position and normal to world space
#ifdef PIN_SKINNED
    int boneIndex = IN.Extra.r;

    float4 worldRow0 = WorldMatrixArray[boneIndex * 3 + 0];
    float4 worldRow1 = WorldMatrixArray[boneIndex * 3 + 1];
    float4 worldRow2 = WorldMatrixArray[boneIndex * 3 + 2];
		
	float3 posWorld = float3(dot(worldRow0, IN.Position), dot(worldRow1, IN.Position), dot(worldRow2, IN.Position));
    float3 normalWorld = float3(dot(worldRow0.xyz, IN.Normal), dot(worldRow1.xyz, IN.Normal), dot(worldRow2.xyz, IN.Normal));
#else
	float3 posWorld = IN.Position.xyz;
    float3 normalWorld = IN.Normal;
#endif

    // Decode diffuse/specular parameters; encoding depends on the skinned flag due to vertex declaration differences
#if defined(PIN_DEBUG)
    float4 color = DebugColor;
#else
    float4 color = IN.Color;
#endif

    float specularIntensity = IN.Extra.g / 255.f;
    float specularPower = IN.Extra.b;

    float ndotl = dot(normalWorld, -G(Lamp0Dir));

#ifdef PIN_HQ
    // We'll calculate specular in pixel shader
    float2 lt = float2(saturate(ndotl), (ndotl > 0));
#else
    // Using lit here improves performance on software vertex shader implementations
    float2 lt = lit(ndotl, dot(normalize(-G(Lamp0Dir) + normalize(G(CameraPosition).xyz - posWorld.xyz)), normalWorld), specularPower).yz;
#endif

	HPosition = mul(G(ViewProjection), float4(posWorld, 1));

	OUT.Uv_EdgeDistance1.xy = IN.Uv;
	OUT.UvStuds_EdgeDistance2.xy = IN.UvStuds;
	
    OUT.Color = color;
    OUT.LightPosition_Fog = float4(lgridPrepareSample(lgridOffset(posWorld, normalWorld)), (G(FogParams).z - HPosition.w) * G(FogParams).w);

	OUT.View_Depth = float4(G(CameraPosition).xyz - posWorld, HPosition.w);

    #if defined(PIN_HQ) || defined(PIN_REFLECTION)
	    float4 edgeDistances = IN.EdgeDistances*G(FadeDistance_GlowFactor).z + 0.5 * OUT.View_Depth.w  * G(FadeDistance_GlowFactor).y;
		
	    OUT.Uv_EdgeDistance1.zw = edgeDistances.xy;
	    OUT.UvStuds_EdgeDistance2.zw = edgeDistances.zw;
        OUT.Normal_SpecPower = float4(normalWorld, specularPower);
        OUT.PosLightSpace_Reflectance.w = IN.Extra.a / 255.f;
    #endif

    #ifdef PIN_SURFACE
        #ifdef PIN_SKINNED
            float3 tangent = float3(dot(worldRow0.xyz, IN.Tangent), dot(worldRow1.xyz, IN.Tangent), dot(worldRow2.xyz, IN.Tangent));
        #else
            float3 tangent = IN.Tangent;
        #endif

        OUT.Tangent = tangent;
    #else
        float3 diffuse = lt.x * G(Lamp0Color) + max(-ndotl, 0) * G(Lamp1Color);

        OUT.Diffuse_Specular = float4(diffuse, lt.y * specularIntensity);
    #endif

    OUT.PosLightSpace_Reflectance.xyz = shadowPrepareSample(posWorld);

	return OUT;
}

#ifdef PIN_SURFACE
struct SurfaceInput
{
    float4 Color;
    float2 Uv;
    float2 UvStuds;

#ifdef PIN_REFLECTION
    float Reflectance;
#endif
};

struct Surface
{
    float3 albedo;
    float3 normal;
    float specular;
    float gloss;
    float reflectance;
};

Surface surfaceShader(SurfaceInput IN, float2 fade);

Surface surfaceShaderExec(VertexOutput IN)
{
    SurfaceInput SIN;
    SIN.Color = IN.Color;
    SIN.Uv = IN.Uv_EdgeDistance1.xy;
    SIN.UvStuds = IN.UvStuds_EdgeDistance2.xy;

    #ifdef PIN_REFLECTION
        SIN.Reflectance = IN.PosLightSpace_Reflectance.w;
    #endif

    float2 fade;
	fade.x = saturate0(1 - IN.View_Depth.w * LQMAT_FADE_FACTOR );
	fade.y = saturate0(1 - IN.View_Depth.w * G(FadeDistance_GlowFactor).y );

    return surfaceShader(SIN, fade);
}
#endif

TEX_DECLARE2D(StudsMap, 0);

LGRID_SAMPLER(LightMap, 1);
TEX_DECLARE2D(LightMapLookup, 2);

TEX_DECLARE2D(ShadowMap, 3);

TEX_DECLARECUBE(EnvironmentMap, 4);

TEX_DECLARE2D(DiffuseMap, 5);
TEX_DECLARE2D(NormalMap, 6);
TEX_DECLARE2D(SpecularMap, 7);

#ifndef GLSLES
TEX_DECLARE2D(NormalDetailMap, 8);
#endif

uniform float4 LqmatFarTilingFactor; // material tiling factor for low-quality shader, must be the same as CFG_FAR_TILING


float4 sampleFar1(TEXTURE_IN_2D(s), float2 uv, float fade, float cutoff)
{
#ifdef GLSLES
    return tex2D(s, uv);
#else
    if (cutoff == 0)
        return tex2D(s, uv);
    else
    {
        float cscale = 1 / (1 - cutoff);

        return lerp(tex2D(s, uv * (LqmatFarTilingFactor.xy) ), tex2D(s, uv ), saturate0(fade * cscale - cutoff * cscale));
    }
#endif
}

#if defined(PIN_WANG) || defined(PIN_WANG_FALLBACK)
float4 sampleWangSimple(TEXTURE_IN_2D(s), float2 uv)
{
    float2 wangUv;
    float4 wangUVDerivatives;
    getWang(TEXTURE_WANG(NormalDetailMap), uv, 1, wangUv, wangUVDerivatives);
    return sampleWang(TEXTURE(s), wangUv, wangUVDerivatives);
}
#endif

void DefaultPS(VertexOutput IN,
#ifdef PIN_GBUFFER
    out float4 oColor1: COLOR1,
#endif
    out float4 oColor0: COLOR0)
{
    // Compute albedo term
#ifdef PIN_SURFACE
    Surface surface = surfaceShaderExec(IN);

    float4 albedo = float4(surface.albedo, IN.Color.a);

    float3 bitangent = cross(IN.Normal_SpecPower.xyz, IN.Tangent.xyz);
    float3 normal = normalize(surface.normal.x * IN.Tangent.xyz + surface.normal.y * bitangent + surface.normal.z * IN.Normal_SpecPower.xyz);

    float ndotl = dot(normal, -G(Lamp0Dir));

    float3 diffuseIntensity = saturate0(ndotl) * G(Lamp0Color) + max(-ndotl, 0) * G(Lamp1Color);
    float specularIntensity = step(0, ndotl) * surface.specular;
    float specularPower = surface.gloss;

    float reflectance = surface.reflectance;
#elif PIN_LOWQMAT

#ifndef CFG_FAR_DIFFUSE_CUTOFF
#define CFG_FAR_DIFFUSE_CUTOFF (0.6f)
#endif

#if defined(PIN_WANG) || defined(PIN_WANG_FALLBACK)
	float4 albedo = sampleWangSimple(TEXTURE(DiffuseMap), IN.Uv_EdgeDistance1.xy);
#else
    float fade = saturate0(1 - IN.View_Depth.w * LQMAT_FADE_FACTOR);
    float4 albedo = sampleFar1(TEXTURE(DiffuseMap), IN.Uv_EdgeDistance1.xy, fade, CFG_FAR_DIFFUSE_CUTOFF);
#endif

	albedo.rgb = lerp(float3(1, 1, 1), IN.Color.rgb, albedo.a ) * albedo.rgb;
	albedo.a   = IN.Color.a;

	float3 diffuseIntensity = IN.Diffuse_Specular.xyz;
    float specularIntensity = IN.Diffuse_Specular.w;
	float reflectance = 0;

#else
    #ifdef PIN_PLASTIC
        float4 studs = tex2D(StudsMap, IN.UvStuds_EdgeDistance2.xy);
        float4 albedo = float4(IN.Color.rgb * (studs.r * 2), IN.Color.a);
	#else
        float4 albedo = tex2D(DiffuseMap, IN.Uv_EdgeDistance1.xy) * IN.Color;
	#endif

    #ifdef PIN_HQ
        float3 normal = normalize(IN.Normal_SpecPower.xyz);
        float specularPower = IN.Normal_SpecPower.w;
    #elif defined(PIN_REFLECTION)
        float3 normal = IN.Normal_SpecPower.xyz;
    #endif

    float3 diffuseIntensity = IN.Diffuse_Specular.xyz;
    float specularIntensity = IN.Diffuse_Specular.w;

    #ifdef PIN_REFLECTION
        float reflectance = IN.PosLightSpace_Reflectance.w;
    #endif

#endif

    float4 light = lgridSample(TEXTURE(LightMap), TEXTURE(LightMapLookup), IN.LightPosition_Fog.xyz);
	float shadow = shadowSample(TEXTURE(ShadowMap), IN.PosLightSpace_Reflectance.xyz, light.a);

    // Compute reflection term
#if defined(PIN_SURFACE) || defined(PIN_REFLECTION)
    float3 reflection = texCUBE(EnvironmentMap, reflect(-IN.View_Depth.xyz, normal)).rgb;

    albedo.rgb = lerp(albedo.rgb, reflection.rgb, reflectance);
#endif
    
    // Compute diffuse term
    float3 diffuse = (G(AmbientColor) + diffuseIntensity * shadow + light.rgb) * albedo.rgb;

    // Compute specular term
#ifdef PIN_HQ
    float3 specular = G(Lamp0Color) * (specularIntensity * shadow * (float)(half)pow(saturate(dot(normal, normalize(-G(Lamp0Dir) + normalize(IN.View_Depth.xyz)))), specularPower));
#else
    float3 specular = G(Lamp0Color) * (specularIntensity * shadow);
#endif

    // Combine
    oColor0.rgb = diffuse.rgb + specular.rgb;
    oColor0.a   = albedo.a;

#ifdef PIN_HQ
	float ViewDepthMul = IN.View_Depth.w * G(FadeDistance_GlowFactor).y;
	float outlineFade = saturate1( ViewDepthMul * G(OutlineBrightness_ShadowInfo).x + G(OutlineBrightness_ShadowInfo).y);
	float2 minIntermediate = min(IN.Uv_EdgeDistance1.wz, IN.UvStuds_EdgeDistance2.wz);
	float minEdgesPlus = min(minIntermediate.x, minIntermediate.y) / ViewDepthMul;
	oColor0.rgb *= saturate1(outlineFade *(1.5 - minEdgesPlus) + minEdgesPlus);
#endif

    float fogAlpha = saturate(IN.LightPosition_Fog.w);

#ifdef PIN_NEON
    oColor0.rgb = IN.Color.rgb * G(FadeDistance_GlowFactor).w;
    oColor0.a = 1 - fogAlpha * IN.Color.a;

    // disable SSAO by setting diffuse/specular to 1
    diffuse.rgb = specular.rgb = 1;
#endif

    oColor0.rgb = lerp(G(FogColor), oColor0.rgb, fogAlpha);

#ifdef PIN_GBUFFER
    oColor1 = gbufferPack(IN.View_Depth.w, diffuse.rgb, specular.rgb, fogAlpha);
#endif

}
