//============================================================================
//  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 vtkm_m_worklet_Clip_h
#define vtkm_m_worklet_Clip_h

#include <vtkm/filter/contour/worklet/clip/ClipTables.h>
#include <vtkm/worklet/DispatcherMapField.h>
#include <vtkm/worklet/DispatcherMapTopology.h>
#include <vtkm/worklet/DispatcherReduceByKey.h>
#include <vtkm/worklet/Keys.h>
#include <vtkm/worklet/WorkletMapField.h>
#include <vtkm/worklet/WorkletMapTopology.h>
#include <vtkm/worklet/WorkletReduceByKey.h>

#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/ArrayCopy.h>
#include <vtkm/cont/ArrayHandlePermutation.h>
#include <vtkm/cont/ArrayHandleView.h>
#include <vtkm/cont/CellSetExplicit.h>
#include <vtkm/cont/ConvertNumComponentsToOffsets.h>
#include <vtkm/cont/CoordinateSystem.h>
#include <vtkm/cont/Timer.h>
#include <vtkm/cont/UnknownArrayHandle.h>

#include <vtkm/ImplicitFunction.h>

#include <utility>
#include <vtkm/exec/FunctorBase.h>

#if defined(THRUST_MAJOR_VERSION) && THRUST_MAJOR_VERSION == 1 && THRUST_MINOR_VERSION == 8 && \
  THRUST_SUBMINOR_VERSION < 3
// Workaround a bug in thrust 1.8.0 - 1.8.2 scan implementations which produces
// wrong results
#include <vtkm/exec/cuda/internal/ThrustPatches.h>
VTKM_THIRDPARTY_PRE_INCLUDE
#include <thrust/detail/type_traits.h>
VTKM_THIRDPARTY_POST_INCLUDE
#define THRUST_SCAN_WORKAROUND
#endif

namespace vtkm
{
namespace worklet
{
struct ClipStats
{
  vtkm::Id NumberOfCells = 0;
  vtkm::Id NumberOfIndices = 0;
  vtkm::Id NumberOfEdgeIndices = 0;

  // Stats for interpolating new points within cell.
  vtkm::Id NumberOfInCellPoints = 0;
  vtkm::Id NumberOfInCellIndices = 0;
  vtkm::Id NumberOfInCellInterpPoints = 0;
  vtkm::Id NumberOfInCellEdgeIndices = 0;

  struct SumOp
  {
    VTKM_EXEC_CONT
    ClipStats operator()(const ClipStats& stat1, const ClipStats& stat2) const
    {
      ClipStats sum = stat1;
      sum.NumberOfCells += stat2.NumberOfCells;
      sum.NumberOfIndices += stat2.NumberOfIndices;
      sum.NumberOfEdgeIndices += stat2.NumberOfEdgeIndices;
      sum.NumberOfInCellPoints += stat2.NumberOfInCellPoints;
      sum.NumberOfInCellIndices += stat2.NumberOfInCellIndices;
      sum.NumberOfInCellInterpPoints += stat2.NumberOfInCellInterpPoints;
      sum.NumberOfInCellEdgeIndices += stat2.NumberOfInCellEdgeIndices;
      return sum;
    }
  };
};

struct EdgeInterpolation
{
  vtkm::Id Vertex1 = -1;
  vtkm::Id Vertex2 = -1;
  vtkm::Float64 Weight = 0;

  struct LessThanOp
  {
    VTKM_EXEC
    bool operator()(const EdgeInterpolation& v1, const EdgeInterpolation& v2) const
    {
      return (v1.Vertex1 < v2.Vertex1) || (v1.Vertex1 == v2.Vertex1 && v1.Vertex2 < v2.Vertex2);
    }
  };

  struct EqualToOp
  {
    VTKM_EXEC
    bool operator()(const EdgeInterpolation& v1, const EdgeInterpolation& v2) const
    {
      return v1.Vertex1 == v2.Vertex1 && v1.Vertex2 == v2.Vertex2;
    }
  };
};

namespace internal
{

template <typename T>
VTKM_EXEC_CONT T Scale(const T& val, vtkm::Float64 scale)
{
  return static_cast<T>(scale * static_cast<vtkm::Float64>(val));
}

template <typename T, vtkm::IdComponent NumComponents>
VTKM_EXEC_CONT vtkm::Vec<T, NumComponents> Scale(const vtkm::Vec<T, NumComponents>& val,
                                                 vtkm::Float64 scale)
{
  return val * scale;
}

template <typename Device>
class ExecutionConnectivityExplicit
{
private:
  using UInt8Portal = typename vtkm::cont::ArrayHandle<vtkm::UInt8>::WritePortalType;
  using IdComponentPortal = typename vtkm::cont::ArrayHandle<vtkm::IdComponent>::WritePortalType;
  using IdPortal = typename vtkm::cont::ArrayHandle<vtkm::Id>::WritePortalType;

public:
  VTKM_CONT
  ExecutionConnectivityExplicit() = default;

  VTKM_CONT
  ExecutionConnectivityExplicit(vtkm::cont::ArrayHandle<vtkm::UInt8> shapes,
                                vtkm::cont::ArrayHandle<vtkm::IdComponent> numberOfIndices,
                                vtkm::cont::ArrayHandle<vtkm::Id> connectivity,
                                vtkm::cont::ArrayHandle<vtkm::Id> offsets,
                                ClipStats stats,
                                vtkm::cont::Token& token)
    : Shapes(shapes.PrepareForOutput(stats.NumberOfCells, Device(), token))
    , NumberOfIndices(numberOfIndices.PrepareForOutput(stats.NumberOfCells, Device(), token))
    , Connectivity(connectivity.PrepareForOutput(stats.NumberOfIndices, Device(), token))
    , Offsets(offsets.PrepareForOutput(stats.NumberOfCells, Device(), token))
  {
  }

  VTKM_EXEC
  void SetCellShape(vtkm::Id cellIndex, vtkm::UInt8 shape) { this->Shapes.Set(cellIndex, shape); }

  VTKM_EXEC
  void SetNumberOfIndices(vtkm::Id cellIndex, vtkm::IdComponent numIndices)
  {
    this->NumberOfIndices.Set(cellIndex, numIndices);
  }

  VTKM_EXEC
  void SetIndexOffset(vtkm::Id cellIndex, vtkm::Id indexOffset)
  {
    this->Offsets.Set(cellIndex, indexOffset);
  }

  VTKM_EXEC
  void SetConnectivity(vtkm::Id connectivityIndex, vtkm::Id pointIndex)
  {
    this->Connectivity.Set(connectivityIndex, pointIndex);
  }

private:
  UInt8Portal Shapes;
  IdComponentPortal NumberOfIndices;
  IdPortal Connectivity;
  IdPortal Offsets;
};

class ConnectivityExplicit : vtkm::cont::ExecutionObjectBase
{
public:
  VTKM_CONT
  ConnectivityExplicit() = default;

  VTKM_CONT
  ConnectivityExplicit(const vtkm::cont::ArrayHandle<vtkm::UInt8>& shapes,
                       const vtkm::cont::ArrayHandle<vtkm::IdComponent>& numberOfIndices,
                       const vtkm::cont::ArrayHandle<vtkm::Id>& connectivity,
                       const vtkm::cont::ArrayHandle<vtkm::Id>& offsets,
                       const ClipStats& stats)
    : Shapes(shapes)
    , NumberOfIndices(numberOfIndices)
    , Connectivity(connectivity)
    , Offsets(offsets)
    , Stats(stats)
  {
  }

  template <typename Device>
  VTKM_CONT ExecutionConnectivityExplicit<Device> PrepareForExecution(
    Device,
    vtkm::cont::Token& token) const
  {
    ExecutionConnectivityExplicit<Device> execConnectivity(
      this->Shapes, this->NumberOfIndices, this->Connectivity, this->Offsets, this->Stats, token);
    return execConnectivity;
  }

private:
  vtkm::cont::ArrayHandle<vtkm::UInt8> Shapes;
  vtkm::cont::ArrayHandle<vtkm::IdComponent> NumberOfIndices;
  vtkm::cont::ArrayHandle<vtkm::Id> Connectivity;
  vtkm::cont::ArrayHandle<vtkm::Id> Offsets;
  vtkm::worklet::ClipStats Stats;
};


} // namespace internal

class Clip
{
  // Add support for invert
public:
  using TypeClipStats = vtkm::List<ClipStats>;

  using TypeEdgeInterp = vtkm::List<EdgeInterpolation>;

  class ComputeStats : public vtkm::worklet::WorkletVisitCellsWithPoints
  {
  public:
    VTKM_CONT
    ComputeStats(vtkm::Float64 value, bool invert)
      : Value(value)
      , Invert(invert)
    {
    }

    using ControlSignature =
      void(CellSetIn, FieldInPoint, ExecObject clippingData, FieldOutCell, FieldOutCell);

    using ExecutionSignature = void(CellShape, PointCount, _2, _3, _4, _5);

    using InputDomain = _1;

    template <typename CellShapeTag, typename ScalarFieldVec, typename DeviceAdapter>
    VTKM_EXEC void operator()(CellShapeTag shape,
                              vtkm::IdComponent pointCount,
                              const ScalarFieldVec& scalars,
                              const internal::ClipTables::DevicePortal<DeviceAdapter>& clippingData,
                              ClipStats& clipStat,
                              vtkm::Id& clipDataIndex) const
    {
      (void)shape; // C4100 false positive workaround
      vtkm::Id caseId = 0;
      for (vtkm::IdComponent iter = pointCount - 1; iter >= 0; iter--)
      {
        if (!this->Invert && static_cast<vtkm::Float64>(scalars[iter]) <= this->Value)
        {
          caseId++;
        }
        else if (this->Invert && static_cast<vtkm::Float64>(scalars[iter]) >= this->Value)
        {
          caseId++;
        }
        if (iter > 0)
          caseId *= 2;
      }
      vtkm::Id index = clippingData.GetCaseIndex(shape.Id, caseId);
      clipDataIndex = index;
      vtkm::Id numberOfCells = clippingData.ValueAt(index++);
      clipStat.NumberOfCells = numberOfCells;
      for (vtkm::IdComponent shapes = 0; shapes < numberOfCells; shapes++)
      {
        vtkm::Id cellShape = clippingData.ValueAt(index++);
        vtkm::Id numberOfIndices = clippingData.ValueAt(index++);
        if (cellShape == 0)
        {
          --clipStat.NumberOfCells;
          // Shape is 0, which is a case of interpolating new point within a cell
          // Gather stats for later operation.
          clipStat.NumberOfInCellPoints = 1;
          clipStat.NumberOfInCellInterpPoints = numberOfIndices;
          for (vtkm::IdComponent points = 0; points < numberOfIndices; points++, index++)
          {
            //Find how many points need to be calculated using edge interpolation.
            vtkm::Id element = clippingData.ValueAt(index);
            clipStat.NumberOfInCellEdgeIndices += (element < 100) ? 1 : 0;
          }
        }
        else
        {
          // Collect number of indices required for storing current shape
          clipStat.NumberOfIndices += numberOfIndices;
          // Collect number of new points
          for (vtkm::IdComponent points = 0; points < numberOfIndices; points++, index++)
          {
            //Find how many points need to found using edge interpolation.
            vtkm::Id element = clippingData.ValueAt(index);
            if (element == 255)
            {
              clipStat.NumberOfInCellIndices++;
            }
            else if (element < 100)
            {
              clipStat.NumberOfEdgeIndices++;
            }
          }
        }
      }
    }

  private:
    vtkm::Float64 Value;
    bool Invert;
  };

  class GenerateCellSet : public vtkm::worklet::WorkletVisitCellsWithPoints
  {
  public:
    VTKM_CONT
    GenerateCellSet(vtkm::Float64 value)
      : Value(value)
    {
    }

    using ControlSignature = void(CellSetIn,
                                  FieldInPoint,
                                  FieldInCell clipTableIndices,
                                  FieldInCell clipStats,
                                  ExecObject clipTables,
                                  ExecObject connectivityObject,
                                  WholeArrayOut edgePointReverseConnectivity,
                                  WholeArrayOut edgePointInterpolation,
                                  WholeArrayOut inCellReverseConnectivity,
                                  WholeArrayOut inCellEdgeReverseConnectivity,
                                  WholeArrayOut inCellEdgeInterpolation,
                                  WholeArrayOut inCellInterpolationKeys,
                                  WholeArrayOut inCellInterpolationInfo,
                                  WholeArrayOut cellMapOutputToInput);

    using ExecutionSignature = void(CellShape,
                                    WorkIndex,
                                    PointIndices,
                                    _2,
                                    _3,
                                    _4,
                                    _5,
                                    _6,
                                    _7,
                                    _8,
                                    _9,
                                    _10,
                                    _11,
                                    _12,
                                    _13,
                                    _14);

    template <typename CellShapeTag,
              typename PointVecType,
              typename ScalarVecType,
              typename ConnectivityObject,
              typename IdArrayType,
              typename EdgeInterpolationPortalType,
              typename DeviceAdapter>
    VTKM_EXEC void operator()(CellShapeTag shape,
                              vtkm::Id workIndex,
                              const PointVecType& points,
                              const ScalarVecType& scalars,
                              vtkm::Id clipDataIndex,
                              const ClipStats& clipStats,
                              const internal::ClipTables::DevicePortal<DeviceAdapter>& clippingData,
                              ConnectivityObject& connectivityObject,
                              IdArrayType& edgePointReverseConnectivity,
                              EdgeInterpolationPortalType& edgePointInterpolation,
                              IdArrayType& inCellReverseConnectivity,
                              IdArrayType& inCellEdgeReverseConnectivity,
                              EdgeInterpolationPortalType& inCellEdgeInterpolation,
                              IdArrayType& inCellInterpolationKeys,
                              IdArrayType& inCellInterpolationInfo,
                              IdArrayType& cellMapOutputToInput) const
    {
      (void)shape;
      vtkm::Id clipIndex = clipDataIndex;
      // Start index for the cells of this case.
      vtkm::Id cellIndex = clipStats.NumberOfCells;
      // Start index to store connevtivity of this case.
      vtkm::Id connectivityIndex = clipStats.NumberOfIndices;
      // Start indices for reverse mapping into connectivity for this case.
      vtkm::Id edgeIndex = clipStats.NumberOfEdgeIndices;
      vtkm::Id inCellIndex = clipStats.NumberOfInCellIndices;
      vtkm::Id inCellPoints = clipStats.NumberOfInCellPoints;
      // Start Indices to keep track of interpolation points for new cell.
      vtkm::Id inCellInterpPointIndex = clipStats.NumberOfInCellInterpPoints;
      vtkm::Id inCellEdgeInterpIndex = clipStats.NumberOfInCellEdgeIndices;

      // Iterate over the shapes for the current cell and begin to fill connectivity.
      vtkm::Id numberOfCells = clippingData.ValueAt(clipIndex++);
      for (vtkm::Id cell = 0; cell < numberOfCells; ++cell)
      {
        vtkm::UInt8 cellShape = clippingData.ValueAt(clipIndex++);
        vtkm::IdComponent numberOfPoints = clippingData.ValueAt(clipIndex++);
        if (cellShape == 0)
        {
          // Case for a new cell point.

          // 1. Output the input cell id for which we need to generate new point.
          // 2. Output number of points used for interpolation.
          // 3. If vertex
          //    - Add vertex to connectivity interpolation information.
          // 4. If edge
          //    - Add edge interpolation information for new points.
          //    - Reverse connectivity map for new points.
          // Make an array which has all the elements that need to be used
          // for interpolation.
          for (vtkm::IdComponent point = 0; point < numberOfPoints;
               point++, inCellInterpPointIndex++, clipIndex++)
          {
            vtkm::IdComponent entry =
              static_cast<vtkm::IdComponent>(clippingData.ValueAt(clipIndex));
            inCellInterpolationKeys.Set(inCellInterpPointIndex, workIndex);
            if (entry >= 100)
            {
              inCellInterpolationInfo.Set(inCellInterpPointIndex, points[entry - 100]);
            }
            else
            {
              internal::ClipTables::EdgeVec edge = clippingData.GetEdge(shape.Id, entry);
              VTKM_ASSERT(edge[0] != 255);
              VTKM_ASSERT(edge[1] != 255);
              EdgeInterpolation ei;
              ei.Vertex1 = points[edge[0]];
              ei.Vertex2 = points[edge[1]];
              // For consistency purposes keep the points ordered.
              if (ei.Vertex1 > ei.Vertex2)
              {
                this->swap(ei.Vertex1, ei.Vertex2);
                this->swap(edge[0], edge[1]);
              }
              ei.Weight = (static_cast<vtkm::Float64>(scalars[edge[0]]) - this->Value) /
                static_cast<vtkm::Float64>(scalars[edge[1]] - scalars[edge[0]]);

              inCellEdgeReverseConnectivity.Set(inCellEdgeInterpIndex, inCellInterpPointIndex);
              inCellEdgeInterpolation.Set(inCellEdgeInterpIndex, ei);
              inCellEdgeInterpIndex++;
            }
          }
        }
        else
        {
          // Just a normal cell, generate edge representations,

          // 1. Add cell type to connectivity information.
          // 2. If vertex
          //    - Add vertex to connectivity information.
          // 3. If edge point
          //    - Add edge to edge points
          //    - Add edge point index to edge point reverse connectivity.
          // 4. If cell point
          //    - Add cell point index to connectivity
          //      (as there is only one cell point per required cell)
          // 5. Store input cell index against current cell for mapping cell data.
          connectivityObject.SetCellShape(cellIndex, cellShape);
          connectivityObject.SetNumberOfIndices(cellIndex, numberOfPoints);
          connectivityObject.SetIndexOffset(cellIndex, connectivityIndex);
          for (vtkm::IdComponent point = 0; point < numberOfPoints; point++, clipIndex++)
          {
            vtkm::IdComponent entry =
              static_cast<vtkm::IdComponent>(clippingData.ValueAt(clipIndex));
            if (entry == 255) // case of cell point interpolation
            {
              // Add index of the corresponding cell point.
              inCellReverseConnectivity.Set(inCellIndex++, connectivityIndex);
              connectivityObject.SetConnectivity(connectivityIndex, inCellPoints);
              connectivityIndex++;
            }
            else if (entry >= 100) // existing vertex
            {
              connectivityObject.SetConnectivity(connectivityIndex++, points[entry - 100]);
            }
            else // case of a new edge point
            {
              internal::ClipTables::EdgeVec edge = clippingData.GetEdge(shape.Id, entry);
              VTKM_ASSERT(edge[0] != 255);
              VTKM_ASSERT(edge[1] != 255);
              EdgeInterpolation ei;
              ei.Vertex1 = points[edge[0]];
              ei.Vertex2 = points[edge[1]];
              // For consistency purposes keep the points ordered.
              if (ei.Vertex1 > ei.Vertex2)
              {
                this->swap(ei.Vertex1, ei.Vertex2);
                this->swap(edge[0], edge[1]);
              }
              ei.Weight = (static_cast<vtkm::Float64>(scalars[edge[0]]) - this->Value) /
                static_cast<vtkm::Float64>(scalars[edge[1]] - scalars[edge[0]]);
              //Add to set of new edge points
              //Add reverse connectivity;
              edgePointReverseConnectivity.Set(edgeIndex, connectivityIndex++);
              edgePointInterpolation.Set(edgeIndex, ei);
              edgeIndex++;
            }
          }
          cellMapOutputToInput.Set(cellIndex, workIndex);
          ++cellIndex;
        }
      }
    }

    template <typename T>
    VTKM_EXEC void swap(T& v1, T& v2) const
    {
      T temp = v1;
      v1 = v2;
      v2 = temp;
    }

  private:
    vtkm::Float64 Value;
  };

  class ScatterEdgeConnectivity : public vtkm::worklet::WorkletMapField
  {
  public:
    VTKM_CONT
    ScatterEdgeConnectivity(vtkm::Id edgePointOffset)
      : EdgePointOffset(edgePointOffset)
    {
    }

    using ControlSignature = void(FieldIn sourceValue,
                                  FieldIn destinationIndices,
                                  WholeArrayOut destinationData);

    using ExecutionSignature = void(_1, _2, _3);

    using InputDomain = _1;

    template <typename ConnectivityDataType>
    VTKM_EXEC void operator()(vtkm::Id sourceValue,
                              vtkm::Id destinationIndex,
                              ConnectivityDataType& destinationData) const
    {
      destinationData.Set(destinationIndex, (sourceValue + EdgePointOffset));
    }

  private:
    vtkm::Id EdgePointOffset;
  };

  class ScatterInCellConnectivity : public vtkm::worklet::WorkletMapField
  {
  public:
    VTKM_CONT
    ScatterInCellConnectivity(vtkm::Id inCellPointOffset)
      : InCellPointOffset(inCellPointOffset)
    {
    }

    using ControlSignature = void(FieldIn destinationIndices, WholeArrayOut destinationData);

    using ExecutionSignature = void(_1, _2);

    using InputDomain = _1;

    template <typename ConnectivityDataType>
    VTKM_EXEC void operator()(vtkm::Id destinationIndex,
                              ConnectivityDataType& destinationData) const
    {
      auto sourceValue = destinationData.Get(destinationIndex);
      destinationData.Set(destinationIndex, (sourceValue + InCellPointOffset));
    }

  private:
    vtkm::Id InCellPointOffset;
  };

  Clip()
    : ClipTablesInstance()
    , EdgePointsInterpolation()
    , InCellInterpolationKeys()
    , InCellInterpolationInfo()
    , CellMapOutputToInput()
    , EdgePointsOffset()
    , InCellPointsOffset()
  {
  }

  template <typename CellSetType, typename ScalarsArrayHandle>
  vtkm::cont::CellSetExplicit<> Run(const CellSetType& cellSet,
                                    const ScalarsArrayHandle& scalars,
                                    vtkm::Float64 value,
                                    bool invert)
  {
    // Create the required output fields.
    vtkm::cont::ArrayHandle<ClipStats> clipStats;
    vtkm::cont::ArrayHandle<vtkm::Id> clipTableIndices;

    ComputeStats statsWorklet(value, invert);
    //Send this CellSet to process
    vtkm::worklet::DispatcherMapTopology<ComputeStats> statsDispatcher(statsWorklet);
    statsDispatcher.Invoke(cellSet, scalars, this->ClipTablesInstance, clipStats, clipTableIndices);

    ClipStats zero;
    vtkm::cont::ArrayHandle<ClipStats> cellSetStats;
    ClipStats total =
      vtkm::cont::Algorithm::ScanExclusive(clipStats, cellSetStats, ClipStats::SumOp(), zero);
    clipStats.ReleaseResources();

    vtkm::cont::ArrayHandle<vtkm::UInt8> shapes;
    vtkm::cont::ArrayHandle<vtkm::IdComponent> numberOfIndices;
    vtkm::cont::ArrayHandle<vtkm::Id> connectivity;
    vtkm::cont::ArrayHandle<vtkm::Id> offsets;
    internal::ConnectivityExplicit connectivityObject(
      shapes, numberOfIndices, connectivity, offsets, total);

    //Begin Process of Constructing the new CellSet.
    vtkm::cont::ArrayHandle<vtkm::Id> edgePointReverseConnectivity;
    edgePointReverseConnectivity.Allocate(total.NumberOfEdgeIndices);
    vtkm::cont::ArrayHandle<EdgeInterpolation> edgeInterpolation;
    edgeInterpolation.Allocate(total.NumberOfEdgeIndices);

    vtkm::cont::ArrayHandle<vtkm::Id> cellPointReverseConnectivity;
    cellPointReverseConnectivity.Allocate(total.NumberOfInCellIndices);
    vtkm::cont::ArrayHandle<vtkm::Id> cellPointEdgeReverseConnectivity;
    cellPointEdgeReverseConnectivity.Allocate(total.NumberOfInCellEdgeIndices);
    vtkm::cont::ArrayHandle<EdgeInterpolation> cellPointEdgeInterpolation;
    cellPointEdgeInterpolation.Allocate(total.NumberOfInCellEdgeIndices);

    this->InCellInterpolationKeys.Allocate(total.NumberOfInCellInterpPoints);
    this->InCellInterpolationInfo.Allocate(total.NumberOfInCellInterpPoints);
    this->CellMapOutputToInput.Allocate(total.NumberOfCells);

    GenerateCellSet cellSetWorklet(value);
    //Send this CellSet to process
    vtkm::worklet::DispatcherMapTopology<GenerateCellSet> cellSetDispatcher(cellSetWorklet);
    cellSetDispatcher.Invoke(cellSet,
                             scalars,
                             clipTableIndices,
                             cellSetStats,
                             this->ClipTablesInstance,
                             connectivityObject,
                             edgePointReverseConnectivity,
                             edgeInterpolation,
                             cellPointReverseConnectivity,
                             cellPointEdgeReverseConnectivity,
                             cellPointEdgeInterpolation,
                             this->InCellInterpolationKeys,
                             this->InCellInterpolationInfo,
                             this->CellMapOutputToInput);

    // Get unique EdgeInterpolation : unique edge points.
    // LowerBound for edgeInterpolation : get index into new edge points array.
    // LowerBound for cellPointEdgeInterpolation : get index into new edge points array.
    vtkm::cont::Algorithm::SortByKey(
      edgeInterpolation, edgePointReverseConnectivity, EdgeInterpolation::LessThanOp());
    vtkm::cont::Algorithm::Copy(edgeInterpolation, this->EdgePointsInterpolation);
    vtkm::cont::Algorithm::Unique(this->EdgePointsInterpolation, EdgeInterpolation::EqualToOp());

    vtkm::cont::ArrayHandle<vtkm::Id> edgeInterpolationIndexToUnique;
    vtkm::cont::Algorithm::LowerBounds(this->EdgePointsInterpolation,
                                       edgeInterpolation,
                                       edgeInterpolationIndexToUnique,
                                       EdgeInterpolation::LessThanOp());

    vtkm::cont::ArrayHandle<vtkm::Id> cellInterpolationIndexToUnique;
    vtkm::cont::Algorithm::LowerBounds(this->EdgePointsInterpolation,
                                       cellPointEdgeInterpolation,
                                       cellInterpolationIndexToUnique,
                                       EdgeInterpolation::LessThanOp());

    this->EdgePointsOffset = scalars.GetNumberOfValues();
    this->InCellPointsOffset =
      this->EdgePointsOffset + this->EdgePointsInterpolation.GetNumberOfValues();

    // Scatter these values into the connectivity array,
    // scatter indices are given in reverse connectivity.
    ScatterEdgeConnectivity scatterEdgePointConnectivity(this->EdgePointsOffset);
    vtkm::worklet::DispatcherMapField<ScatterEdgeConnectivity> scatterEdgeDispatcher(
      scatterEdgePointConnectivity);
    scatterEdgeDispatcher.Invoke(
      edgeInterpolationIndexToUnique, edgePointReverseConnectivity, connectivity);
    scatterEdgeDispatcher.Invoke(cellInterpolationIndexToUnique,
                                 cellPointEdgeReverseConnectivity,
                                 this->InCellInterpolationInfo);
    // Add offset in connectivity of all new in-cell points.
    ScatterInCellConnectivity scatterInCellPointConnectivity(this->InCellPointsOffset);
    vtkm::worklet::DispatcherMapField<ScatterInCellConnectivity> scatterInCellDispatcher(
      scatterInCellPointConnectivity);
    scatterInCellDispatcher.Invoke(cellPointReverseConnectivity, connectivity);

    vtkm::cont::CellSetExplicit<> output;
    vtkm::Id numberOfPoints = scalars.GetNumberOfValues() +
      this->EdgePointsInterpolation.GetNumberOfValues() + total.NumberOfInCellPoints;

    vtkm::cont::ConvertNumComponentsToOffsets(numberOfIndices, offsets);

    output.Fill(numberOfPoints, shapes, connectivity, offsets);
    return output;
  }

  template <typename CellSetType, typename ImplicitFunction>
  class ClipWithImplicitFunction
  {
  public:
    VTKM_CONT
    ClipWithImplicitFunction(Clip* clipper,
                             const CellSetType& cellSet,
                             const ImplicitFunction& function,
                             vtkm::Float64 offset,
                             bool invert,
                             vtkm::cont::CellSetExplicit<>* result)
      : Clipper(clipper)
      , CellSet(&cellSet)
      , Function(function)
      , Offset(offset)
      , Invert(invert)
      , Result(result)
    {
    }

    template <typename ArrayHandleType>
    VTKM_CONT void operator()(const ArrayHandleType& handle) const
    {
      // Evaluate the implicit function on the input coordinates using
      // ArrayHandleTransform
      vtkm::cont::ArrayHandleTransform<ArrayHandleType,
                                       vtkm::ImplicitFunctionValueFunctor<ImplicitFunction>>
        clipScalars(handle, this->Function);

      // Clip at locations where the implicit function evaluates to `Offset`
      *this->Result = this->Clipper->Run(*this->CellSet, clipScalars, this->Offset, this->Invert);
    }

  private:
    Clip* Clipper;
    const CellSetType* CellSet;
    ImplicitFunction Function;
    vtkm::Float64 Offset;
    bool Invert;
    vtkm::cont::CellSetExplicit<>* Result;
  };

  template <typename CellSetType, typename ImplicitFunction>
  vtkm::cont::CellSetExplicit<> Run(const CellSetType& cellSet,
                                    const ImplicitFunction& clipFunction,
                                    vtkm::Float64 offset,
                                    const vtkm::cont::CoordinateSystem& coords,
                                    bool invert)
  {
    vtkm::cont::CellSetExplicit<> output;

    ClipWithImplicitFunction<CellSetType, ImplicitFunction> clip(
      this, cellSet, clipFunction, offset, invert, &output);

    CastAndCall(coords, clip);
    return output;
  }

  template <typename CellSetType, typename ImplicitFunction>
  vtkm::cont::CellSetExplicit<> Run(const CellSetType& cellSet,
                                    const ImplicitFunction& clipFunction,
                                    const vtkm::cont::CoordinateSystem& coords,
                                    bool invert)
  {
    return this->Run(cellSet, clipFunction, 0.0, coords, invert);
  }

  template <typename ArrayHandleType>
  class InterpolateField
  {
  public:
    using ValueType = typename ArrayHandleType::ValueType;
    using TypeMappedValue = vtkm::List<ValueType>;

    InterpolateField(vtkm::cont::ArrayHandle<EdgeInterpolation> edgeInterpolationArray,
                     vtkm::cont::ArrayHandle<vtkm::Id> inCellInterpolationKeys,
                     vtkm::cont::ArrayHandle<vtkm::Id> inCellInterpolationInfo,
                     vtkm::Id edgePointsOffset,
                     vtkm::Id inCellPointsOffset,
                     ArrayHandleType* output)
      : EdgeInterpolationArray(edgeInterpolationArray)
      , InCellInterpolationKeys(inCellInterpolationKeys)
      , InCellInterpolationInfo(inCellInterpolationInfo)
      , EdgePointsOffset(edgePointsOffset)
      , InCellPointsOffset(inCellPointsOffset)
      , Output(output)
    {
    }

    class PerformEdgeInterpolations : public vtkm::worklet::WorkletMapField
    {
    public:
      PerformEdgeInterpolations(vtkm::Id edgePointsOffset)
        : EdgePointsOffset(edgePointsOffset)
      {
      }

      using ControlSignature = void(FieldIn edgeInterpolations, WholeArrayInOut outputField);

      using ExecutionSignature = void(_1, _2, WorkIndex);

      template <typename EdgeInterp, typename OutputFieldPortal>
      VTKM_EXEC void operator()(const EdgeInterp& ei,
                                OutputFieldPortal& field,
                                vtkm::Id workIndex) const
      {
        using T = typename OutputFieldPortal::ValueType;
        T v1 = field.Get(ei.Vertex1);
        T v2 = field.Get(ei.Vertex2);
        field.Set(this->EdgePointsOffset + workIndex,
                  static_cast<T>(internal::Scale(T(v1 - v2), ei.Weight) + v1));
      }

    private:
      vtkm::Id EdgePointsOffset;
    };

    class PerformInCellInterpolations : public vtkm::worklet::WorkletReduceByKey
    {
    public:
      using ControlSignature = void(KeysIn keys, ValuesIn toReduce, ReducedValuesOut centroid);

      using ExecutionSignature = void(_2, _3);

      template <typename MappedValueVecType, typename MappedValueType>
      VTKM_EXEC void operator()(const MappedValueVecType& toReduce, MappedValueType& centroid) const
      {
        vtkm::IdComponent numValues = toReduce.GetNumberOfComponents();
        MappedValueType sum = toReduce[0];
        for (vtkm::IdComponent i = 1; i < numValues; i++)
        {
          MappedValueType value = toReduce[i];
          // static_cast is for when MappedValueType is a small int that gets promoted to int32.
          sum = static_cast<MappedValueType>(sum + value);
        }
        centroid = internal::Scale(sum, 1. / static_cast<vtkm::Float64>(numValues));
      }
    };

    template <typename Storage>
    VTKM_CONT void operator()(const vtkm::cont::ArrayHandle<ValueType, Storage>& field) const
    {
      vtkm::worklet::Keys<vtkm::Id> interpolationKeys(InCellInterpolationKeys);

      vtkm::Id numberOfOriginalValues = field.GetNumberOfValues();
      vtkm::Id numberOfEdgePoints = EdgeInterpolationArray.GetNumberOfValues();
      vtkm::Id numberOfInCellPoints = interpolationKeys.GetUniqueKeys().GetNumberOfValues();

      ArrayHandleType result;
      result.Allocate(numberOfOriginalValues + numberOfEdgePoints + numberOfInCellPoints);
      vtkm::cont::Algorithm::CopySubRange(field, 0, numberOfOriginalValues, result);

      PerformEdgeInterpolations edgeInterpWorklet(numberOfOriginalValues);
      vtkm::worklet::DispatcherMapField<PerformEdgeInterpolations> edgeInterpDispatcher(
        edgeInterpWorklet);
      edgeInterpDispatcher.Invoke(this->EdgeInterpolationArray, result);

      // Perform a gather on output to get all required values for calculation of
      // centroids using the interpolation info array.
      using IdHandle = vtkm::cont::ArrayHandle<vtkm::Id>;
      using ValueHandle = vtkm::cont::ArrayHandle<ValueType>;
      vtkm::cont::ArrayHandlePermutation<IdHandle, ValueHandle> toReduceValues(
        InCellInterpolationInfo, result);

      vtkm::cont::ArrayHandle<ValueType> reducedValues;
      vtkm::worklet::DispatcherReduceByKey<PerformInCellInterpolations>
        inCellInterpolationDispatcher;
      inCellInterpolationDispatcher.Invoke(interpolationKeys, toReduceValues, reducedValues);
      vtkm::Id inCellPointsOffset = numberOfOriginalValues + numberOfEdgePoints;
      vtkm::cont::Algorithm::CopySubRange(
        reducedValues, 0, reducedValues.GetNumberOfValues(), result, inCellPointsOffset);
      *(this->Output) = result;
    }

  private:
    vtkm::cont::ArrayHandle<EdgeInterpolation> EdgeInterpolationArray;
    vtkm::cont::ArrayHandle<vtkm::Id> InCellInterpolationKeys;
    vtkm::cont::ArrayHandle<vtkm::Id> InCellInterpolationInfo;
    vtkm::Id EdgePointsOffset;
    vtkm::Id InCellPointsOffset;
    ArrayHandleType* Output;
  };

  template <typename ValueType, typename StorageType>
  vtkm::cont::ArrayHandle<ValueType> ProcessPointField(
    const vtkm::cont::ArrayHandle<ValueType, StorageType>& fieldData) const
  {
    using ResultType = vtkm::cont::ArrayHandle<ValueType>;
    using Worker = InterpolateField<ResultType>;

    ResultType output;

    Worker worker = Worker(this->EdgePointsInterpolation,
                           this->InCellInterpolationKeys,
                           this->InCellInterpolationInfo,
                           this->EdgePointsOffset,
                           this->InCellPointsOffset,
                           &output);
    worker(fieldData);

    return output;
  }

  vtkm::cont::ArrayHandle<vtkm::Id> GetCellMapOutputToInput() const
  {
    return this->CellMapOutputToInput;
  }

private:
  internal::ClipTables ClipTablesInstance;
  vtkm::cont::ArrayHandle<EdgeInterpolation> EdgePointsInterpolation;
  vtkm::cont::ArrayHandle<vtkm::Id> InCellInterpolationKeys;
  vtkm::cont::ArrayHandle<vtkm::Id> InCellInterpolationInfo;
  vtkm::cont::ArrayHandle<vtkm::Id> CellMapOutputToInput;
  vtkm::Id EdgePointsOffset;
  vtkm::Id InCellPointsOffset;
};
}
} // namespace vtkm::worklet

#if defined(THRUST_SCAN_WORKAROUND)
namespace thrust
{
namespace detail
{

// causes a different code path which does not have the bug
template <>
struct is_integral<vtkm::worklet::ClipStats> : public true_type
{
};
}
} // namespace thrust::detail
#endif

#endif // vtkm_m_worklet_Clip_h
