/* SPDX-FileCopyrightText: 2010-2022 Blender Foundation
 *
 * SPDX-License-Identifier: Apache-2.0 */

#pragma once

#include "kernel/geom/object.h"
#include "kernel/globals.h"

#include "kernel/integrator/state.h"

#include "kernel/light/area.h"
#include "kernel/light/background.h"
#include "kernel/light/distant.h"
#include "kernel/light/point.h"
#include "kernel/light/spot.h"
#include "kernel/light/triangle.h"
#include "kernel/sample/lcg.h"
#include "kernel/types.h"

CCL_NAMESPACE_BEGIN

/* Light info. */

ccl_device_inline bool light_select_reached_max_bounces(KernelGlobals kg,
                                                        const int index,
                                                        const int bounce)
{
  return (bounce > kernel_data_fetch(lights, index).max_bounces);
}

/* Light linking. */

ccl_device_inline int light_link_receiver_nee(KernelGlobals kg, const ccl_private ShaderData *sd)
{
#ifdef __LIGHT_LINKING__
  if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING)) {
    return OBJECT_NONE;
  }

  return sd->object;
#else
  return OBJECT_NONE;
#endif
}

ccl_device_inline int light_link_receiver_forward(KernelGlobals kg, IntegratorState state)
{
#ifdef __LIGHT_LINKING__
  if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING)) {
    return OBJECT_NONE;
  }

  return INTEGRATOR_STATE(state, path, mis_ray_object);
#else
  return OBJECT_NONE;
#endif
}

ccl_device_inline bool light_link_light_match(KernelGlobals kg,
                                              const int object_receiver,
                                              const int object_emitter)
{
#ifdef __LIGHT_LINKING__
  if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING)) {
    return true;
  }

  const uint64_t set_membership = kernel_data_fetch(objects, object_emitter).light_set_membership;
  const uint receiver_set = (object_receiver != OBJECT_NONE) ?
                                kernel_data_fetch(objects, object_receiver).receiver_light_set :
                                0;
  return ((uint64_t(1) << uint64_t(receiver_set)) & set_membership) != 0;
#else
  return true;
#endif
}

ccl_device_inline bool light_link_object_match(KernelGlobals kg,
                                               const int object_receiver,
                                               const int object_emitter)
{
#ifdef __LIGHT_LINKING__
  if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING)) {
    return true;
  }

  /* Emitter is OBJECT_NONE when the emitter is a world volume.
   * It is not explicitly linkable to any object, so assume it is coming from the default light
   * set which affects all objects in the scene. */
  if (object_emitter == OBJECT_NONE) {
    return true;
  }

  const uint64_t set_membership = kernel_data_fetch(objects, object_emitter).light_set_membership;
  const uint receiver_set = (object_receiver != OBJECT_NONE) ?
                                kernel_data_fetch(objects, object_receiver).receiver_light_set :
                                0;
  return ((uint64_t(1) << uint64_t(receiver_set)) & set_membership) != 0;
#else
  return true;
#endif
}

/* Sample point on an individual light. */

template<bool in_volume_segment>
ccl_device_inline bool light_sample(KernelGlobals kg,
                                    const int lamp,
                                    const float2 rand,
                                    const float3 P,
                                    const float3 N,
                                    const int shader_flags,
                                    const uint32_t path_flag,
                                    ccl_private LightSample *ls)
{
  const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
  if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
    if (klight->shader_id & SHADER_EXCLUDE_SHADOW_CATCHER) {
      return false;
    }
  }

  const LightType type = (LightType)klight->type;
  ls->type = type;
  ls->shader = klight->shader_id;
  ls->object = klight->object_id;
  ls->prim = lamp;
  ls->group = object_lightgroup(kg, ls->object);

  if (in_volume_segment && (type == LIGHT_DISTANT || type == LIGHT_BACKGROUND)) {
    /* Distant lights in a volume get a dummy sample, position will not actually
     * be used in that case. Only when sampling from a specific scatter position
     * do we actually need to evaluate these. */
    ls->P = zero_float3();
    ls->Ng = zero_float3();
    ls->D = zero_float3();
    ls->pdf = 1.0f;
    ls->eval_fac = 0.0f;
    ls->t = FLT_MAX;
    return true;
  }

  if (type == LIGHT_DISTANT) {
    if (!distant_light_sample(klight, rand, ls)) {
      return false;
    }
  }
  else if (type == LIGHT_BACKGROUND) {
    /* infinite area light (e.g. light dome or env light) */
    const float3 D = -background_light_sample(kg, P, rand, &ls->pdf);

    ls->P = D;
    ls->Ng = D;
    ls->D = -D;
    ls->t = FLT_MAX;
    ls->eval_fac = 1.0f;
  }
  else if (type == LIGHT_SPOT) {
    if (!spot_light_sample<in_volume_segment>(kg, klight, rand, P, N, shader_flags, ls)) {
      return false;
    }
  }
  else if (type == LIGHT_POINT) {
    if (!point_light_sample(klight, rand, P, N, shader_flags, ls)) {
      return false;
    }
  }
  else {
    /* area light */
    if (!area_light_sample<in_volume_segment>(klight, rand, P, ls)) {
      return false;
    }
  }

  return in_volume_segment || (ls->pdf > 0.0f);
}

/* Sample a point on the chosen emitter. */

template<bool in_volume_segment>
ccl_device_noinline bool light_sample(KernelGlobals kg,
                                      const float3 rand_light,
                                      const float time,
                                      const float3 P,
                                      const float3 N,
                                      const int object_receiver,
                                      const int shader_flags,
                                      const int bounce,
                                      const uint32_t path_flag,
                                      ccl_private LightSample *ls)
{
  /* The first two dimensions of the Sobol sequence have better stratification, use them to sample
   * position on the light. */
  const float2 rand = make_float2(rand_light);

  int prim;
  int shader_flag;
  int object_id;
#ifdef __LIGHT_TREE__
  if (kernel_data.integrator.use_light_tree) {
    const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
                                                                           ls->emitter_id);
    prim = kemitter->light.id;
    shader_flag = kemitter->shader_flag;
    object_id = (prim >= 0) ? ls->object : kemitter->object_id;
  }
  else
#endif
  {
    const ccl_global KernelLightDistribution *kdistribution = &kernel_data_fetch(
        light_distribution, ls->emitter_id);
    prim = kdistribution->prim;
    object_id = kdistribution->object_id;
    shader_flag = kdistribution->shader_flag;
  }

  if (!light_link_object_match(kg, object_receiver, object_id)) {
    return false;
  }

  if (prim >= 0) {
    /* Mesh light. */

    /* Exclude synthetic meshes from shadow catcher pass. */
    if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
        !(kernel_data_fetch(object_flag, object_id) & SD_OBJECT_SHADOW_CATCHER))
    {
      return false;
    }

    if (!triangle_light_sample<in_volume_segment>(kg, prim, object_id, rand, time, ls, P)) {
      return false;
    }
    ls->shader |= shader_flag;
  }
  else {
    const int light = ~prim;

    if (UNLIKELY(light_select_reached_max_bounces(kg, light, bounce))) {
      return false;
    }

    if (!light_sample<in_volume_segment>(kg, light, rand, P, N, shader_flags, path_flag, ls)) {
      return false;
    }
  }

  ls->pdf *= ls->pdf_selection;
  return in_volume_segment || (ls->pdf > 0.0f);
}

/* Intersect ray with individual light. */

/* Returns the total number of hits (the input num_hits plus the number of the new intersections).
 */
template<bool is_main_path>
ccl_device_forceinline int lights_intersect_impl(KernelGlobals kg,
                                                 const ccl_private Ray *ccl_restrict ray,
                                                 ccl_private Intersection *ccl_restrict isect,
                                                 const int last_prim,
                                                 const int last_object,
                                                 const int last_type,
                                                 const uint32_t path_flag,
                                                 const uint8_t path_mnee,
                                                 const int receiver_forward,
                                                 ccl_private uint *lcg_state,
                                                 int num_hits)
{
#ifdef __SHADOW_LINKING__
  const bool is_indirect_ray = !(path_flag & PATH_RAY_CAMERA);
#endif

  for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
    const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
    const int object = klight->object_id;

    if (path_flag & PATH_RAY_CAMERA) {
      if (klight->shader_id & SHADER_EXCLUDE_CAMERA) {
        continue;
      }
    }
    else {
      if (!(klight->shader_id & SHADER_USE_MIS)) {
        continue;
      }

#ifdef __MNEE__
      /* This path should have been resolved with mnee, it will
       * generate a firefly for small lights since it is improbable. */
      if ((path_mnee & PATH_MNEE_CULL_LIGHT_CONNECTION) && klight->use_caustics) {
        continue;
      }
#endif
    }

    if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
      if (klight->shader_id & SHADER_EXCLUDE_SHADOW_CATCHER) {
        continue;
      }
    }

#ifdef __SHADOW_LINKING__
    /* For the main path exclude shadow-linked lights if intersecting with an indirect light ray.
     * Those lights are handled via dedicated light intersect and shade kernels.
     * For the shadow path used for the dedicated light shading ignore all non-shadow-linked
     * lights. */
    if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_LINKING) {
      if (is_main_path) {
        if (is_indirect_ray &&
            kernel_data_fetch(objects, object).shadow_set_membership != LIGHT_LINK_MASK_ALL)
        {
          continue;
        }
      }
      else if (kernel_data_fetch(objects, object).shadow_set_membership == LIGHT_LINK_MASK_ALL) {
        continue;
      }
    }
#endif

#ifdef __LIGHT_LINKING__
    /* Light linking. */
    if (!light_link_light_match(kg, receiver_forward, object) && !(path_flag & PATH_RAY_CAMERA)) {
      continue;
    }
#endif

    const LightType type = (LightType)klight->type;
    float t = 0.0f;

    if (type == LIGHT_SPOT) {
      if (!spot_light_intersect(klight, ray, &t)) {
        continue;
      }
    }
    else if (type == LIGHT_POINT) {
      if (!point_light_intersect(klight, ray, &t)) {
        continue;
      }
    }
    else if (type == LIGHT_AREA) {
      if (!area_light_intersect(klight, ray, &t)) {
        continue;
      }
    }
    else if (type == LIGHT_DISTANT) {
      if (is_main_path || ray->tmax != FLT_MAX) {
        continue;
      }
      if (!distant_light_intersect(klight, ray, &t)) {
        continue;
      }
    }
    else {
      continue;
    }

    /* Avoid self-intersections. */
    if (last_prim == lamp && last_object == object && last_type == PRIMITIVE_LAMP) {
      continue;
    }

    ++num_hits;

#ifdef __SHADOW_LINKING__
    if (!is_main_path) {
      /* The non-main rays are only raced by the dedicated light kernel, after the shadow linking
       * feature check. */
      kernel_assert(kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_LINKING);

      if ((isect->prim != PRIM_NONE) && (lcg_step_float(lcg_state) > 1.0f / num_hits)) {
        continue;
      }
    }
    else
#endif
        if (t >= isect->t)
    {
      continue;
    }

    isect->t = t;
    isect->u = 0.0f;
    isect->v = 0.0f;
    isect->type = PRIMITIVE_LAMP;
    isect->prim = lamp;
    isect->object = object;
  }

  return num_hits;
}

/* Lights intersection for the main path.
 * Intersects spot, point, and area lights. */
ccl_device bool lights_intersect(KernelGlobals kg,
                                 IntegratorState state,
                                 const ccl_private Ray *ccl_restrict ray,
                                 ccl_private Intersection *ccl_restrict isect,
                                 const int last_prim,
                                 const int last_object,
                                 const int last_type,
                                 const uint32_t path_flag)
{
  const uint8_t path_mnee = INTEGRATOR_STATE(state, path, mnee);
  const int receiver_forward = light_link_receiver_forward(kg, state);

  lights_intersect_impl<true>(kg,
                              ray,
                              isect,
                              last_prim,
                              last_object,
                              last_type,
                              path_flag,
                              path_mnee,
                              receiver_forward,
                              nullptr,
                              0);

  return isect->prim != PRIM_NONE;
}

/* Lights intersection for the shadow linking.
 * Intersects spot, point, area, and distant lights.
 *
 * Returns the total number of hits (the input num_hits plus the number of the new intersections).
 */
ccl_device int lights_intersect_shadow_linked(KernelGlobals kg,
                                              const ccl_private Ray *ccl_restrict ray,
                                              ccl_private Intersection *ccl_restrict isect,
                                              const int last_prim,
                                              const int last_object,
                                              const int last_type,
                                              const uint32_t path_flag,
                                              const int receiver_forward,
                                              ccl_private uint *lcg_state,
                                              const int num_hits)
{
  return lights_intersect_impl<false>(kg,
                                      ray,
                                      isect,
                                      last_prim,
                                      last_object,
                                      last_type,
                                      path_flag,
                                      PATH_MNEE_NONE,
                                      receiver_forward,
                                      lcg_state,
                                      num_hits);
}

/* Setup light sample from intersection. */

ccl_device LightEval
light_eval_from_intersection(KernelGlobals kg,
                             const ccl_private Intersection *ccl_restrict isect,
                             const float3 ray_P,
                             const float3 ray_D,
                             const float3 N,
                             const uint32_t path_flag)
{
  const ccl_global KernelLight *klight = &kernel_data_fetch(lights, isect->prim);
  const LightType type = (LightType)klight->type;

  if (type == LIGHT_SPOT) {
    return spot_light_eval_from_intersection(kg, klight, ray_P, ray_D, isect->t, N, path_flag);
  }
  if (type == LIGHT_POINT) {
    return point_light_eval_from_intersection(klight, ray_P, ray_D, isect->t, N, path_flag);
  }
  if (type == LIGHT_AREA) {
    return area_light_eval_from_intersection(klight, ray_P, ray_D, isect->t);
  }

  kernel_assert(!"Invalid lamp type in light_eval_from_intersection");
  return LightEval{};
}

/* Get light coordinates from position on light. */
ccl_device void light_normal_uv_from_position(KernelGlobals kg,
                                              const ccl_global KernelLight *klight,
                                              const float3 P,
                                              const float3 D,
                                              ccl_private float3 &Ng,
                                              ccl_private float2 &uv)
{
  const LightType type = (LightType)klight->type;

  if (type == LIGHT_SPOT) {
    Ng = (klight->spot.is_sphere) ? normalize(P - klight->co) : -D;
    const float3 local_ray = spot_light_to_local(kg, klight, -D);
    uv = spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle);
  }
  else if (type == LIGHT_POINT) {
    Ng = (klight->spot.is_sphere) ? normalize(P - klight->co) : -D;
    uv = point_light_uv(kg, klight, Ng);
  }
  else if (type == LIGHT_AREA) {
    Ng = klight->area.dir;
    uv = area_light_uv(klight, P);
  }
  else if (type == LIGHT_DISTANT) {
    Ng = -D;
    uv = distant_light_uv(kg, klight, D);
  }
  else {
    kernel_assert(0);
  }
}

CCL_NAMESPACE_END
