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

#pragma once

#include "../common/Data.ih"
#include "openvkl/VKLDataType.h"
#include "openvkl/VKLFilter.h"

struct GridAccelerator;
struct SharedStructuredVolume;

#include "StructuredVolumeShared.h"

///////////////////////////////////////////////////////////////////////////////
// Coordinate transformations for all supported structured volume types ///////
///////////////////////////////////////////////////////////////////////////////

// Structured regular /////////////////////////////////////////////////////////

inline void transformLocalToObject_varying_structured_regular(
    const SharedStructuredVolume *uniform self,
    const varying vec3f &localCoordinates,
    varying vec3f &objectCoordinates)
{
  objectCoordinates = self->gridOrigin + localCoordinates * self->gridSpacing;
}

inline void transformObjectToLocal_varying_structured_regular(
    const SharedStructuredVolume *uniform self,
    const varying vec3f &objectCoordinates,
    varying vec3f &localCoordinates)
{
  localCoordinates =
      1.f / (self->gridSpacing) * (objectCoordinates - self->gridOrigin);
}

inline void transformLocalToObject_uniform_structured_regular(
    const SharedStructuredVolume *uniform self,
    const uniform vec3f &localCoordinates,
    uniform vec3f &objectCoordinates)
{
  objectCoordinates = self->gridOrigin + localCoordinates * self->gridSpacing;
}

inline void transformObjectToLocal_uniform_structured_regular(
    const SharedStructuredVolume *uniform self,
    const uniform vec3f &objectCoordinates,
    uniform vec3f &localCoordinates)
{
  localCoordinates =
      1.f / (self->gridSpacing) * (objectCoordinates - self->gridOrigin);
}

// Structured spherical ///////////////////////////////////////////////////////

#define template_transformLocalToObject_structured_spherical(univary)       \
  inline void transformLocalToObject_##univary##_structured_spherical(      \
      const SharedStructuredVolume *uniform self,                           \
      const univary vec3f &localCoordinates,                                \
      univary vec3f &objectCoordinates)                                     \
  {                                                                         \
    /* (r, inclination, azimuth) -> (x, y, z), using the ISO convention for \
     coordinates and ordering. all angles in radians. */                    \
                                                                            \
    const univary float r =                                                 \
        self->gridOrigin.x + localCoordinates.x * self->gridSpacing.x;      \
                                                                            \
    const univary float inclination =                                       \
        self->gridOrigin.y + localCoordinates.y * self->gridSpacing.y;      \
                                                                            \
    const univary float azimuth =                                           \
        self->gridOrigin.z + localCoordinates.z * self->gridSpacing.z;      \
                                                                            \
    univary float sinInc, cosInc;                                           \
    sincos(inclination, &sinInc, &cosInc);                                  \
                                                                            \
    univary float sinAz, cosAz;                                             \
    sincos(azimuth, &sinAz, &cosAz);                                        \
                                                                            \
    objectCoordinates.x = r * sinInc * cosAz;                               \
    objectCoordinates.y = r * sinInc * sinAz;                               \
    objectCoordinates.z = r * cosInc;                                       \
  }

template_transformLocalToObject_structured_spherical(varying);
template_transformLocalToObject_structured_spherical(uniform);
#undef template_transformLocalToObject_structured_spherical

#define template_transformObjectToLocal_structured_spherical(univary)         \
  inline void transformObjectToLocal_##univary##_structured_spherical(        \
      const SharedStructuredVolume *uniform self,                             \
      const univary vec3f &objectCoordinates,                                 \
      univary vec3f &localCoordinates)                                        \
  {                                                                           \
    /* (x, y, z) -> (r, inclination, azimuth), using the ISO convention for   \
     coordinates and ordering. all angles in radians. */                      \
    const univary float r = sqrtf(objectCoordinates.x * objectCoordinates.x + \
                                  objectCoordinates.y * objectCoordinates.y + \
                                  objectCoordinates.z * objectCoordinates.z); \
                                                                              \
    const univary float inclination = acos(objectCoordinates.z / r);          \
                                                                              \
    univary float azimuth = atan2(objectCoordinates.y, objectCoordinates.x);  \
                                                                              \
    /* the above returns [-PI, PI], while our azimuth grid convention is [0,  \
     * 2*PI] */                                                               \
    if (azimuth < 0.f) {                                                      \
      azimuth += 2.f * PI;                                                    \
    }                                                                         \
                                                                              \
    localCoordinates.x =                                                      \
        (1.f / self->gridSpacing.x) * (r - self->gridOrigin.x);               \
    localCoordinates.y =                                                      \
        (1.f / self->gridSpacing.y) * (inclination - self->gridOrigin.y);     \
    localCoordinates.z =                                                      \
        (1.f / self->gridSpacing.z) * (azimuth - self->gridOrigin.z);         \
  }

template_transformObjectToLocal_structured_spherical(varying);
template_transformObjectToLocal_structured_spherical(uniform);
#undef template_transformObjectToLocal_structured_spherical

// Dispatch functions /////////////////////////////////////////////////////////

inline void transformLocalToObject_varying_dispatch(
    const SharedStructuredVolume *uniform self,
    const varying vec3f &localCoordinates,
    varying vec3f &objectCoordinates)
{
  if (self->gridType == structured_regular) {
    transformLocalToObject_varying_structured_regular(
        self, localCoordinates, objectCoordinates);
  } else {
    transformLocalToObject_varying_structured_spherical(
        self, localCoordinates, objectCoordinates);
  }
}

inline void transformLocalToObject_uniform_dispatch(
    const SharedStructuredVolume *uniform self,
    const uniform vec3f &localCoordinates,
    uniform vec3f &objectCoordinates)
{
  if (self->gridType == structured_regular) {
    transformLocalToObject_uniform_structured_regular(
        self, localCoordinates, objectCoordinates);
  } else {
    transformLocalToObject_uniform_structured_spherical(
        self, localCoordinates, objectCoordinates);
  }
}

inline void transformObjectToLocal_varying_dispatch(
    const SharedStructuredVolume *uniform self,
    const varying vec3f &objectCoordinates,
    varying vec3f &localCoordinates)
{
  if (self->gridType == structured_regular) {
    transformObjectToLocal_varying_structured_regular(
        self, objectCoordinates, localCoordinates);
  } else {
    transformObjectToLocal_varying_structured_spherical(
        self, objectCoordinates, localCoordinates);
  }
}

inline void transformObjectToLocal_uniform_dispatch(
    const SharedStructuredVolume *uniform self,
    const uniform vec3f &objectCoordinates,
    uniform vec3f &localCoordinates)
{
  if (self->gridType == structured_regular) {
    transformObjectToLocal_uniform_structured_regular(
        self, objectCoordinates, localCoordinates);
  } else {
    transformObjectToLocal_uniform_structured_spherical(
        self, objectCoordinates, localCoordinates);
  }
}
