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

#include <vtkm/worklet/DispatcherMapTopology.h>

#include <vtkm/cont/ArrayCopy.h>
#include <vtkm/cont/ArrayHandleIndex.h>
#include <vtkm/cont/CellSetExplicit.h>
#include <vtkm/cont/CellSetPermutation.h>
#include <vtkm/cont/UncertainCellSet.h>

#include <vtkm/worklet/CellDeepCopy.h>

#include <vtkm/CellTraits.h>

#include <vtkm/exec/CellFace.h>

namespace vtkm
{
namespace worklet
{

struct RemoveDegenerateCells
{
  struct IdentifyDegenerates : vtkm::worklet::WorkletVisitCellsWithPoints
  {
    using ControlSignature = void(CellSetIn, FieldOutCell);
    using ExecutionSignature = _2(CellShape, PointIndices);
    using InputDomain = _1;

    template <vtkm::IdComponent dimensionality, typename CellShapeTag, typename PointVecType>
    VTKM_EXEC bool CheckForDimensionality(vtkm::CellTopologicalDimensionsTag<dimensionality>,
                                          CellShapeTag,
                                          PointVecType&& pointIds) const
    {
      const vtkm::IdComponent numPoints = pointIds.GetNumberOfComponents();
      vtkm::IdComponent numUnduplicatedPoints = 0;
      // Skip first point if it is the same as the last.
      for (vtkm::IdComponent localPointId = ((pointIds[0] != pointIds[numPoints - 1]) ? 0 : 1);
           localPointId < numPoints;
           ++localPointId)
      {
        ++numUnduplicatedPoints;
        if (numUnduplicatedPoints >= dimensionality + 1)
        {
          return true;
        }
        // Skip over any repeated points. Assume any repeated points are adjacent.
        while ((localPointId < numPoints - 1) &&
               (pointIds[localPointId] == pointIds[localPointId + 1]))
        {
          ++localPointId;
        }
      }
      return false;
    }

    template <typename CellShapeTag, typename PointVecType>
    VTKM_EXEC bool CheckForDimensionality(vtkm::CellTopologicalDimensionsTag<0>,
                                          CellShapeTag,
                                          PointVecType&&) const
    {
      return true;
    }

    template <typename CellShapeTag, typename PointVecType>
    VTKM_EXEC bool CheckForDimensionality(vtkm::CellTopologicalDimensionsTag<3>,
                                          CellShapeTag shape,
                                          PointVecType&& pointIds) const
    {
      vtkm::IdComponent numFaces;
      vtkm::exec::CellFaceNumberOfFaces(shape, numFaces);
      vtkm::Id numValidFaces = 0;
      for (vtkm::IdComponent faceId = 0; faceId < numFaces; ++faceId)
      {
        if (this->CheckForDimensionality(
              vtkm::CellTopologicalDimensionsTag<2>(), vtkm::CellShapeTagPolygon(), pointIds))
        {
          ++numValidFaces;
          if (numValidFaces > 2)
          {
            return true;
          }
        }
      }
      return false;
    }

    template <typename CellShapeTag, typename PointIdVec>
    VTKM_EXEC bool operator()(CellShapeTag shape, const PointIdVec& pointIds) const
    {
      using Traits = vtkm::CellTraits<CellShapeTag>;
      return this->CheckForDimensionality(
        typename Traits::TopologicalDimensionsTag(), shape, pointIds);
    }

    template <typename PointIdVec>
    VTKM_EXEC bool operator()(vtkm::CellShapeTagGeneric shape, PointIdVec&& pointIds) const
    {
      bool passCell = true;
      switch (shape.Id)
      {
        vtkmGenericCellShapeMacro(passCell = (*this)(CellShapeTag(), pointIds));
        default:
          // Raise an error for unknown cell type? Pass if we don't know.
          passCell = true;
      }
      return passCell;
    }
  };

  template <typename CellSetType>
  vtkm::cont::CellSetExplicit<> Run(const CellSetType& cellSet)
  {
    vtkm::cont::ArrayHandle<bool> passFlags;
    DispatcherMapTopology<IdentifyDegenerates> dispatcher;
    dispatcher.Invoke(cellSet, passFlags);

    vtkm::cont::ArrayHandleCounting<vtkm::Id> indices =
      vtkm::cont::make_ArrayHandleCounting(vtkm::Id(0), vtkm::Id(1), passFlags.GetNumberOfValues());
    vtkm::cont::Algorithm::CopyIf(
      vtkm::cont::ArrayHandleIndex(passFlags.GetNumberOfValues()), passFlags, this->ValidCellIds);

    vtkm::cont::CellSetPermutation<CellSetType> permutation(this->ValidCellIds, cellSet);
    vtkm::cont::CellSetExplicit<> output;
    vtkm::worklet::CellDeepCopy::Run(permutation, output);
    return output;
  }

  template <typename CellSetList>
  vtkm::cont::CellSetExplicit<> Run(const vtkm::cont::UncertainCellSet<CellSetList>& cellSet)
  {
    vtkm::cont::CellSetExplicit<> output;
    cellSet.CastAndCall([&](const auto& concrete) { output = this->Run(concrete); });

    return output;
  }

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

private:
  vtkm::cont::ArrayHandle<vtkm::Id> ValidCellIds;
};
}
}

#endif //vtk_m_worklet_RemoveDegeneratePolygons_h
