#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ArrayHandleIndex.h>
#include <vtkm/cont/DeviceAdapterAlgorithm.h>
#include <vtkm/cont/TryExecute.h>

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

namespace TryExecuteExample
{

////
//// BEGIN-EXAMPLE ArrayAverageImpl.cxx
////
template<typename T, typename Storage, typename Device>
VTKM_CONT T ArrayAverage(const vtkm::cont::ArrayHandle<T, Storage>& array, Device)
{
  T sum = vtkm::cont::DeviceAdapterAlgorithm<Device>::Reduce(array, T(0));
  return sum / T(array.GetNumberOfValues());
}
////
//// END-EXAMPLE ArrayAverageImpl.cxx
////

////
//// BEGIN-EXAMPLE ArrayAverageTryExecute.cxx
////
namespace detail
{

template<typename T, typename Storage>
struct ArrayAverageFunctor
{
  using InArrayType = vtkm::cont::ArrayHandle<T, Storage>;

  InArrayType InArray;
  T OutValue;

  VTKM_CONT
  ArrayAverageFunctor(const InArrayType& array)
    : InArray(array)
  {
  }

  template<typename Device>
  VTKM_CONT bool operator()(Device)
  {
    // Call the version of ArrayAverage that takes a DeviceAdapter.
    this->OutValue = ArrayAverage(this->InArray, Device());

    return true;
  }
};

} // namespace detail

template<typename T, typename Storage>
VTKM_CONT T ArrayAverage(const vtkm::cont::ArrayHandle<T, Storage>& array,
                         vtkm::cont::RuntimeDeviceTracker tracker =
                           vtkm::cont::GetGlobalRuntimeDeviceTracker())
{
  detail::ArrayAverageFunctor<T, Storage> functor(array);

  bool foundAverage = vtkm::cont::TryExecute(functor, tracker);

  if (!foundAverage)
  {
    throw vtkm::cont::ErrorExecution("Could not compute array average.");
  }

  return functor.OutValue;
}
////
//// END-EXAMPLE ArrayAverageTryExecute.cxx
////

void Run()
{
  static const vtkm::Id ARRAY_SIZE = 11;

  std::cout << "Running average on " << ARRAY_SIZE << " indices" << std::endl;
  vtkm::Id average = ArrayAverage(vtkm::cont::ArrayHandleIndex(ARRAY_SIZE));
  VTKM_TEST_ASSERT(average == (ARRAY_SIZE - 1) / 2, "Bad average");
}

} // namespace TryExecuteExample

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