// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
 
#define CC_MAX_SHADOW_LIGHTS 2

#define CC_SHADOW_TYPE_HARD 2
#define CC_SHADOW_TYPE_SOFT_PCF3X3 3
#define CC_SHADOW_TYPE_SOFT_PCF5X5 4

#define CC_DEFINE_SHADOW_MAP(index) \
  #if CC_NUM_SHADOW_LIGHTS > index \
    #pragma builtin(global) \
    uniform sampler2D cc_shadow_map_##index; \
  #endif

#if CC_USE_SHADOW_MAP && CC_NUM_SHADOW_LIGHTS > 0

  #pragma builtin(global)
  uniform CC_SHADOW {
    mat4 cc_shadow_lightViewProjMatrix[CC_MAX_SHADOW_LIGHTS];
    vec4 cc_shadow_info[CC_MAX_SHADOW_LIGHTS];        // [minDepth, maxDepth, shadow resolution, darkness]
  };

  CC_DEFINE_SHADOW_MAP(0)
  CC_DEFINE_SHADOW_MAP(1)

  varying vec4 v_posLightSpace[CC_MAX_SHADOW_LIGHTS];
  varying float v_depth[CC_MAX_SHADOW_LIGHTS];
#endif

void CCShadowInput (vec3 worldPos) {
  #if CC_USE_SHADOW_MAP && CC_NUM_SHADOW_LIGHTS > 0
  for (int i = 0; i < CC_NUM_SHADOW_LIGHTS; i++) {
    v_posLightSpace[i] = cc_shadow_lightViewProjMatrix[i] * vec4(worldPos, 1.0);
    v_depth[i] = (v_posLightSpace[i].z + cc_shadow_info[i].x) / (cc_shadow_info[i].x + cc_shadow_info[i].y);
  }
  #endif
}

#include <packing>

float getDepth(sampler2D shadowMap, vec2 shadowUV) {
    return unpackRGBAToDepth(texture(shadowMap, shadowUV));
}

float computeFallOff(float shadow, vec2 coords, float frustumEdgeFalloff) {
  // float mask = smoothstep(1.0 - frustumEdgeFalloff, 1.0, clamp(dot(coords, coords), 0.0, 1.0));
  // return mix(esm, 1.0, mask);
  return shadow;
}

// standard hard shadow
float shadowSimple(sampler2D shadowMap, vec2 shadowUV, float currentDepth, float darkness) {
  float closestDepth = getDepth(shadowMap, shadowUV);
  return currentDepth > closestDepth  ? 1.0 - darkness : 1.0;
}

// PCF
float shadowPCF3X3(sampler2D shadowMap, vec2 shadowUV, float currentDepth, float darkness, float shadowSize) {
  float shadow = 0.0;
  for (int x = -1; x <= 1; ++x) {
    for (int y = -1; y <= 1; ++y) {
      float closestDepth = getDepth(shadowMap, shadowUV + vec2(x, y) * 1.0/shadowSize);
      shadow += currentDepth > closestDepth  ? 1.0 - darkness : 1.0;
    }
  }
  shadow /= 9.0;
  return shadow;
}
float shadowPCF5X5(sampler2D shadowMap, vec2 shadowUV, float currentDepth, float darkness, float shadowSize) {
  float shadow = 0.0;
  for (int x = -2; x <= 2; ++x) {
    for (int y = -2; y <= 2; ++y) {
      float closestDepth = getDepth(shadowMap, shadowUV + vec2(x, y) * 1.0/shadowSize);
      shadow += currentDepth > closestDepth  ? 1.0 - darkness : 1.0;
    }
  }
  shadow /= 25.0;
  return shadow;
}

#define CC_CALC_SHADOW(index, light) \
  #if CC_USE_SHADOW_MAP && CC_NUM_SHADOW_LIGHTS > index \
    float shadow_##index = 1.0; \
    vec2 projCoords##index = v_posLightSpace[index].xy / v_posLightSpace[index].w; \
    vec2 shadowUV##index = projCoords##index * 0.5 + vec2(0.5); /*(-1, 1) => (0, 1)*/ \
    if (shadowUV##index.x >= 0.0 && shadowUV##index.x <= 1.0 && shadowUV##index.y >= 0.0 && shadowUV##index.y <= 1.0) { \
      float currentDepth##index = clamp(v_depth[index], 0.0, 1.0); \
      #if CC_SHADOW_##index##_TYPE == CC_SHADOW_TYPE_SOFT_PCF3X3 \
        shadow_##index = shadowPCF3X3(cc_shadow_map_##index, shadowUV##index, currentDepth##index, cc_shadow_info[index].w, cc_shadow_info[index].z); \
      #elif CC_SHADOW_##index##_TYPE == CC_SHADOW_TYPE_SOFT_PCF5X5 \
        shadow_##index = shadowPCF5X5(cc_shadow_map_##index, shadowUV##index, currentDepth##index, cc_shadow_info[index].w, cc_shadow_info[index].z); \
      #else \
        shadow_##index = shadowSimple(cc_shadow_map_##index, shadowUV##index, currentDepth##index, cc_shadow_info[index].w); \
      #endif \
      shadow_##index = computeFallOff(shadow_##index, projCoords##index, 0.0); \
    } \
    \
    light.diffuse *= shadow_##index; \
    light.specular *= shadow_##index; \
  #endif