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

#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/CellSetExplicit.h>
#include <vtkm/cont/ConvertNumComponentsToOffsets.h>
#include <vtkm/cont/Invoker.h>
#include <vtkm/exec/ParametricCoordinates.h>
#include <vtkm/worklet/ScatterCounting.h>
#include <vtkm/worklet/WorkletMapTopology.h>

namespace vtkm
{
namespace worklet
{
class Dual
{
public:
  struct ComputeCellCoordinates : vtkm::worklet::WorkletVisitCellsWithPoints
  {
    using ControlSignature = void(CellSetIn, FieldInPoint coords, FieldOutCell cellCenter);
    using ExecutionSignature = void(PointCount, _3 cellCenter, _2 coords, CellShape);

    using InputDomain = _1;

    template <typename CoordsArrayType, typename ShapeTagType>
    VTKM_EXEC void operator()(vtkm::IdComponent numPointsInCell,
                              vtkm::Vec3f& cellCenter,
                              const CoordsArrayType& coords,
                              ShapeTagType cellShape) const
    {
      vtkm::Vec3f center;
      vtkm::exec::ParametricCoordinatesCenter(numPointsInCell, cellShape, center);
      vtkm::exec::CellInterpolate(coords, center, cellShape, cellCenter);
    }
  };

  struct ComputeSizesAndShapes : vtkm::worklet::WorkletVisitPointsWithCells
  {
    using ControlSignature = void(CellSetIn,
                                  FieldOutPoint newCellShapes,
                                  FieldOutPoint newCellSizes);
    using ExecutionSignature = void(CellCount, _2 newCellShape, _3 newCellSizes);

    using InputDomain = _1;

    template <typename ShapeTagType>
    VTKM_EXEC void operator()(vtkm::IdComponent cellCount,
                              ShapeTagType newCellShape,
                              vtkm::IdComponent newCellSize) const
    {
      newCellSize = cellCount;
      switch (cellCount)
      {
        case 1:
          newCellShape = vtkm::CELL_SHAPE_VERTEX;
          break;
        case 2:
          newCellShape = vtkm::CELL_SHAPE_LINE;
          break;
        case 3:
          newCellShape = vtkm::CELL_SHAPE_TRIANGLE;
          break;
        default:
          // Unsupported ?
          newCellShape = vtkm::CELL_SHAPE_POLYGON;
          break;
      }
    }
  };

  struct ComputeConnectivity : vtkm::worklet::WorkletVisitPointsWithCells
  {
    using ControlSignature = void(CellSetIn, FieldOutPoint connectivity);
    using ExecutionSignature = void(CellIndices, VisitIndex, _2 connectivity);

    using InputDomain = _1;

    using ScatterType = vtkm::worklet::ScatterCounting;

    template <typename CellIndiciesType>
    VTKM_EXEC void operator()(CellIndiciesType adjacentCells,
                              vtkm::IdComponent index,
                              vtkm::Id connectivity) const
    {
      connectivity = adjacentCells[index];
    }
  };

  template <typename CellSetType,
            typename CoordsComType,
            typename CoordsInStorageType,
            typename CoordsOutStorageType>
  void Run(
    const CellSetType& oldCellset,
    const vtkm::cont::ArrayHandle<vtkm::Vec<CoordsComType, 3>, CoordsInStorageType>& oldCoords,
    vtkm::cont::ArrayHandle<vtkm::Vec<CoordsComType, 3>, CoordsOutStorageType>& newCoords,
    vtkm::cont::CellSetExplicit<>& newCellset)
  {
    vtkm::cont::Invoker invoke;

    // Pass 1 : Cells become points whose coordinates are the cells' centers
    vtkm::cont::ArrayHandle<vtkm::Vec3f> cellCenters;
    invoke(ComputeCellCoordinates{}, oldCellset, oldCoords, newCoords);

    // // Pass 2 : Compute new cell sizes and shapes
    vtkm::cont::CellSetExplicit<>::ShapesArrayType cellShapes;
    vtkm::cont::ArrayHandle<vtkm::IdComponent> cellSizes;
    invoke(ComputeSizesAndShapes{}, oldCellset, cellShapes, cellSizes);

    // // Pass 3 : Compute new connectivity array (scatter)
    vtkm::cont::ArrayHandle<vtkm::Id> connectivity;
    vtkm::worklet::ScatterCounting scatter(cellSizes, true);
    vtkm::cont::ArrayHandle<vtkm::Id> offsets = scatter.GetInputToOutputMap();
    invoke(ComputeConnectivity{}, scatter, oldCellset, connectivity);

    // // Build the new dataset
    newCellset.Fill(oldCellset.GetNumberOfCells(),
                    cellShapes,
                    connectivity,
                    vtkm::cont::ConvertNumComponentsToOffsets(cellSizes));
  }
};
} // namespace vtkm::worklet
} // namespace vtkm

#endif // vtk_m_worklet_Dual_h
