// Copyright 2020 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "DataShared.h"
#include "openvkl/VKLDataType.h"

#ifdef ISPC

#define VARYING_SUPPORT 1
#define HALF_FLOAT_SUPPORT 1
#define DOUBLE_SUPPORT 1

#else

#define VARYING_SUPPORT 0
#define HALF_FLOAT_SUPPORT 0
#define DOUBLE_SUPPORT 0

#define uniform

#endif

#ifndef ISPC
// In non-ISPC world we need to define ispc namespace
namespace ispc {
#endif

#define process_hi28(univary) process_hi28_##univary
#ifdef ISPC
#define process_hi28_varying foreach_unique(hi in hi28)
#define process_hi28_uniform uniform uint32 hi = hi28;
#else
#define process_hi28_ uint32 hi = hi28;
#endif

  // in all inlined functions here, ok to pass-by-value, will be inlined and
  // optimized
  inline uniform bool isValid(const uniform Data1D data)
  {
    return data.addr != NULL;
  }

  // indicates if the provided index can be safely used in the 32-bit indexing
  // accessors below (as a single 32-bit index, or offsetIndex)
  inline uniform bool safe_32bit_indexing(const uniform Data1D data,
                                          const uniform uint64 maxIndex)
  {
    const uniform uint64 maxBytes = maxIndex * data.byteStride;
    return (maxBytes < (1ULL << 31));
  }

  // same as above, but assumes maximum index allowed by the array
  inline uniform bool safe_32bit_indexing(const uniform Data1D data)
  {
    const uniform uint64 maxBytes = (data.numItems - 1) * data.byteStride;
    return (maxBytes < (1ULL << 31));
  }

// T: user-requested type
// retT: return type
// repT: internal representation type
// univary: uniform or varying
// convFunc: an optional conversion function to transform from repT to retT
#define template_get(T, retT, repT, univary, convFunc)                                       \
  inline univary retT get_##T##_compact(const uniform Data1D data,                           \
                                        const univary uint32 index32)                        \
  {                                                                                          \
    assert(index32 >= 0 && index32 < data.numItems);                                         \
    return convFunc(((const repT *uniform)data.addr)[index32]);                              \
  }                                                                                          \
                                                                                             \
  inline univary retT get_##T##_strided(const uniform Data1D data,                           \
                                        const univary uint32 index32)                        \
  {                                                                                          \
    assert(index32 >= 0 && index32 < data.numItems);                                         \
    return convFunc(                                                                         \
        *((const uniform repT *)(data.addr + index32 * data.byteStride)));                   \
  }                                                                                          \
                                                                                             \
  inline univary retT get_##T##_compact(const uniform Data1D data,                           \
                                        const uniform uint64 baseIndex64,                    \
                                        const univary uint32 offsetIndex32)                  \
  {                                                                                          \
    assert(baseIndex64 + offsetIndex32 >= 0 &&                                               \
           baseIndex64 + offsetIndex32 < data.numItems);                                     \
    const repT *uniform base =                                                               \
        (const repT *uniform)(data.addr + baseIndex64 * data.byteStride);                    \
    return convFunc(base[offsetIndex32]);                                                    \
  }                                                                                          \
                                                                                             \
  inline univary retT get_##T##_strided(const uniform Data1D data,                           \
                                        const uniform uint64 baseIndex64,                    \
                                        const univary uint32 offsetIndex32)                  \
  {                                                                                          \
    assert(baseIndex64 + offsetIndex32 >= 0 &&                                               \
           baseIndex64 + offsetIndex32 < data.numItems);                                     \
    const uint8 *uniform base =                                                              \
        (const uint8 *uniform)(data.addr + baseIndex64 * data.byteStride);                   \
    return convFunc(                                                                         \
        *((const uniform repT *)(base + offsetIndex32 * data.byteStride)));                  \
  }                                                                                          \
                                                                                             \
  inline univary retT get_##T(const uniform Data1D data,                                     \
                              const univary uint64 index64)                                  \
  {                                                                                          \
    assert(index64 >= 0 && index64 < data.numItems);                                         \
    const univary uint64 address = index64 * data.byteStride;                                \
    const univary uint32 hi28    = address >> 28;                                            \
    const univary uint32 lo28    = address & ((1 << 28) - 1);                                \
    univary retT value;                                                                      \
    process_hi28(univary)                                                                    \
    {                                                                                        \
      const uniform uint64 hi64 = hi;                                                        \
      value                     = convFunc(                                                  \
          *((const uniform repT *)((data.addr + (hi64 << 28)) + lo28))); \
    }                                                                                        \
    return value;                                                                            \
  }

#if VARYING_SUPPORT
  template_get(uint8, uint8, uint8, varying, );
  template_get(int16, int16, int16, varying, );
  template_get(uint16, uint16, uint16, varying, );
  template_get(uint32, uint32, uint32, varying, );
  template_get(uint64, uint64, uint64, varying, );
  template_get(float, float, float, varying, );
#if DOUBLE_SUPPORT
  template_get(double, double, double, varying, );
#endif
  template_get(vec3f, vec3f, vec3f, varying, );
  template_get(vec4f, vec4f, vec4f, varying, );
#endif

  template_get(uint8, uint8, uint8, uniform, );
  template_get(int16, int16, int16, uniform, );
  template_get(uint16, uint16, uint16, uniform, );
  template_get(uint32, uint32, uint32, uniform, );
  template_get(uint64, uint64, uint64, uniform, );
  template_get(float, float, float, uniform, );
#if DOUBLE_SUPPORT
  template_get(double, double, double, uniform, );
#endif
  template_get(vec3f, vec3f, vec3f, uniform, );
  template_get(vec4f, vec4f, vec4f, uniform, );

// special case: half
#if HALF_FLOAT_SUPPORT
#if VARYING_SUPPORT
  template_get(half, float, unsigned int16, varying, half_to_float);
#endif
  template_get(half, float, unsigned int16, uniform, half_to_float);
#endif

#undef template_get

#if VARYING_SUPPORT
// define wrappers that appropriately call the compact and strided getters. for
// the uniform case, these will always use the strided path (which avoids the
// conditional and is more performant). for the varying case, the compact or
// strided functions will be used as appropriate.
#define template_get_wrapper(T, retT)                                          \
  inline uniform retT get_##T(const uniform Data1D data,                       \
                              const uniform uint32 index32)                    \
  {                                                                            \
    return get_##T##_strided(data, index32);                                   \
  }                                                                            \
                                                                               \
  inline varying retT get_##T(const uniform Data1D data,                       \
                              const varying uint32 index32)                    \
  {                                                                            \
    return data.compact ? get_##T##_compact(data, index32)                     \
                        : get_##T##_strided(data, index32);                    \
  }                                                                            \
                                                                               \
  inline uniform retT get_##T(const uniform Data1D data,                       \
                              const uniform uint64 baseIndex64,                \
                              const uniform uint32 offsetIndex32)              \
  {                                                                            \
    return get_##T##_strided(data, baseIndex64, offsetIndex32);                \
  }                                                                            \
                                                                               \
  inline varying retT get_##T(const uniform Data1D data,                       \
                              const uniform uint64 baseIndex64,                \
                              const varying uint32 offsetIndex32)              \
  {                                                                            \
    return data.compact ? get_##T##_compact(data, baseIndex64, offsetIndex32)  \
                        : get_##T##_strided(data, baseIndex64, offsetIndex32); \
  }
#else

#define template_get_wrapper(T, retT)                             \
  inline uniform retT get_##T(const uniform Data1D data,          \
                              const uniform uint32 index32)       \
  {                                                               \
    return get_##T##_strided(data, index32);                      \
  }                                                               \
                                                                  \
  inline uniform retT get_##T(const uniform Data1D data,          \
                              const uniform uint64 baseIndex64,   \
                              const uniform uint32 offsetIndex32) \
  {                                                               \
    return get_##T##_strided(data, baseIndex64, offsetIndex32);   \
  }

#endif

  template_get_wrapper(uint8, uint8);
  template_get_wrapper(int16, int16);
  template_get_wrapper(uint16, uint16);
  template_get_wrapper(uint32, uint32);
  template_get_wrapper(uint64, uint64);
  template_get_wrapper(float, float);
#if DOUBLE_SUPPORT
  template_get_wrapper(double, double);
#endif
  template_get_wrapper(vec3f, vec3f);
  template_get_wrapper(vec4f, vec4f);

#if HALF_FLOAT_SUPPORT
  // special case: half
  template_get_wrapper(half, float);
#endif

#undef template_get_wrapper

// limited support for getters on _varying_ Data1D objects
#if VARYING_SUPPORT
#define template_get_special(T, retT, repT, convFunc)                        \
  inline varying retT get_##T##_compact(const uniform Data1D *varying data,  \
                                        const varying uint32 index32)        \
  {                                                                          \
    assert(index32 >= 0 && index32 < data->numItems);                        \
    return convFunc(((const uniform repT *)data->addr)[index32]);            \
  }                                                                          \
                                                                             \
  inline varying retT get_##T##_strided(const uniform Data1D *varying data,  \
                                        const varying uint32 index32)        \
  {                                                                          \
    assert(index32 >= 0 && index32 < data->numItems);                        \
    return convFunc(                                                         \
        *((const uniform repT *)(data->addr + index32 * data->byteStride))); \
  }                                                                          \
                                                                             \
  inline varying retT get_##T(const uniform Data1D *varying data,            \
                              const varying uint32 index32)                  \
  {                                                                          \
    return data->compact ? get_##T##_compact(data, index32)                  \
                         : get_##T##_strided(data, index32);                 \
  }                                                                          \
                                                                             \
  inline varying retT get_##T##_compact(const uniform Data1D *varying data,  \
                                        const varying uint64 index64)        \
  {                                                                          \
    assert(index64 >= 0 && index64 < data->numItems);                        \
    return convFunc(((const uniform repT *)data->addr)[index64]);            \
  }                                                                          \
                                                                             \
  inline varying retT get_##T##_strided(const uniform Data1D *varying data,  \
                                        const varying uint64 index64)        \
  {                                                                          \
    assert(index64 >= 0 && index64 < data->numItems);                        \
    return convFunc(                                                         \
        *((const uniform repT *)(data->addr + index64 * data->byteStride))); \
  }                                                                          \
                                                                             \
  inline varying retT get_##T(const uniform Data1D *varying data,            \
                              const varying uint64 index64)                  \
  {                                                                          \
    return data->compact ? get_##T##_compact(data, index64)                  \
                         : get_##T##_strided(data, index64);                 \
  }                                                                          \
                                                                             \
  /* helpers for getting the first element only of a Data1D object */        \
                                                                             \
  inline varying retT get_##T##_0(const uniform Data1D *varying data)        \
  {                                                                          \
    assert(data->numItems > 0);                                              \
    return convFunc(*((const uniform repT *)data->addr));                    \
  }                                                                          \
                                                                             \
  inline uniform retT get_##T##_0(const uniform Data1D data)                 \
  {                                                                          \
    assert(data.numItems > 0);                                               \
    return convFunc(*((const uniform repT *)data.addr));                     \
  }
#else
#define template_get_special(T, retT, repT, convFunc)        \
  inline uniform retT get_##T##_0(const uniform Data1D data) \
  {                                                          \
    assert(data.numItems > 0);                               \
    return convFunc(*((const uniform repT *)data.addr));     \
  }
#endif

// these are required for sparse VDB motion blur cases.
#if HALF_FLOAT_SUPPORT
  template_get_special(half, float, unsigned int16, half_to_float);
#endif
  template_get_special(float, float, float, );

  template_get_special(uint32, uint32, uint32, );
  template_get_special(uint64, uint64, uint64, );
#undef template_get_special

#ifndef ISPC
  // close ISPC namespace
}
#endif