#include <vtkm/cont/ArrayHandleUniformPointCoordinates.h>

#include <vtkm/worklet/DispatcherMapField.h>
#include <vtkm/worklet/ScatterCounting.h>
#include <vtkm/worklet/WorkletMapField.h>

#include <vtkm/Bounds.h>

#include <vtkm/cont/testing/Testing.h>

namespace
{

////
//// BEGIN-EXAMPLE ScatterCounting.cxx
////
struct ClipPoints
{
  class Count : public vtkm::worklet::WorkletMapField
  {
  public:
    using ControlSignature = void(FieldIn points, FieldOut count);
    using ExecutionSignature = _2(_1);
    using InputDomain = _1;

    VTKM_CONT Count(const vtkm::Bounds& bounds)
      : Bounds(bounds)
    {
    }

    template<typename T>
    VTKM_EXEC vtkm::IdComponent operator()(const vtkm::Vec<T, 3>& point) const
    {
      return (this->Bounds.Contains(point) ? 1 : 0);
    }

  private:
    vtkm::Bounds Bounds;
  };

  class Generate : public vtkm::worklet::WorkletMapField
  {
  public:
    using ControlSignature = void(FieldIn inPoints, FieldOut outPoints);
    using ExecutionSignature = void(_1, _2);
    using InputDomain = _1;

    ////
    //// BEGIN-EXAMPLE DeclareScatter.cxx
    ////
    using ScatterType = vtkm::worklet::ScatterCounting;

    template<typename CountArrayType>
    VTKM_CONT static ScatterType MakeScatter(const CountArrayType& countArray)
    {
      VTKM_IS_ARRAY_HANDLE(CountArrayType);
      return ScatterType(countArray);
    }
    ////
    //// END-EXAMPLE DeclareScatter.cxx
    ////

    template<typename InType, typename OutType>
    VTKM_EXEC void operator()(const vtkm::Vec<InType, 3>& inPoint,
                              vtkm::Vec<OutType, 3>& outPoint) const
    {
      // The scatter ensures that this method is only called for input points
      // that are passed to the output (where the count was 1). Thus, in this
      // case we know that we just need to copy the input to the output.
      outPoint = vtkm::Vec<OutType, 3>(inPoint[0], inPoint[1], inPoint[2]);
    }
  };
};

//
// Later in the associated Filter class...
//

//// PAUSE-EXAMPLE
struct DemoClipPoints
{
  vtkm::cont::Invoker Invoke;

  vtkm::Bounds Bounds;

  template<typename T, typename Storage>
  VTKM_CONT vtkm::cont::ArrayHandle<T> Run(
    const vtkm::cont::ArrayHandle<T, Storage>& inField)
  {
    //// RESUME-EXAMPLE
    vtkm::cont::ArrayHandle<vtkm::IdComponent> countArray;

    this->Invoke(ClipPoints::Count(this->Bounds), inField, countArray);

    vtkm::cont::ArrayHandle<T> clippedPointsArray;

    ////
    //// BEGIN-EXAMPLE ConstructScatterForInvoke.cxx
    ////
    vtkm::worklet::ScatterCounting generateScatter =
      ClipPoints::Generate::MakeScatter(countArray);
    this->Invoke(
      ClipPoints::Generate{}, generateScatter, inField, clippedPointsArray);
    ////
    //// END-EXAMPLE ConstructScatterForInvoke.cxx
    ////
    ////
    //// END-EXAMPLE ScatterCounting.cxx
    ////

    return clippedPointsArray;
  }
};

void Run()
{
  std::cout << "Trying clip points." << std::endl;
  vtkm::cont::ArrayHandleUniformPointCoordinates points(vtkm::Id3(10, 10, 10));
  vtkm::Bounds bounds(
    vtkm::Range(0.5, 8.5), vtkm::Range(0.5, 8.5), vtkm::Range(0.5, 8.5));

  VTKM_TEST_ASSERT(points.GetNumberOfValues() == 1000,
                   "Unexpected number of input points.");

  DemoClipPoints demo;
  demo.Bounds = bounds;
  vtkm::cont::ArrayHandle<vtkm::Vec3f> clippedPoints = demo.Run(points);

  vtkm::cont::printSummary_ArrayHandle(clippedPoints, std::cout);
  std::cout << std::endl;
  VTKM_TEST_ASSERT(clippedPoints.GetNumberOfValues() == 512,
                   "Unexpected number of output points.");
}

} // anonymous namespace

int ScatterCounting(int argc, char* argv[])
{
  return vtkm::cont::testing::Testing::Run(Run, argc, argv);
}
