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

#pragma once

#include "kernel/integrator/state.h"

#include "kernel/sample/pattern.h"

CCL_NAMESPACE_BEGIN

/* Initialize queues, so that this path is considered terminated.
 * Used for early outputs in the camera ray initialization, as well as initialization of split
 * states for shadow catcher. */
ccl_device_inline void path_state_init_queues(IntegratorState state)
{
  INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0;
#ifndef __KERNEL_GPU__
  INTEGRATOR_STATE_WRITE(&state->shadow, shadow_path, queued_kernel) = 0;
  INTEGRATOR_STATE_WRITE(&state->ao, shadow_path, queued_kernel) = 0;
#endif
}

/* Minimalistic initialization of the path state, which is needed for early outputs in the
 * integrator initialization to work. */
ccl_device_inline void path_state_init(IntegratorState state,
                                       const ccl_global KernelWorkTile *ccl_restrict tile,
                                       const int x,
                                       const int y)
{
  const uint render_pixel_index = (uint)tile->offset + x + y * tile->stride;

  INTEGRATOR_STATE_WRITE(state, path, render_pixel_index) = render_pixel_index;

  path_state_init_queues(state);
}

/* Initialize the rest of the path state needed to continue the path integration. */
ccl_device_inline void path_state_init_integrator(KernelGlobals kg,
                                                  IntegratorState state,
                                                  const int sample,
                                                  const uint rng_pixel,
                                                  const Spectrum throughput)
{
  INTEGRATOR_STATE_WRITE(state, path, sample) = sample;
  INTEGRATOR_STATE_WRITE(state, path, bounce) = 0;
  INTEGRATOR_STATE_WRITE(state, path, diffuse_bounce) = 0;
  INTEGRATOR_STATE_WRITE(state, path, glossy_bounce) = 0;
  INTEGRATOR_STATE_WRITE(state, path, transmission_bounce) = 0;
  INTEGRATOR_STATE_WRITE(state, path, transparent_bounce) = 0;
  INTEGRATOR_STATE_WRITE(state, path, volume_bounce) = 0;
  INTEGRATOR_STATE_WRITE(state, path, volume_bounds_bounce) = 0;
  if ((kernel_data.kernel_features & KERNEL_FEATURE_NODE_PORTAL)) {
    INTEGRATOR_STATE_WRITE(state, path, portal_bounce) = 0;
  }
  INTEGRATOR_STATE_WRITE(state, path, rng_pixel) = rng_pixel;
  INTEGRATOR_STATE_WRITE(state, path, rng_offset) = PRNG_BOUNCE_NUM;
  INTEGRATOR_STATE_WRITE(state, path, flag) = PATH_RAY_CAMERA | PATH_RAY_MIS_SKIP |
                                              PATH_RAY_TRANSPARENT_BACKGROUND;
  INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = 0.0f;
  INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = FLT_MAX;
  INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = 1.0f;
  INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput;
  INTEGRATOR_STATE_WRITE(state, path, optical_depth) = 0.0f;
#if defined(__PATH_GUIDING__)
  if ((kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING)) {
    INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) = 1.0f;
    INTEGRATOR_STATE_WRITE(state, guiding, path_segment) = nullptr;
    INTEGRATOR_STATE_WRITE(state, guiding, use_surface_guiding) = false;
    INTEGRATOR_STATE_WRITE(state, guiding, sample_surface_guiding_rand) = 0.5f;
    INTEGRATOR_STATE_WRITE(state, guiding, surface_guiding_sampling_prob) = 0.0f;
    INTEGRATOR_STATE_WRITE(state, guiding, bssrdf_sampling_prob) = 0.0f;
    INTEGRATOR_STATE_WRITE(state, guiding, use_volume_guiding) = false;
    INTEGRATOR_STATE_WRITE(state, guiding, sample_volume_guiding_rand) = 0.5f;
    INTEGRATOR_STATE_WRITE(state, guiding, volume_guiding_sampling_prob) = 0.0f;
  }
#endif
#ifdef __MNEE__
  INTEGRATOR_STATE_WRITE(state, path, mnee) = 0;
#endif

  INTEGRATOR_STATE_WRITE(state, isect, object) = OBJECT_NONE;
  INTEGRATOR_STATE_WRITE(state, isect, prim) = PRIM_NONE;
  INTEGRATOR_STATE_WRITE(state, isect, type) = PRIMITIVE_NONE;

  if (kernel_data.kernel_features & KERNEL_FEATURE_VOLUME) {
    INTEGRATOR_STATE_ARRAY_WRITE(
        state, volume_stack, 0, object) = kernel_data.background.object_index;
    INTEGRATOR_STATE_ARRAY_WRITE(
        state, volume_stack, 0, shader) = kernel_data.background.volume_shader;
    INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 1, object) = OBJECT_NONE;
    INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 1, shader) = SHADER_NONE;
  }

#ifdef __DENOISING_FEATURES__
  if (kernel_data.kernel_features & KERNEL_FEATURE_DENOISING) {
    INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_DENOISING_FEATURES;
    INTEGRATOR_STATE_WRITE(state, path, denoising_feature_throughput) = one_spectrum();
  }
#endif

#ifdef __LIGHT_LINKING__
  if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING) {
    INTEGRATOR_STATE_WRITE(state, path, mis_ray_object) = OBJECT_NONE;
  }
#endif
}

ccl_device_inline void path_state_next(KernelGlobals kg,
                                       IntegratorState state,
                                       const int label,
                                       const int shader_flag)
{
  uint32_t flag = INTEGRATOR_STATE(state, path, flag);

  /* ray through transparent keeps same flags from previous ray and is
   * not counted as a regular bounce, transparent has separate max */
  if (label & (LABEL_TRANSPARENT | LABEL_RAY_PORTAL)) {
    const int transparent_bounce = INTEGRATOR_STATE(state, path, transparent_bounce) + 1;

    flag |= PATH_RAY_TRANSPARENT;
    if (transparent_bounce >= kernel_data.integrator.transparent_max_bounce) {
      /* FIXME: `transparent_max_bounce` could be 0, but `transparent_bounce` is at least 1 when we
       * enter this path. */
      flag |= PATH_RAY_TERMINATE_ON_NEXT_SURFACE;
    }

    if (shader_flag & SD_RAY_PORTAL) {
      flag |= PATH_RAY_MIS_SKIP;
      INTEGRATOR_STATE_WRITE(
          state, path, portal_bounce) = INTEGRATOR_STATE(state, path, portal_bounce) + 1;
    }

    INTEGRATOR_STATE_WRITE(state, path, flag) = flag;
    INTEGRATOR_STATE_WRITE(state, path, transparent_bounce) = transparent_bounce;
    /* Random number generator next bounce. */
    INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
    return;
  }

  const int bounce = INTEGRATOR_STATE(state, path, bounce) + 1;
  if (bounce >= kernel_data.integrator.max_bounce) {
    flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
  }

  flag &= ~(PATH_RAY_ALL_VISIBILITY | PATH_RAY_MIS_SKIP | PATH_RAY_MIS_HAD_TRANSMISSION);

#ifdef __VOLUME__
  if (label & LABEL_VOLUME_SCATTER) {
    /* volume scatter */
    flag |= PATH_RAY_VOLUME_SCATTER | PATH_RAY_MIS_HAD_TRANSMISSION;
    flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;
    if (!(flag & PATH_RAY_ANY_PASS)) {
      flag |= PATH_RAY_VOLUME_PASS;
    }

    const int volume_bounce = INTEGRATOR_STATE(state, path, volume_bounce) + 1;
    INTEGRATOR_STATE_WRITE(state, path, volume_bounce) = volume_bounce;
    if (volume_bounce >= kernel_data.integrator.max_volume_bounce) {
      flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
    }

    if (bounce == 1) {
      flag &= ~PATH_RAY_VOLUME_PRIMARY_TRANSMIT;
    }
  }
  else
#endif
  {
    /* surface reflection/transmission */
    if (label & LABEL_REFLECT) {
      flag |= PATH_RAY_REFLECT;
      flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;

      if (label & LABEL_DIFFUSE) {
        const int diffuse_bounce = INTEGRATOR_STATE(state, path, diffuse_bounce) + 1;
        INTEGRATOR_STATE_WRITE(state, path, diffuse_bounce) = diffuse_bounce;
        if (diffuse_bounce >= kernel_data.integrator.max_diffuse_bounce) {
          flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
        }
      }
      else {
        const int glossy_bounce = INTEGRATOR_STATE(state, path, glossy_bounce) + 1;
        INTEGRATOR_STATE_WRITE(state, path, glossy_bounce) = glossy_bounce;
        if (glossy_bounce >= kernel_data.integrator.max_glossy_bounce) {
          flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
        }
      }
    }
    else {
      kernel_assert(label & LABEL_TRANSMIT);

      flag |= PATH_RAY_TRANSMIT;

      if (!(label & LABEL_TRANSMIT_TRANSPARENT)) {
        flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;
      }

      const int transmission_bounce = INTEGRATOR_STATE(state, path, transmission_bounce) + 1;
      INTEGRATOR_STATE_WRITE(state, path, transmission_bounce) = transmission_bounce;
      if (transmission_bounce >= kernel_data.integrator.max_transmission_bounce) {
        flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
      }
    }

    /* diffuse/glossy/singular */
    if (label & LABEL_DIFFUSE) {
      flag |= PATH_RAY_DIFFUSE | PATH_RAY_DIFFUSE_ANCESTOR;
    }
    else if (label & LABEL_GLOSSY) {
      flag |= PATH_RAY_GLOSSY;
    }
    else {
      kernel_assert(label & LABEL_SINGULAR);
      flag |= PATH_RAY_GLOSSY | PATH_RAY_SINGULAR | PATH_RAY_MIS_SKIP;
    }

    /* Flag for consistent MIS weights with light tree. */
    if (shader_flag & SD_BSDF_HAS_TRANSMISSION) {
      flag |= PATH_RAY_MIS_HAD_TRANSMISSION;
    }

    /* Render pass categories. */
    if (!(flag & PATH_RAY_ANY_PASS) && !(flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
      flag |= PATH_RAY_SURFACE_PASS;
    }
  }

  INTEGRATOR_STATE_WRITE(state, path, flag) = flag;
  INTEGRATOR_STATE_WRITE(state, path, bounce) = bounce;

  /* Random number generator next bounce. */
  INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
}

#ifdef __VOLUME__
ccl_device_inline bool path_state_volume_next(IntegratorState state)
{
  /* For volume bounding meshes we pass through without counting transparent
   * bounces, only sanity check in case self intersection gets us stuck. */
  const uint32_t volume_bounds_bounce = INTEGRATOR_STATE(state, path, volume_bounds_bounce) + 1;
  INTEGRATOR_STATE_WRITE(state, path, volume_bounds_bounce) = volume_bounds_bounce;
  if (volume_bounds_bounce > VOLUME_BOUNDS_MAX) {
    return false;
  }

  /* Random number generator next bounce. */
  INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;

  return true;
}
#endif

ccl_device_inline uint path_state_ray_visibility(ConstIntegratorState state)
{
  const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);

  uint32_t visibility = path_flag & PATH_RAY_ALL_VISIBILITY;

  /* For visibility, diffuse/glossy are for reflection only. */
  if (visibility & PATH_RAY_TRANSMIT) {
    visibility &= ~(PATH_RAY_DIFFUSE | PATH_RAY_GLOSSY);
  }

  visibility = SHADOW_CATCHER_PATH_VISIBILITY(path_flag, visibility);

  return visibility;
}

ccl_device_inline float path_state_continuation_probability(KernelGlobals kg,
                                                            ConstIntegratorState state,
                                                            const uint32_t path_flag)
{
  if (path_flag & PATH_RAY_TRANSPARENT) {
    const int transparent_bounce = INTEGRATOR_STATE(state, path, transparent_bounce);
    /* Do at least specified number of bounces without RR. */
    if (transparent_bounce <= kernel_data.integrator.transparent_min_bounce) {
      return 1.0f;
    }
  }
  else {
    const int bounce = INTEGRATOR_STATE(state, path, bounce);
    /* Do at least specified number of bounces without RR. */
    if (bounce <= kernel_data.integrator.min_bounce) {
      return 1.0f;
    }
  }

  /* Probabilistic termination: use `sqrt()` to roughly match typical view
   * transform and do path termination a bit later on average. */
  Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
  if ((kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING)) {
    throughput *= INTEGRATOR_STATE(state, path, unguided_throughput);
  }
#endif
  return min(sqrtf(reduce_max(fabs(throughput))), 1.0f);
}

ccl_device_inline bool path_state_ao_bounce(KernelGlobals kg, ConstIntegratorState state)
{
  if (!kernel_data.integrator.ao_bounces) {
    return false;
  }

  const int bounce = INTEGRATOR_STATE(state, path, bounce) -
                     INTEGRATOR_STATE(state, path, transmission_bounce) -
                     (INTEGRATOR_STATE(state, path, glossy_bounce) > 0) + 1;
  return (bounce > kernel_data.integrator.ao_bounces);
}

/* Random Number Sampling Utility Functions
 *
 * For each random number in each step of the path we must have a unique
 * dimension to avoid using the same sequence twice.
 *
 * For branches in the path we must be careful not to reuse the same number
 * in a sequence and offset accordingly.
 */

/* RNG State loaded onto stack. */
struct RNGState {
  uint rng_pixel;
  uint rng_offset;
  int sample;
};

ccl_device_inline void path_state_rng_load(ConstIntegratorState state,
                                           ccl_private RNGState *rng_state)
{
  rng_state->rng_pixel = INTEGRATOR_STATE(state, path, rng_pixel);
  rng_state->rng_offset = INTEGRATOR_STATE(state, path, rng_offset);
  rng_state->sample = INTEGRATOR_STATE(state, path, sample);
}

ccl_device_inline void shadow_path_state_rng_load(ConstIntegratorShadowState state,
                                                  ccl_private RNGState *rng_state)
{
  rng_state->rng_pixel = INTEGRATOR_STATE(state, shadow_path, rng_pixel);
  rng_state->rng_offset = INTEGRATOR_STATE(state, shadow_path, rng_offset);
  rng_state->sample = INTEGRATOR_STATE(state, shadow_path, sample);
}

ccl_device_inline void path_state_rng_scramble(ccl_private RNGState *rng_state, const int seed)
{
  /* To get an uncorrelated sequence of samples (e.g. for subsurface random walk), just change
   * the dimension offset since all implemented samplers can generate unlimited numbers of
   * dimensions anyways. The only thing to ensure is that the offset is divisible by 4. */
  rng_state->rng_offset = hash_hp_seeded_uint(rng_state->rng_offset, seed) & ~0x3;
}

ccl_device_inline float path_state_rng_1D(KernelGlobals kg,
                                          const ccl_private RNGState *rng_state,
                                          const int dimension)
{
  return path_rng_1D(
      kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension);
}

ccl_device_inline float2 path_state_rng_2D(KernelGlobals kg,
                                           const ccl_private RNGState *rng_state,
                                           const int dimension)
{
  return path_rng_2D(
      kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension);
}

ccl_device_inline float3 path_state_rng_3D(KernelGlobals kg,
                                           const ccl_private RNGState *rng_state,
                                           const int dimension)
{
  return path_rng_3D(
      kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension);
}

ccl_device_inline float path_branched_rng_1D(KernelGlobals kg,
                                             const ccl_private RNGState *rng_state,
                                             const int branch,
                                             const int num_branches,
                                             const int dimension)
{
  return path_rng_1D(kg,
                     rng_state->rng_pixel,
                     rng_state->sample * num_branches + branch,
                     rng_state->rng_offset + dimension);
}

ccl_device_inline float2 path_branched_rng_2D(KernelGlobals kg,
                                              const ccl_private RNGState *rng_state,
                                              const int branch,
                                              const int num_branches,
                                              const int dimension)
{
  return path_rng_2D(kg,
                     rng_state->rng_pixel,
                     rng_state->sample * num_branches + branch,
                     rng_state->rng_offset + dimension);
}

ccl_device_inline float3 path_branched_rng_3D(KernelGlobals kg,
                                              const ccl_private RNGState *rng_state,
                                              const int branch,
                                              const int num_branches,
                                              const int dimension)
{
  return path_rng_3D(kg,
                     rng_state->rng_pixel,
                     rng_state->sample * num_branches + branch,
                     rng_state->rng_offset + dimension);
}

/* Utility functions to get light termination value,
 * since it might not be needed in many cases.
 */
ccl_device_inline float path_state_rng_light_termination(KernelGlobals kg,
                                                         const ccl_private RNGState *state)
{
  if (kernel_data.integrator.light_inv_rr_threshold > 0.0f) {
    return path_state_rng_1D(kg, state, PRNG_LIGHT_TERMINATE);
  }
  return 0.0f;
}

CCL_NAMESPACE_END
