//============================================================================
//  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.
//============================================================================
#ifndef vtk_m_exec_CellEdge_h
#define vtk_m_exec_CellEdge_h

#include <vtkm/CellShape.h>
#include <vtkm/CellTraits.h>
#include <vtkm/ErrorCode.h>
#include <vtkm/Types.h>
#include <vtkm/exec/FunctorBase.h>

namespace vtkm
{
namespace exec
{

namespace detail
{

class CellEdgeTables
{
public:
  static constexpr vtkm::Int32 MAX_NUM_EDGES = 12;

public:
  VTKM_EXEC vtkm::Int32 NumEdges(vtkm::Int32 cellShapeId) const
  {
    VTKM_STATIC_CONSTEXPR_ARRAY vtkm::Int32 numEdges[vtkm::NUMBER_OF_CELL_SHAPES] = {
      // NumEdges
      0,  //  0: CELL_SHAPE_EMPTY
      0,  //  1: CELL_SHAPE_VERTEX
      0,  //  2: Unused
      0,  //  3: CELL_SHAPE_LINE
      0,  //  4: CELL_SHAPE_POLY_LINE
      3,  //  5: CELL_SHAPE_TRIANGLE
      0,  //  6: Unused
      -1, //  7: CELL_SHAPE_POLYGON  ---special case---
      0,  //  8: Unused
      4,  //  9: CELL_SHAPE_QUAD
      6,  // 10: CELL_SHAPE_TETRA
      0,  // 11: Unused
      12, // 12: CELL_SHAPE_HEXAHEDRON
      9,  // 13: CELL_SHAPE_WEDGE
      8   // 14: CELL_SHAPE_PYRAMID
    };
    return numEdges[cellShapeId];
  }

  VTKM_EXEC vtkm::Int32 PointsInEdge(vtkm::Int32 cellShapeId,
                                     vtkm::Int32 edgeIndex,
                                     vtkm::Int32 localPointIndex) const
  {
    VTKM_STATIC_CONSTEXPR_ARRAY vtkm::Int32
      pointsInEdge[vtkm::NUMBER_OF_CELL_SHAPES][MAX_NUM_EDGES][2] = {
        // clang-format off
        //  0: CELL_SHAPE_EMPTY
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  1: CELL_SHAPE_VERTEX
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  2: Unused
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  3: CELL_SHAPE_LINE
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  4: CELL_SHAPE_POLY_LINE
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  5: CELL_SHAPE_TRIANGLE
        { { 0, 1 },   { 1, 2 },   { 2, 0 },   { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  6: Unused
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  7: CELL_SHAPE_POLYGON  --- special case ---
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  8: Unused
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        //  9: CELL_SHAPE_QUAD
        { { 0, 1 },   { 1, 2 },   { 2, 3 },   { 3, 0 },   { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        // 10: CELL_SHAPE_TETRA
        { { 0, 1 },   { 1, 2 },   { 2, 0 },   { 0, 3 },   { 1, 3 },   { 2, 3 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        // 11: Unused
        { { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 },
          { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        // 12: CELL_SHAPE_HEXAHEDRON
        { { 0, 1 }, { 1, 2 }, { 3, 2 }, { 0, 3 }, { 4, 5 }, { 5, 6 },
          { 7, 6 }, { 4, 7 }, { 0, 4 }, { 1, 5 }, { 3, 7 }, { 2, 6 } },
        // 13: CELL_SHAPE_WEDGE
        { { 0, 1 }, { 1, 2 }, { 2, 0 }, { 3, 4 },   { 4, 5 },   { 5, 3 },
          { 0, 3 }, { 1, 4 }, { 2, 5 }, { -1, -1 }, { -1, -1 }, { -1, -1 } },
        // 14: CELL_SHAPE_PYRAMID
        { { 0, 1 }, { 1, 2 }, { 2, 3 },   { 3, 0 },   { 0, 4 },   { 1, 4 },
          { 2, 4 }, { 3, 4 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 } }
        // clang-format on
      };

    return pointsInEdge[cellShapeId][edgeIndex][localPointIndex];
  }
};

} // namespace detail

template <typename CellShapeTag>
static inline VTKM_EXEC vtkm::ErrorCode CellEdgeNumberOfEdges(vtkm::IdComponent numPoints,
                                                              CellShapeTag,
                                                              vtkm::IdComponent& numEdges)
{
  if (numPoints != vtkm::CellTraits<CellShapeTag>::NUM_POINTS)
  {
    numEdges = -1;
    return vtkm::ErrorCode::InvalidNumberOfPoints;
  }
  numEdges = detail::CellEdgeTables{}.NumEdges(CellShapeTag::Id);
  return vtkm::ErrorCode::Success;
}

static inline VTKM_EXEC vtkm::ErrorCode CellEdgeNumberOfEdges(vtkm::IdComponent numPoints,
                                                              vtkm::CellShapeTagPolygon,
                                                              vtkm::IdComponent& numEdges)
{
  if (numPoints <= 0)
  {
    numEdges = -1;
    return vtkm::ErrorCode::InvalidNumberOfPoints;
  }
  numEdges = numPoints;
  return vtkm::ErrorCode::Success;
}

static inline VTKM_EXEC vtkm::ErrorCode CellEdgeNumberOfEdges(vtkm::IdComponent numPoints,
                                                              vtkm::CellShapeTagPolyLine,
                                                              vtkm::IdComponent& numEdges)
{
  if (numPoints <= 0)
  {
    numEdges = -1;
    return vtkm::ErrorCode::InvalidNumberOfPoints;
  }
  numEdges = detail::CellEdgeTables{}.NumEdges(vtkm::CELL_SHAPE_POLY_LINE);
  return vtkm::ErrorCode::Success;
}

/// @brief Get the number of edges in a cell.
///
/// @param[in]  numPoints The number of points in the cell.
/// @param[in]  shape A tag of type `CellShapeTag*` to identify the shape of the cell.
///     This method is overloaded for different shape types.
/// @param[out] numEdges A reference to return the number of edges.
static inline VTKM_EXEC vtkm::ErrorCode CellEdgeNumberOfEdges(vtkm::IdComponent numPoints,
                                                              vtkm::CellShapeTagGeneric shape,
                                                              vtkm::IdComponent& numEdges)
{
  if (shape.Id == vtkm::CELL_SHAPE_POLYGON)
  {
    return CellEdgeNumberOfEdges(numPoints, vtkm::CellShapeTagPolygon(), numEdges);
  }
  else if (shape.Id == vtkm::CELL_SHAPE_POLY_LINE)
  {
    return CellEdgeNumberOfEdges(numPoints, vtkm::CellShapeTagPolyLine(), numEdges);
  }
  else
  {
    numEdges = detail::CellEdgeTables{}.NumEdges(shape.Id);
    return vtkm::ErrorCode::Success;
  }
}

template <typename CellShapeTag>
static inline VTKM_EXEC vtkm::ErrorCode CellEdgeLocalIndex(vtkm::IdComponent numPoints,
                                                           vtkm::IdComponent pointIndex,
                                                           vtkm::IdComponent edgeIndex,
                                                           CellShapeTag shape,
                                                           vtkm::IdComponent& result)
{
  if ((pointIndex < 0) || (pointIndex > 1))
  {
    result = -1;
    return vtkm::ErrorCode::InvalidPointId;
  }
  if ((edgeIndex < 0) || (edgeIndex >= detail::CellEdgeTables::MAX_NUM_EDGES))
  {
    result = -1;
    return vtkm::ErrorCode::InvalidEdgeId;
  }

  vtkm::IdComponent numEdges;
  VTKM_RETURN_ON_ERROR(vtkm::exec::CellEdgeNumberOfEdges(numPoints, shape, numEdges));
  if (edgeIndex >= numEdges)
  {
    result = -1;
    return vtkm::ErrorCode::InvalidEdgeId;
  }

  detail::CellEdgeTables table;
  result = table.PointsInEdge(CellShapeTag::Id, edgeIndex, pointIndex);
  return vtkm::ErrorCode::Success;
}

static inline VTKM_EXEC vtkm::ErrorCode CellEdgeLocalIndex(vtkm::IdComponent numPoints,
                                                           vtkm::IdComponent pointIndex,
                                                           vtkm::IdComponent edgeIndex,
                                                           vtkm::CellShapeTagPolygon,
                                                           vtkm::IdComponent& result)
{
  if (numPoints < 3)
  {
    result = -1;
    return vtkm::ErrorCode::InvalidNumberOfPoints;
  }
  if ((pointIndex < 0) || (pointIndex > 1))
  {
    result = -1;
    return vtkm::ErrorCode::InvalidPointId;
  }
  if ((edgeIndex < 0) || (edgeIndex >= numPoints))
  {
    result = -1;
    return vtkm::ErrorCode::InvalidEdgeId;
  }

  if (edgeIndex + pointIndex < numPoints)
  {
    result = edgeIndex + pointIndex;
  }
  else
  {
    result = 0;
  }
  return vtkm::ErrorCode::Success;
}

/// Given the index for an edge of a cell and one of the points on that edge, this
/// function returns the point index for the cell.
/// To get the point indices relative to the data set, the returned index should be used
/// to reference a `PointIndices` list.
///
/// @param[in]  numPoints The number of points in the cell.
/// @param[in]  pointIndex The index of the edge within the cell.
/// @param[in]  edgeIndex The index of the point on the edge (either 0 or 1).
/// @param[in]  shape A tag of type `CellShapeTag*` to identify the shape of the cell.
///     This method is overloaded for different shape types.
/// @param[out] result Reference to put the index of the point relative to the cell
///     (between 0 and the number of points in the cell).
static inline VTKM_EXEC vtkm::ErrorCode CellEdgeLocalIndex(vtkm::IdComponent numPoints,
                                                           vtkm::IdComponent pointIndex,
                                                           vtkm::IdComponent edgeIndex,
                                                           vtkm::CellShapeTagGeneric shape,
                                                           vtkm::IdComponent& result)
{
  if ((pointIndex < 0) || (pointIndex > 1))
  {
    result = -1;
    return vtkm::ErrorCode::InvalidPointId;
  }
  if ((edgeIndex < 0) || (edgeIndex >= detail::CellEdgeTables::MAX_NUM_EDGES))
  {
    result = -1;
    return vtkm::ErrorCode::InvalidEdgeId;
  }

  if (shape.Id == vtkm::CELL_SHAPE_POLYGON)
  {
    return CellEdgeLocalIndex(
      numPoints, pointIndex, edgeIndex, vtkm::CellShapeTagPolygon(), result);
  }
  else
  {
    detail::CellEdgeTables table;
    if (edgeIndex >= table.NumEdges(shape.Id))
    {
      result = -1;
      return vtkm::ErrorCode::InvalidEdgeId;
    }

    result = table.PointsInEdge(shape.Id, edgeIndex, pointIndex);
    return vtkm::ErrorCode::Success;
  }
}

/// @brief Returns a canonical identifier for a cell edge
///
/// Given information about a cell edge and the global point indices for that cell, returns a
/// vtkm::Id2 that contains values that are unique to that edge. The values for two edges will be
/// the same if and only if the edges contain the same points.
///
template <typename CellShapeTag, typename GlobalPointIndicesVecType>
static inline VTKM_EXEC vtkm::ErrorCode CellEdgeCanonicalId(
  vtkm::IdComponent numPoints,
  vtkm::IdComponent edgeIndex,
  CellShapeTag shape,
  const GlobalPointIndicesVecType& globalPointIndicesVec,
  vtkm::Id2& result)
{
  result = { -1, -1 };

  vtkm::IdComponent localIndex0;
  VTKM_RETURN_ON_ERROR(vtkm::exec::CellEdgeLocalIndex(numPoints, 0, edgeIndex, shape, localIndex0));
  vtkm::Id pointIndex0 = globalPointIndicesVec[localIndex0];

  vtkm::IdComponent localIndex1;
  VTKM_RETURN_ON_ERROR(vtkm::exec::CellEdgeLocalIndex(numPoints, 1, edgeIndex, shape, localIndex1));
  vtkm::Id pointIndex1 = globalPointIndicesVec[localIndex1];

  if (pointIndex0 < pointIndex1)
  {
    result = vtkm::Id2(pointIndex0, pointIndex1);
  }
  else
  {
    result = vtkm::Id2(pointIndex1, pointIndex0);
  }

  return vtkm::ErrorCode::Success;
}

}
} // namespace vtkm::exec

#endif //vtk_m_exec_CellFaces_h
