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

// ========================================================================== //
// This is a shared ISPC and C++ header, which contains the internal data
// types used to represent VDB trees.
// ========================================================================== //

#pragma once

#include "VKLDataType.h"
#include "VKLFilter.h"
#include "ispc_cpp_interop.h"

// ========================================================================== //
// Tree topology information. The file vdb_topology.h is generated
// from vdb_topology.h.in, and defines both VKL_VDB_NUM_LEVELS and, for
// each <level> in {0, VKL_VDB_NUM_LEVELS-1}:
//  VKL_VDB_LOG_RES_<level>:       The base two logarithm of the storage
//                                 resolution of nodes on this level.
//  VKL_VDB_STORAGE_RES_<level>:   The storage resolution of nodes on this
//                                 level.
//  VKL_VDB_NUM_VOXELS_<level>:    The number of voxels stored in a node on
//                                 this level.
//  VKL_VDB_TOTAL_LOG_RES_<level>: The base two logarithm of the domain
//                                 resolution on this level.
//  VKL_VDB_RES_<level>:           The domain resolution on this level.
//
//  This file also defines macros
//
//  __vkl_vdb_iterate_levels_<level>(macro) (see __vkl_vdb_iterate_levels below)
//  __vkl_vdb_map_offset_to_voxel_<level>(univary, offset)
//  __vkl_vdb_3d_to_linear_<level>(offx, offy, offz)
//  __vkl_vdb_map_offset_to_lindex_<level>(offset_3d)
// ========================================================================== //
//
#include "openvkl/vdb/topology.h"  // This file is generated by cmake.

// ========================================================================== //
// The following are runtime versions of the constants described above,
// to be used when the level is not known at runtime.
// ========================================================================== //

inline VKL_INTEROP_CONSTEXPR VKL_INTEROP_UNIFORM vkl_uint32 vklVdbNumLevels()
{
  return VKL_VDB_NUM_LEVELS;
}

#define __vkl_vdb_switch_case(Level, Prefix) \
  case Level:                                \
    return Prefix##Level;

#define __vkl_vdb_define_topology_functions(univary)                         \
  inline univary vkl_uint32 vklVdbLevelLogRes(univary vkl_uint32 level)      \
  {                                                                          \
    switch (level) {                                                         \
      __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,                      \
                                 VKL_VDB_LOG_RES_) default : return 0;       \
    }                                                                        \
  }                                                                          \
  inline univary vkl_uint32 vklVdbLevelResShift(univary vkl_uint32 level)    \
  {                                                                          \
    return vklVdbLevelLogRes(level);                                         \
  }                                                                          \
  inline univary vkl_uint32 vklVdbLevelTotalLogRes(univary vkl_uint32 level) \
  {                                                                          \
    switch (level) {                                                         \
      __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,                      \
                                 VKL_VDB_TOTAL_LOG_RES_) default : return 0; \
    }                                                                        \
  }                                                                          \
  inline univary vkl_uint32 vklVdbLevelStorageRes(univary vkl_uint32 level)  \
  {                                                                          \
    switch (level) {                                                         \
      __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,                      \
                                 VKL_VDB_STORAGE_RES_) default : return 0;   \
    }                                                                        \
  }                                                                          \
  inline univary vkl_uint32 vklVdbLevelRes(univary vkl_uint32 level)         \
  {                                                                          \
    switch (level) {                                                         \
      __vkl_vdb_iterate_levels_0(                                            \
          __vkl_vdb_switch_case,                                             \
          VKL_VDB_RES_) /* Special case: we use this to determine cell       \
                           resolution. */                                    \
          case VKL_VDB_NUM_LEVELS : return 1;                                \
    default:                                                                 \
      return 0;                                                              \
    }                                                                        \
  }                                                                          \
  inline univary vkl_uint32 vklVdbLevelNumVoxels(univary vkl_uint32 level)   \
  {                                                                          \
    switch (level) {                                                         \
      __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,                      \
                                 VKL_VDB_NUM_VOXELS_) default : return 0;    \
    }                                                                        \
  }

__vkl_interop_univary(__vkl_vdb_define_topology_functions)
#undef __vkl_vdb_define_topology_functions
#undef __vkl_vdb_switch_case

// ========================================================================== //
// Functions that help with index computations.
// ========================================================================== //

#define __vkl_vdb_switch_case(Level, Macro, ...) \
  __vkl_vdb_expand(case Level : return Macro##_##Level(__VA_ARGS__));

    /*
     * Map a 1D domain offset w.r.t. the root node to an offset inside the
     * surrounding voxel on the given level.
     */
    inline VKL_INTEROP_UNIFORM vkl_uint64
    vklVdbDomainOffsetToVoxel(VKL_INTEROP_UNIFORM vkl_uint32 level,
                              VKL_INTEROP_UNIFORM vkl_uint32 offset)
{
  switch (level) {
    __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,
                               __vkl_vdb_domain_offset_to_voxel_uniform,
                               offset) default : return 0;
  }
}

/*
 * Map a 3D offset to a linear index. Note that vdb volume store
 * data in z-major order!
 */
inline VKL_INTEROP_UNIFORM vkl_uint64
vklVdb3DToLinear(VKL_INTEROP_UNIFORM vkl_uint32 level,
                 VKL_INTEROP_UNIFORM vkl_uint64 offsetX,
                 VKL_INTEROP_UNIFORM vkl_uint64 offsetY,
                 VKL_INTEROP_UNIFORM vkl_uint64 offsetZ)
{
  switch (level) {
    __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,
                               __vkl_vdb_3d_to_linear_uniform,
                               offsetX,
                               offsetY,
                               offsetZ) default : return 0;
  }
}

/*
 * Map a 3D domain offset w.r.t. to the root node to a linear voxel index
 * inside the surrounding voxel on the given level.
 */
inline VKL_INTEROP_UNIFORM vkl_uint64
vklVdbDomainOffsetToLinear(VKL_INTEROP_UNIFORM vkl_uint32 level,
                           VKL_INTEROP_UNIFORM vkl_uint64 offsetX,
                           VKL_INTEROP_UNIFORM vkl_uint64 offsetY,
                           VKL_INTEROP_UNIFORM vkl_uint64 offsetZ)
{
  switch (level) {
    __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,
                               __vkl_vdb_domain_offset_to_linear_uniform,
                               offsetX,
                               offsetY,
                               offsetZ) default : return 0;
  }
}

#if defined(ISPC)

// Varying versions of the above. We do not use __vkl_interop_univary here
// because there is no standard way to handle empty __VA_ARGS__, which happen
// in C++ when univary expands to nothing.
// This also conveniently leads to better error messages.

/*
 * Map a 1D domain offset w.r.t. the root node to an offset inside the
 * surrounding voxel on the given level.
 */
inline varying vkl_uint64 vklVdbDomainOffsetToVoxel(varying vkl_uint32 level,
                                                    varying vkl_uint32 offset)
{
  switch (level) {
    __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,
                               __vkl_vdb_domain_offset_to_voxel_varying,
                               offset) default : return 0;
  }
}

/*
 * Map a 3D offset to a linear index. Note that vdb volume store
 * data in z-major order!
 */
inline varying vkl_uint64 vklVdb3DToLinear(varying vkl_uint32 level,
                                           varying vkl_uint64 offsetX,
                                           varying vkl_uint64 offsetY,
                                           varying vkl_uint64 offsetZ)
{
  switch (level) {
    __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,
                               __vkl_vdb_3d_to_linear_varying,
                               offsetX,
                               offsetY,
                               offsetZ) default : return 0;
  }
}

/*
 * Map a 3D domain offset w.r.t. to the root node to a linear voxel index
 * inside the surrounding voxel on the given level.
 */
inline varying vkl_uint64 vklVdbDomainOffsetToLinear(varying vkl_uint32 level,
                                                     varying vkl_uint64 offsetX,
                                                     varying vkl_uint64 offsetY,
                                                     varying vkl_uint64 offsetZ)
{
  switch (level) {
    __vkl_vdb_iterate_levels_0(__vkl_vdb_switch_case,
                               __vkl_vdb_domain_offset_to_linear_varying,
                               offsetX,
                               offsetY,
                               offsetZ) default : return 0;
  }
}

#endif

#undef __vkl_vdb_switch_case
