////
//// BEGIN-EXAMPLE DefaultDeviceAdapter.cxx
////
// Uncomment one (and only one) of the following to reconfigure the VTK-m
// code to use a particular device. Comment them all to automatically pick a
// device.
#define VTKM_DEVICE_ADAPTER VTKM_DEVICE_ADAPTER_SERIAL
//#define VTKM_DEVICE_ADAPTER VTKM_DEVICE_ADAPTER_CUDA
//#define VTKM_DEVICE_ADAPTER VTKM_DEVICE_ADAPTER_OPENMP
//#define VTKM_DEVICE_ADAPTER VTKM_DEVICE_ADAPTER_TBB

#include <vtkm/cont/DeviceAdapter.h>
////
//// END-EXAMPLE DefaultDeviceAdapter.cxx
////

#include <vtkm/cont/cuda/DeviceAdapterCuda.h>
#include <vtkm/cont/tbb/DeviceAdapterTBB.h>

#include <vtkm/exec/FunctorBase.h>

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

namespace DeviceAdapterTagExamples {

////
//// BEGIN-EXAMPLE DefaultDeviceTemplateArg.cxx
////
template<typename Device = VTKM_DEFAULT_DEVICE_ADAPTER_TAG>
struct SetPortalFunctor : vtkm::exec::FunctorBase
{
  VTKM_IS_DEVICE_ADAPTER_TAG(Device);

  using ExecPortalType =
    typename vtkm::cont::ArrayHandle<vtkm::Id>::ExecutionTypes<Device>::Portal;
  ExecPortalType Portal;

  VTKM_CONT
  SetPortalFunctor(vtkm::cont::ArrayHandle<vtkm::Id> array, vtkm::Id size)
    : Portal(array.PrepareForOutput(size, Device()))
  {  }

  //// PAUSE-EXAMPLE
  VTKM_CONT
  SetPortalFunctor(const ExecPortalType &portal) : Portal(portal)
  {  }
  //// RESUME-EXAMPLE
  VTKM_EXEC
  void operator()(vtkm::Id index) const
  {
    //// PAUSE-EXAMPLE
    VTKM_ASSERT(index >= 0);
    VTKM_ASSERT(index < this->Portal.GetNumberOfValues());
    //// RESUME-EXAMPLE
    using ValueType = typename ExecPortalType::ValueType;
    this->Portal.Set(index, TestValue(index, ValueType()));
  }
};
////
//// END-EXAMPLE DefaultDeviceTemplateArg.cxx
////

template<typename ExecPortalType,
         typename ArrayHandleType,
         typename Device>
VTKM_CONT
void TryUsingExecPortal(const ExecPortalType &execPortal,
                        const ArrayHandleType &arrayHandle,
                        Device)
{
  using ValueType = typename ArrayHandleType::ValueType;

  SetPortalFunctor<Device> functor(execPortal);

  vtkm::cont::DeviceAdapterAlgorithm<Device>::Schedule(
        functor, arrayHandle.GetNumberOfValues());

  typename ArrayHandleType::PortalConstControl contPortal =
      arrayHandle.GetPortalConstControl();
  for (vtkm::Id index = 0; index < arrayHandle.GetNumberOfValues(); index++)
  {
    VTKM_TEST_ASSERT(contPortal.Get(index) == TestValue(index, ValueType()),
                     "Bad value set.");
  }
}

void UseTBBDeviceAdapter()
{
  vtkm::cont::ArrayHandle<vtkm::Id> arrayHandle;

  vtkm::cont::ArrayHandle<vtkm::Id>::
      ExecutionTypes<vtkm::cont::DeviceAdapterTagTBB>::Portal portal =
  ////
  //// BEGIN-EXAMPLE SpecifyDeviceAdapter.cxx
  ////
  arrayHandle.PrepareForOutput(50, vtkm::cont::DeviceAdapterTagTBB());
  ////
  //// END-EXAMPLE SpecifyDeviceAdapter.cxx
  ////

  TryUsingExecPortal(portal, arrayHandle, vtkm::cont::DeviceAdapterTagTBB());
}

void UseDefaultDeviceAdapter()
{
  vtkm::cont::ArrayHandle<vtkm::Id> arrayHandle;

  SetPortalFunctor<> functor(arrayHandle, 50);

  TryUsingExecPortal(functor.Portal,
                     arrayHandle,
                     VTKM_DEFAULT_DEVICE_ADAPTER_TAG());
}

////
//// BEGIN-EXAMPLE DeviceAdapterTraits.cxx
////
template <typename ArrayHandleType, typename DeviceAdapterTag>
void CheckArrayHandleDevice(const ArrayHandleType& array, DeviceAdapterTag)
{
  VTKM_IS_ARRAY_HANDLE(ArrayHandleType);
  VTKM_IS_DEVICE_ADAPTER_TAG(DeviceAdapterTag);

  vtkm::cont::DeviceAdapterId currentDevice = array.GetDeviceAdapterId();
  if (currentDevice == vtkm::cont::DeviceAdapterTraits<DeviceAdapterTag>::GetId())
  {
    std::cout << "Array is already on device "
              << vtkm::cont::DeviceAdapterTraits<DeviceAdapterTag>::GetName()
              << std::endl;
  }
  else
  {
    std::cout << "Copying array to device "
              << vtkm::cont::DeviceAdapterTraits<DeviceAdapterTag>::GetName()
              << std::endl;
    array.PrepareForInput(DeviceAdapterTag());
  }
}
////
//// END-EXAMPLE DeviceAdapterTraits.cxx
////

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

template <typename ArrayHandleType, typename DeviceAdapterTag>
void SafeCheckArrayHandleDeviceImpl(
    const ArrayHandleType& array, DeviceAdapterTag, std::true_type)
{
  CheckArrayHandleDevice(array, DeviceAdapterTag());
}

template <typename ArrayHandleType, typename DeviceAdapterTag>
void SafeCheckArrayHandleDeviceImpl(
    const ArrayHandleType&, DeviceAdapterTag, std::false_type)
{
  std::cout << "Device "
            << vtkm::cont::DeviceAdapterTraits<DeviceAdapterTag>::GetName()
            << " is not available" << std::endl;
}

} // namespace detail

template <typename ArrayHandleType, typename DeviceAdapterTag>
void SafeCheckArrayHandleDevice(const ArrayHandleType& array, DeviceAdapterTag)
{
  static const bool deviceValid =
      vtkm::cont::DeviceAdapterTraits<DeviceAdapterTag>::Valid;
  detail::SafeCheckArrayHandleDeviceImpl(
        array, DeviceAdapterTag(), std::integral_constant<bool, deviceValid>());
}
////
//// END-EXAMPLE DeviceAdapterValid.cxx
////

void TryCheckArrayHandleDevice()
{
  vtkm::cont::ArrayHandle<vtkm::Float32> array;
  array.Allocate(10);
  SetPortal(array.GetPortalControl());
  CheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagSerial());
  CheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagSerial());
  CheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagTBB());
  CheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagTBB());
  SafeCheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagSerial());
  SafeCheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagSerial());
  SafeCheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagTBB());
  SafeCheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagTBB());
  SafeCheckArrayHandleDevice(array, vtkm::cont::DeviceAdapterTagCuda());
}

void Test()
{
  UseTBBDeviceAdapter();
  UseDefaultDeviceAdapter();
  TryCheckArrayHandleDevice();
}

} // namespace DeviceAdapterTagExamples

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