#include <vtkm/cont/ArrayHandleCast.h>
#include <vtkm/cont/ArrayHandleVirtual.h>

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

#include <vector>

static constexpr vtkm::Id ARRAY_SIZE = 10;

namespace
{

////
//// BEGIN-EXAMPLE ArrayHandleTemplate.cxx
////
// NOTE: There are faster ways to sum large arrays in VTK-m.
template<typename ArrayHandleType>
VTKM_CONT vtkm::Float64 SumArrayHandle(const ArrayHandleType& arrayHandle)
{
  VTKM_IS_ARRAY_HANDLE(ArrayHandleType);

  typename ArrayHandleType::PortalConstControl portal =
    arrayHandle.GetPortalConstControl();
  vtkm::Float64 sum = 0.0;
  for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); ++index)
  {
    sum += portal.Get(index);
  }

  return sum;
}
////
//// END-EXAMPLE ArrayHandleTemplate.cxx
////

void TestArrayHandleArgument()
{
  std::cout << "Testing template array handle argument" << std::endl;
  vtkm::cont::ArrayHandle<vtkm::Float64> array;
  array.Allocate(ARRAY_SIZE);
  SetPortal(array.GetPortalControl());
  vtkm::Float64 sum = SumArrayHandle(array);
  std::cout << "  Sum result: " << sum << std::endl;
}

void TestBadArrayHandleCast()
{
  std::cout << "Code for casts that work and don't work." << std::endl;

  ////
  //// BEGIN-EXAMPLE ArrayHandleCastError.cxx
  ////
  std::vector<vtkm::cont::ArrayHandle<vtkm::Float64>> vectorOfArrays;

  // Make basic array.
  vtkm::cont::ArrayHandle<vtkm::Float64> basicArray;
  // Fill basicArray...
  //// PAUSE-EXAMPLE
  basicArray.Allocate(ARRAY_SIZE);
  SetPortal(basicArray.GetPortalControl());
  std::cout << "  Pass compatable arrays" << std::endl;
  //// RESUME-EXAMPLE
  vectorOfArrays.push_back(basicArray); // Works fine
  // The previous line works fine because you are passing a standard ArrayHandle
  // to a method that expects a standard ArrayHandle of the same type.

  // Make fancy array.
  vtkm::cont::ArrayHandleCounting<vtkm::Float64> fancyArray(-1.0, 0.1, ARRAY_SIZE);
  //// PAUSE-EXAMPLE
  std::cout << "  Skipping passing an incompatible array" << std::endl;
#if 0
  //// RESUME-EXAMPLE
  vectorOfArrays.push_back(fancyArray); // ERROR!!!!
  // The previous line fails to compile because it is passing an ArrayHandleCounting
  // to a method that expects a standard ArrayHandle, and you cannot directly make
  // this cast.
  //// PAUSE-EXAMPLE
#endif
  //// RESUME-EXAMPLE
  ////
  //// END-EXAMPLE ArrayHandleCastError.cxx
  ////
}

////
//// BEGIN-EXAMPLE UsingArrayHandleVirtual.cxx
////
VTKM_CONT std::vector<vtkm::Float64> SumSeveralArrayHandles(
  const std::vector<vtkm::cont::ArrayHandleVirtual<vtkm::Float64>>& vectorOfArrays)
{
  std::vector<vtkm::Float64> sums;
  for (auto&& arrayHandle : vectorOfArrays)
  {
    sums.push_back(SumArrayHandle(arrayHandle));
  }

  return sums;
}

VTKM_CONT void DoStuff()
{
  std::vector<vtkm::cont::ArrayHandleVirtual<vtkm::Float64>> vectorOfArrays;

  // Make basic array.
  vtkm::cont::ArrayHandle<vtkm::Float64> basicArray;
  // Fill basicArray...
  //// PAUSE-EXAMPLE
  basicArray.Allocate(ARRAY_SIZE);
  SetPortal(basicArray.GetPortalControl());
  //// RESUME-EXAMPLE
  vectorOfArrays.push_back(basicArray);

  // Make fancy array.
  vtkm::cont::ArrayHandleCounting<vtkm::Float64> fancyArray(-1.0, 0.1, ARRAY_SIZE);
  vectorOfArrays.push_back(fancyArray);

  std::vector<vtkm::Float64> sums = SumSeveralArrayHandles(vectorOfArrays);
  //// PAUSE-EXAMPLE
  std::cout << "  Sum 0: " << sums[0] << std::endl;
  std::cout << "  Sum 1: " << sums[1] << std::endl;
  //// RESUME-EXAMPLE
}
////
//// END-EXAMPLE UsingArrayHandleVirtual.cxx
////

void TestArrayHandleVirtualSums()
{
  std::cout << "Testing array handle virtual sums" << std::endl;
  DoStuff();
}

////
//// BEGIN-EXAMPLE ArrayHandleVirtualCast.cxx
////
VTKM_CONT vtkm::Float64 SumArrayHandleVirtual(
  const vtkm::cont::ArrayHandleVirtual<vtkm::Float64>& virtualArray)
{
  if (virtualArray.IsType<vtkm::cont::ArrayHandle<vtkm::Float64>>())
  {
    // Fast path for basic array storage (direct access to memory)
    vtkm::cont::ArrayHandle<vtkm::Float64> basicArray =
      virtualArray.Cast<vtkm::cont::ArrayHandle<vtkm::Float64>>();
    return SumArrayHandle(basicArray);
  }
  else
  {
    // Slower path to go through general virtual interface
    return SumArrayHandle(virtualArray);
  }
}
////
//// END-EXAMPLE ArrayHandleVirtualCast.cxx
////

void TestArrayHandleVirtualCast()
{
  std::cout << "Testing cast operations on ArrayHandleVirtual" << std::endl;

  vtkm::cont::ArrayHandle<vtkm::Float64> array;
  array.Allocate(ARRAY_SIZE);
  SetPortal(array.GetPortalControl());
  vtkm::Float64 sum = SumArrayHandleVirtual(array);
  std::cout << "  Sum result: " << sum << std::endl;

  sum = SumArrayHandleVirtual(
    vtkm::cont::make_ArrayHandleConstant<vtkm::Float64>(2.0, ARRAY_SIZE));
  std::cout << "  Sum result: " << sum << std::endl;
  VTKM_TEST_ASSERT(test_equal(2.0 * ARRAY_SIZE, sum));
}

static void Test()
{
  TestArrayHandleArgument();
  TestBadArrayHandleCast();
  TestArrayHandleVirtualSums();
  TestArrayHandleVirtualCast();
}

} // anonymous namespace

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