////
//// BEGIN-EXAMPLE UseWorkletPointNeighborhood.cxx
////
#include <vtkm/worklet/DispatcherPointNeighborhood.h>
#include <vtkm/worklet/WorkletPointNeighborhood.h>

#include <vtkm/exec/BoundaryState.h>
#include <vtkm/exec/FieldNeighborhood.h>

#include <vtkm/cont/DataSet.h>
#include <vtkm/cont/DataSetFieldAdd.h>

namespace vtkm
{
namespace worklet
{

struct BoxBlur
{
  class ApplyBoxKernel : public vtkm::worklet::WorkletPointNeighborhood
  {
  private:
    vtkm::IdComponent NumberOfLayers;

  public:
    using ControlSignature = void(CellSetIn cellSet,
                                  FieldInNeighborhood<> inputField,
                                  FieldOut<> outputField);
    using ExecutionSignature = _3(_2, Boundary);

    using InputDomain = _1;

    ApplyBoxKernel(vtkm::IdComponent kernelSize)
    {
      VTKM_ASSERT(kernelSize >= 3);
      VTKM_ASSERT((kernelSize % 2) == 1);

      this->NumberOfLayers = (kernelSize - 1) / 2;
    }

    template<typename InputFieldPortalType>
    VTKM_EXEC typename InputFieldPortalType::ValueType operator()(
      const vtkm::exec::FieldNeighborhood<InputFieldPortalType>& inputField,
      const vtkm::exec::BoundaryState& boundary) const
    {
      using T = typename InputFieldPortalType::ValueType;

      ////
      //// BEGIN-EXAMPLE GetNeighborhoodBoundary.cxx
      ////
      auto minIndices = boundary.MinNeighborIndices(this->NumberOfLayers);
      auto maxIndices = boundary.MaxNeighborIndices(this->NumberOfLayers);

      T sum = 0;
      vtkm::IdComponent size = 0;
      for (vtkm::IdComponent k = minIndices[2]; k <= maxIndices[2]; ++k)
      {
        for (vtkm::IdComponent j = minIndices[1]; j <= maxIndices[1]; ++j)
        {
          for (vtkm::IdComponent i = minIndices[0]; i <= maxIndices[0]; ++i)
          {
            ////
            //// BEGIN-EXAMPLE GetNeighborhoodFieldValue.cxx
            ////
            sum = sum + inputField.Get(i, j, k);
            ////
            //// END-EXAMPLE GetNeighborhoodFieldValue.cxx
            ////
            ++size;
          }
        }
      }
      ////
      //// END-EXAMPLE GetNeighborhoodBoundary.cxx
      ////

      return static_cast<T>(sum / size);
    }
  };

  template<typename CellSetType, typename ValueType, typename StorageType>
  VTKM_CONT static vtkm::cont::ArrayHandle<ValueType> Run(
    const CellSetType& cellSet,
    const vtkm::cont::ArrayHandle<ValueType, StorageType>& inPointField,
    vtkm::IdComponent kernelSize)
  {
    vtkm::cont::ArrayHandle<ValueType> outCellField;

    vtkm::worklet::DispatcherPointNeighborhood<
      vtkm::worklet::BoxBlur::ApplyBoxKernel>
      dispatcher{ ApplyBoxKernel{ kernelSize } };
    dispatcher.Invoke(cellSet, inPointField, outCellField);

    return outCellField;
  }
};

} // namespace worklet
} // namespace vtkm
////
//// END-EXAMPLE UseWorkletPointNeighborhood.cxx
////

#include <vtkm/filter/FilterCell.h>

#include <vtkm/filter/internal/CreateResult.h>

namespace vtkm
{
namespace filter
{

class BoxFilter : public vtkm::filter::FilterCell<BoxFilter>
{
public:
  VTKM_CONT
  BoxFilter(vtkm::IdComponent kernelSize);

  template<typename ArrayHandleType, typename Policy>
  VTKM_CONT vtkm::cont::DataSet DoExecute(
    const vtkm::cont::DataSet& inDataSet,
    const ArrayHandleType& inField,
    const vtkm::filter::FieldMetadata& FieldMetadata,
    vtkm::filter::PolicyBase<Policy>);

private:
  vtkm::IdComponent KernelSize;
};

} // namespace filter
} // namespace vtkm

namespace vtkm
{
namespace filter
{

VTKM_CONT
BoxFilter::BoxFilter(vtkm::IdComponent kernelSize)
  : KernelSize(kernelSize)
{
  this->SetOutputFieldName("");
}

template<typename ArrayHandleType, typename Policy>
VTKM_CONT cont::DataSet BoxFilter::DoExecute(
  const vtkm::cont::DataSet& inDataSet,
  const ArrayHandleType& inField,
  const vtkm::filter::FieldMetadata& fieldMetadata,
  vtkm::filter::PolicyBase<Policy>)
{
  VTKM_IS_ARRAY_HANDLE(ArrayHandleType);

  if (!fieldMetadata.IsPointField())
  {
    throw vtkm::cont::ErrorBadType("Box Filter operates on point data.");
  }

  vtkm::cont::DynamicCellSet cellSet =
    inDataSet.GetCellSet(this->GetActiveCellSetIndex());

  using ValueType = typename ArrayHandleType::ValueType;

  vtkm::cont::ArrayHandle<ValueType> outField = vtkm::worklet::BoxBlur::Run(
    vtkm::filter::ApplyPolicy(cellSet, Policy()), inField, this->KernelSize);

  std::string outFieldName = this->GetOutputFieldName();
  if (outFieldName == "")
  {
    outFieldName = fieldMetadata.GetName() + "_blurred";
  }

  return vtkm::filter::internal::CreateResult(
    inDataSet, outField, outFieldName, vtkm::cont::Field::Association::POINTS);
}

} // namespace filter
} // namespace vtkm

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

namespace
{

void CheckBoxFilter(const vtkm::cont::DataSet& dataSet)
{
  std::cout << "Check box filter." << std::endl;

  static const vtkm::Id NUM_POINTS = 18;
  static const vtkm::Float32 expected[NUM_POINTS] = {
    60.1875f, 65.2f,    70.2125f, 60.1875f, 65.2f,    70.2125f,
    90.2667f, 95.2778f, 100.292f, 90.2667f, 95.2778f, 100.292f,
    120.337f, 125.35f,  130.363f, 120.337f, 125.35f,  130.363f
  };

  vtkm::cont::ArrayHandle<vtkm::Float32> outputArray;
  dataSet.GetField("pointvar_average", vtkm::cont::Field::Association::POINTS)
    .GetData()
    .CopyTo(outputArray);

  vtkm::cont::printSummary_ArrayHandle(outputArray, std::cout, true);

  VTKM_TEST_ASSERT(outputArray.GetNumberOfValues() == NUM_POINTS);

  auto portal = outputArray.GetPortalConstControl();
  for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); ++index)
  {
    vtkm::Float32 computed = portal.Get(index);
    VTKM_TEST_ASSERT(
      test_equal(expected[index], computed), "Unexpected value at index ", index);
  }
}

void Test()
{
  vtkm::cont::testing::MakeTestDataSet makeTestDataSet;

  std::cout << "Making test data set." << std::endl;
  vtkm::cont::DataSet dataSet = makeTestDataSet.Make3DUniformDataSet0();

  std::cout << "Running box filter." << std::endl;
  vtkm::filter::BoxFilter boxFilter(3);
  boxFilter.SetActiveCellSetIndex(0);
  boxFilter.SetActiveField("pointvar");
  boxFilter.SetOutputFieldName("pointvar_average");
  vtkm::cont::DataSet results = boxFilter.Execute(dataSet);

  CheckBoxFilter(results);
}

} // anonymous namespace

int UseWorkletPointNeighborhood(int, char* [])
{
  return vtkm::cont::testing::Testing::Run(Test);
}
