//============================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//
//  Copyright 2015 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
//  Copyright 2015 UT-Battelle, LLC.
//  Copyright 2015 Los Alamos National Security.
//
//  Under the terms of Contract DE-NA0003525 with NTESS,
//  the U.S. Government retains certain rights in this software.
//
//  Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
//  Laboratory (LANL), the U.S. Government retains certain rights in
//  this software.
//============================================================================
#ifndef vtk_m_rendering_raytracing_CellSampler_h
#define vtk_m_rendering_raytracing_CellSampler_h

#include <vtkm/VecVariable.h>
#include <vtkm/exec/CellInterpolate.h>
#include <vtkm/exec/ParametricCoordinates.h>

#ifndef CELL_SHAPE_ZOO
#define CELL_SHAPE_ZOO 255
#endif

#ifndef CELL_SHAPE_STRUCTURED
#define CELL_SHAPE_STRUCTURED 254
#endif

namespace vtkm
{
namespace rendering
{
namespace raytracing
{

namespace detail
{
template <typename CellTag>
VTKM_EXEC_CONT inline vtkm::Int32 GetNumberOfPoints(CellTag tag);

template <>
VTKM_EXEC_CONT inline vtkm::Int32 GetNumberOfPoints<vtkm::CellShapeTagHexahedron>(
  vtkm::CellShapeTagHexahedron vtkmNotUsed(tag))
{
  return 8;
}

template <>
VTKM_EXEC_CONT inline vtkm::Int32 GetNumberOfPoints<vtkm::CellShapeTagTetra>(
  vtkm::CellShapeTagTetra vtkmNotUsed(tag))
{
  return 4;
}

template <>
VTKM_EXEC_CONT inline vtkm::Int32 GetNumberOfPoints<vtkm::CellShapeTagWedge>(
  vtkm::CellShapeTagWedge vtkmNotUsed(tag))
{
  return 6;
}

template <>
VTKM_EXEC_CONT inline vtkm::Int32 GetNumberOfPoints<vtkm::CellShapeTagPyramid>(
  vtkm::CellShapeTagPyramid vtkmNotUsed(tag))
{
  return 5;
}

template <typename P, typename S, typename WorkletType, typename CellShapeTagType>
VTKM_EXEC_CONT inline bool Sample(const vtkm::Vec<vtkm::Vec<P, 3>, 8>& points,
                                  const vtkm::Vec<S, 8>& scalars,
                                  const vtkm::Vec<P, 3>& sampleLocation,
                                  S& lerpedScalar,
                                  const WorkletType& callingWorklet,
                                  const CellShapeTagType& shapeTag)
{

  bool validSample = true;
  vtkm::VecVariable<vtkm::Vec<P, 3>, 8> pointsVec;
  vtkm::VecVariable<S, 8> scalarVec;
  for (vtkm::Int32 i = 0; i < GetNumberOfPoints(shapeTag); ++i)
  {
    pointsVec.Append(points[i]);
    scalarVec.Append(scalars[i]);
  }
  bool success = false; // ignored
  vtkm::Vec<P, 3> pcoords = vtkm::exec::WorldCoordinatesToParametricCoordinates(
    pointsVec, sampleLocation, shapeTag, success, callingWorklet);
  P pmin, pmax;
  pmin = vtkm::Min(vtkm::Min(pcoords[0], pcoords[1]), pcoords[2]);
  pmax = vtkm::Max(vtkm::Max(pcoords[0], pcoords[1]), pcoords[2]);
  if (pmin < 0.f || pmax > 1.f)
  {
    validSample = false;
  }
  lerpedScalar = vtkm::exec::CellInterpolate(scalarVec, pcoords, shapeTag, callingWorklet);
  return validSample;
}

template <typename S, typename P, typename WorkletType, typename CellShapeTagType>
VTKM_EXEC_CONT inline bool Sample(const vtkm::VecAxisAlignedPointCoordinates<3>& points,
                                  const vtkm::Vec<S, 8>& scalars,
                                  const vtkm::Vec<P, 3>& sampleLocation,
                                  S& lerpedScalar,
                                  const WorkletType& callingWorklet,
                                  const CellShapeTagType& vtkmNotUsed(shapeTag))
{

  bool validSample = true;
  bool success;
  vtkm::Vec<P, 3> pcoords = vtkm::exec::WorldCoordinatesToParametricCoordinates(
    points, sampleLocation, vtkm::CellShapeTagHexahedron(), success, callingWorklet);
  P pmin, pmax;
  pmin = vtkm::Min(vtkm::Min(pcoords[0], pcoords[1]), pcoords[2]);
  pmax = vtkm::Max(vtkm::Max(pcoords[0], pcoords[1]), pcoords[2]);
  if (pmin < 0.f || pmax > 1.f)
  {
    validSample = false;
  }
  lerpedScalar =
    vtkm::exec::CellInterpolate(scalars, pcoords, vtkm::CellShapeTagHexahedron(), callingWorklet);
  return validSample;
}
} // namespace detail

//
//  General Template: returns false if sample location is outside the cell
//
template <int CellType>
class CellSampler
{
public:
  template <typename P, typename S, typename WorkletType>
  VTKM_EXEC_CONT inline bool SampleCell(const vtkm::Vec<vtkm::Vec<P, 3>, 8>& vtkmNotUsed(points),
                                        const vtkm::Vec<S, 8>& vtkmNotUsed(scalars),
                                        const vtkm::Vec<P, 3>& vtkmNotUsed(sampleLocation),
                                        S& vtkmNotUsed(lerpedScalar),
                                        const WorkletType& vtkmNotUsed(callingWorklet),
                                        const vtkm::Int32& vtkmNotUsed(cellShape = CellType)) const
  {
    static_assert(CellType != CELL_SHAPE_ZOO && CellType != CELL_SHAPE_STRUCTURED &&
                    CellType != CELL_SHAPE_HEXAHEDRON && CellType != CELL_SHAPE_TETRA &&
                    CellType != CELL_SHAPE_WEDGE && CellType != CELL_SHAPE_PYRAMID,
                  "Cell Sampler: Default template. This should not happen.\n");
    return false;
  }
};

//
// Zoo Sampler
//
template <>
class CellSampler<255>
{
public:
  template <typename P, typename S, typename WorkletType>
  VTKM_EXEC_CONT inline bool SampleCell(const vtkm::Vec<vtkm::Vec<P, 3>, 8>& points,
                                        const vtkm::Vec<S, 8>& scalars,
                                        const vtkm::Vec<P, 3>& sampleLocation,
                                        S& lerpedScalar,
                                        const WorkletType& callingWorklet,
                                        const vtkm::Int32& cellShape) const
  {
    bool valid = false;
    if (cellShape == CELL_SHAPE_HEXAHEDRON)
    {
      valid = detail::Sample(points,
                             scalars,
                             sampleLocation,
                             lerpedScalar,
                             callingWorklet,
                             vtkm::CellShapeTagHexahedron());
    }

    if (cellShape == CELL_SHAPE_TETRA)
    {
      valid = detail::Sample(
        points, scalars, sampleLocation, lerpedScalar, callingWorklet, vtkm::CellShapeTagTetra());
    }

    if (cellShape == CELL_SHAPE_WEDGE)
    {
      valid = detail::Sample(
        points, scalars, sampleLocation, lerpedScalar, callingWorklet, vtkm::CellShapeTagWedge());
    }
    if (cellShape == CELL_SHAPE_PYRAMID)
    {
      valid = detail::Sample(
        points, scalars, sampleLocation, lerpedScalar, callingWorklet, vtkm::CellShapeTagPyramid());
    }
    return valid;
  }
};

//
//  Single type hex
//
template <>
class CellSampler<CELL_SHAPE_HEXAHEDRON>
{
public:
  template <typename P, typename S, typename WorkletType>
  VTKM_EXEC_CONT inline bool SampleCell(
    const vtkm::Vec<vtkm::Vec<P, 3>, 8>& points,
    const vtkm::Vec<S, 8>& scalars,
    const vtkm::Vec<P, 3>& sampleLocation,
    S& lerpedScalar,
    const WorkletType& callingWorklet,
    const vtkm::Int32& vtkmNotUsed(cellShape = CELL_SHAPE_HEXAHEDRON)) const
  {
    return detail::Sample(points,
                          scalars,
                          sampleLocation,
                          lerpedScalar,
                          callingWorklet,
                          vtkm::CellShapeTagHexahedron());
  }
};

//
//  Single type hex uniform and rectilinear
//  calls fast path for sampling
//
template <>
class CellSampler<CELL_SHAPE_STRUCTURED>
{
public:
  template <typename P, typename S, typename WorkletType>
  VTKM_EXEC_CONT inline bool SampleCell(
    const vtkm::Vec<vtkm::Vec<P, 3>, 8>& points,
    const vtkm::Vec<S, 8>& scalars,
    const vtkm::Vec<P, 3>& sampleLocation,
    S& lerpedScalar,
    const WorkletType& callingWorklet,
    const vtkm::Int32& vtkmNotUsed(cellShape = CELL_SHAPE_HEXAHEDRON)) const
  {
    vtkm::VecAxisAlignedPointCoordinates<3> rPoints(points[0], points[6] - points[0]);
    return detail::Sample(rPoints,
                          scalars,
                          sampleLocation,
                          lerpedScalar,
                          callingWorklet,
                          vtkm::CellShapeTagHexahedron());
  }
};

//
//  Single type pyramid
//
template <>
class CellSampler<CELL_SHAPE_PYRAMID>
{
public:
  template <typename P, typename S, typename WorkletType>
  VTKM_EXEC_CONT inline bool SampleCell(
    const vtkm::Vec<vtkm::Vec<P, 3>, 8>& points,
    const vtkm::Vec<S, 8>& scalars,
    const vtkm::Vec<P, 3>& sampleLocation,
    S& lerpedScalar,
    const WorkletType& callingWorklet,
    const vtkm::Int32& vtkmNotUsed(cellShape = CELL_SHAPE_PYRAMID)) const
  {
    return detail::Sample(
      points, scalars, sampleLocation, lerpedScalar, callingWorklet, vtkm::CellShapeTagPyramid());
  }
};


//
//  Single type Tet
//
template <>
class CellSampler<CELL_SHAPE_TETRA>
{
public:
  template <typename P, typename S, typename WorkletType>
  VTKM_EXEC_CONT inline bool SampleCell(
    const vtkm::Vec<vtkm::Vec<P, 3>, 8>& points,
    const vtkm::Vec<S, 8>& scalars,
    const vtkm::Vec<P, 3>& sampleLocation,
    S& lerpedScalar,
    const WorkletType& callingWorklet,
    const vtkm::Int32& vtkmNotUsed(cellShape = CELL_SHAPE_TETRA)) const
  {
    return detail::Sample(
      points, scalars, sampleLocation, lerpedScalar, callingWorklet, vtkm::CellShapeTagTetra());
  }
};

//
//  Single type Wedge
//
template <>
class CellSampler<CELL_SHAPE_WEDGE>
{
public:
  template <typename P, typename S, typename WorkletType>
  VTKM_EXEC_CONT inline bool SampleCell(
    const vtkm::Vec<vtkm::Vec<P, 3>, 8>& points,
    const vtkm::Vec<S, 8>& scalars,
    const vtkm::Vec<P, 3>& sampleLocation,
    S& lerpedScalar,
    const WorkletType& callingWorklet,
    const vtkm::Int32& vtkmNotUsed(cellShape = CELL_SHAPE_WEDGE)) const
  {
    return detail::Sample(
      points, scalars, sampleLocation, lerpedScalar, callingWorklet, vtkm::CellShapeTagWedge());
  }
};
}
}
} // namespace vtkm::rendering::raytracing
#endif
