// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.


// TODO: lights uniform should move back to cc-global

#include <builtin/uniforms/cc-shadow>
#if CC_SUPPORT_CASCADED_SHADOW_MAP
  #include <builtin/uniforms/cc-csm>
#endif

#pragma define CC_MAX_LIGHTS 4

#if CC_NUM_LIGHTS > 0

// directional lights
#pragma builtin(global)
uniform CCLIGHTS {
  vec4 cc_lightPositionAndRange[CC_MAX_LIGHTS];    // xyz range
  vec4 cc_lightDirection[CC_MAX_LIGHTS];           // xyz spotAngle
  vec4 cc_lightColor[CC_MAX_LIGHTS];               // xyz spotExp
};

#endif

struct LightInfo {
  vec3 lightDir;
  vec3 radiance;
  vec4 lightColor;
};

// directional light
LightInfo computeDirectionalLighting(
  vec4 lightDirection,
  vec4 lightColor
) {
  LightInfo ret;
  ret.lightDir = -normalize(lightDirection.xyz);
  ret.radiance = lightColor.rgb;
  ret.lightColor = lightColor;
  return ret;
}

// point light
LightInfo computePointLighting(
  vec3 worldPosition,
  vec4 lightPositionAndRange,
  vec4 lightColor
) {
  LightInfo ret;
  vec3 lightDir = lightPositionAndRange.xyz - worldPosition;
  float attenuation = max(0., 1.0 - length(lightDir) / lightPositionAndRange.w);

  ret.lightDir = normalize(lightDir);
  ret.radiance = lightColor.rgb * attenuation;
  ret.lightColor = lightColor;
  return ret;
}

// spot light
LightInfo computeSpotLighting(
  vec3 worldPosition,
  vec4 lightPositionAndRange,
  vec4 lightDirection,
  vec4 lightColor
) {
  LightInfo ret;
  vec3 lightDir = lightPositionAndRange.xyz - worldPosition;
  float attenuation = max(0., 1.0 - length(lightDir) / lightPositionAndRange.w);
  lightDir = normalize(lightDir);
  float cosConeAngle = max(0., dot(lightDirection.xyz, -lightDir));
  cosConeAngle = cosConeAngle < lightDirection.w ? 0. : cosConeAngle;
  cosConeAngle = pow(cosConeAngle, lightColor.w);

  ret.lightDir = lightDir;
  ret.radiance = lightColor.rgb * attenuation * cosConeAngle;
  ret.lightColor = lightColor;
  return ret;
}

struct Lighting {
  vec3 diffuse;
  vec3 specular;
};

#pragma define CC_CALC_LIGHT(index, surface, result, lightFunc, ambientFunc) \
  #if CC_NUM_LIGHTS > index \
    #if CC_LIGHT_##index##_TYPE == 3 \
      result.diffuse += ambientFunc(s, cc_lightColor[index]); \
    #else \
      LightInfo info##index; \
      #if CC_LIGHT_##index##_TYPE == 0 \
        info##index = computeDirectionalLighting(cc_lightDirection[index], cc_lightColor[index]); \
      #elif CC_LIGHT_##index##_TYPE == 1 \
        info##index = computePointLighting(s.position, cc_lightPositionAndRange[index], cc_lightColor[index]); \
      #elif CC_LIGHT_##index##_TYPE == 2 \
        info##index = computeSpotLighting(s.position, cc_lightPositionAndRange[index], cc_lightDirection[index], cc_lightColor[index]); \
      #endif \
      \
      Lighting result##index = lightFunc(surface, info##index); \
      CC_CALC_SHADOW(index, result##index) \
      result.diffuse += result##index.diffuse; \
      result.specular += result##index.specular; \
    #endif \
  #endif

#pragma define CC_CALC_LIGHTS(surface, result, lightFunc, ambientFunc) \
  result.diffuse = vec3(0, 0, 0); \
  result.specular = vec3(0, 0, 0); \
  \
  CC_CALC_LIGHT(0, surface, result, lightFunc, ambientFunc) \
  CC_CALC_LIGHT(1, surface, result, lightFunc, ambientFunc) \
  CC_CALC_LIGHT(2, surface, result, lightFunc, ambientFunc) \
  CC_CALC_LIGHT(3, surface, result, lightFunc, ambientFunc)
