#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/ArrayCopy.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ArrayPortalToIterators.h>
#include <vtkm/cont/ArrayRangeCompute.h>
#include <vtkm/cont/DeviceAdapter.h>
#include <vtkm/cont/StorageBasic.h>

#include <vtkm/exec/FunctorBase.h>

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

#include <algorithm>
#include <vector>

namespace
{

template<typename T>
vtkm::Float32 TestValue(T index)
{
  return static_cast<vtkm::Float32>(1 + 0.001 * index);
}

void CheckArrayValues(const vtkm::cont::ArrayHandle<vtkm::Float32>& array,
                      vtkm::Float32 factor = 1)
{
  // So far all the examples are using 50 entries. Could change.
  VTKM_TEST_ASSERT(array.GetNumberOfValues() == 50, "Wrong number of values");

  for (vtkm::Id index = 0; index < array.GetNumberOfValues(); index++)
  {
    VTKM_TEST_ASSERT(
      test_equal(array.ReadPortal().Get(index), TestValue(index) * factor),
      "Bad data value.");
  }
}

////
//// BEGIN-EXAMPLE ArrayHandleParameterTemplate.cxx
////
template<typename T, typename Storage>
void Foo(const vtkm::cont::ArrayHandle<T, Storage>& array)
{
  ////
  //// END-EXAMPLE ArrayHandleParameterTemplate.cxx
  ////
  (void)array;
}

////
//// BEGIN-EXAMPLE ArrayHandleFullTemplate.cxx
////
template<typename ArrayType>
void Bar(const ArrayType& array)
{
  VTKM_IS_ARRAY_HANDLE(ArrayType);
  ////
  //// END-EXAMPLE ArrayHandleFullTemplate.cxx
  ////
  (void)array;
}

void BasicConstruction()
{
  ////
  //// BEGIN-EXAMPLE CreateArrayHandle.cxx
  ////
  vtkm::cont::ArrayHandle<vtkm::Float32> outputArray;
  ////
  //// END-EXAMPLE CreateArrayHandle.cxx
  ////

  ////
  //// BEGIN-EXAMPLE ArrayHandleStorageParameter.cxx
  ////
  vtkm::cont::ArrayHandle<vtkm::Float32, vtkm::cont::StorageTagBasic> arrayHandle;
  ////
  //// END-EXAMPLE ArrayHandleStorageParameter.cxx
  ////

  Foo(outputArray);
  Bar(arrayHandle);
}

void ArrayHandleFromCArray()
{
  ////
  //// BEGIN-EXAMPLE ArrayHandleFromCArray.cxx
  ////
  vtkm::Float32 dataBuffer[50];
  // Populate dataBuffer with meaningful data. Perhaps read data from a file.
  //// PAUSE-EXAMPLE
  for (vtkm::Id index = 0; index < 50; index++)
  {
    dataBuffer[index] = TestValue(index);
  }
  //// RESUME-EXAMPLE

  vtkm::cont::ArrayHandle<vtkm::Float32> inputArray =
    vtkm::cont::make_ArrayHandle(dataBuffer, 50);
  ////
  //// END-EXAMPLE ArrayHandleFromCArray.cxx
  ////

  CheckArrayValues(inputArray);
}

vtkm::Float32 GetValueForArray(vtkm::Id index)
{
  return TestValue(index);
}

void AllocateAndFillArrayHandle()
{
  ////
  //// BEGIN-EXAMPLE ArrayHandlePopulate.cxx
  ////
  ////
  //// BEGIN-EXAMPLE ArrayHandleAllocate.cxx
  ////
  vtkm::cont::ArrayHandle<vtkm::Float32> arrayHandle;

  const vtkm::Id ARRAY_SIZE = 50;
  arrayHandle.Allocate(ARRAY_SIZE);
  ////
  //// END-EXAMPLE ArrayHandleAllocate.cxx
  ////

  using PortalType = vtkm::cont::ArrayHandle<vtkm::Float32>::WritePortalType;
  PortalType portal = arrayHandle.WritePortal();

  for (vtkm::Id index = 0; index < ARRAY_SIZE; index++)
  {
    portal.Set(index, GetValueForArray(index));
  }
  ////
  //// END-EXAMPLE ArrayHandlePopulate.cxx
  ////

  CheckArrayValues(arrayHandle);

  ////
  //// BEGIN-EXAMPLE ArrayRangeCompute.cxx
  ////
  vtkm::cont::ArrayHandle<vtkm::Range> rangeArray =
    vtkm::cont::ArrayRangeCompute(arrayHandle);
  auto rangePortal = rangeArray.ReadPortal();
  for (vtkm::Id index = 0; index < rangePortal.GetNumberOfValues(); ++index)
  {
    vtkm::Range componentRange = rangePortal.Get(index);
    std::cout << "Values for component " << index << " go from "
              << componentRange.Min << " to " << componentRange.Max << std::endl;
  }
  ////
  //// END-EXAMPLE ArrayRangeCompute.cxx
  ////

  vtkm::Range range = rangePortal.Get(0);
  VTKM_TEST_ASSERT(test_equal(range.Min, TestValue(0)), "Bad min value.");
  VTKM_TEST_ASSERT(test_equal(range.Max, TestValue(ARRAY_SIZE - 1)),
                   "Bad max value.");
}

////
//// BEGIN-EXAMPLE ArrayOutOfScope.cxx
////
VTKM_CONT
vtkm::cont::ArrayHandle<vtkm::Float32> BadDataLoad()
{
  ////
  //// BEGIN-EXAMPLE ArrayHandleFromVector.cxx
  ////
  std::vector<vtkm::Float32> dataBuffer;
  // Populate dataBuffer with meaningful data. Perhaps read data from a file.
  //// PAUSE-EXAMPLE
  dataBuffer.resize(50);
  for (std::size_t index = 0; index < 50; index++)
  {
    dataBuffer[index] = TestValue(index);
  }
  //// RESUME-EXAMPLE

  vtkm::cont::ArrayHandle<vtkm::Float32> inputArray =
    vtkm::cont::make_ArrayHandle(dataBuffer);
  ////
  //// END-EXAMPLE ArrayHandleFromVector.cxx
  ////
  //// PAUSE-EXAMPLE
  CheckArrayValues(inputArray);
  //// RESUME-EXAMPLE

  return inputArray;
  // THIS IS WRONG! At this point dataBuffer goes out of scope and deletes its
  // memory. However, inputArray has a pointer to that memory, which becomes an
  // invalid pointer in the returned object. Bad things will happen when the
  // ArrayHandle is used.
}

VTKM_CONT
vtkm::cont::ArrayHandle<vtkm::Float32> SafeDataLoad()
{
  std::vector<vtkm::Float32> dataBuffer;
  // Populate dataBuffer with meaningful data. Perhaps read data from a file.
  //// PAUSE-EXAMPLE
  dataBuffer.resize(50);
  for (std::size_t index = 0; index < 50; index++)
  {
    dataBuffer[index] = TestValue(index);
  }
  //// RESUME-EXAMPLE

  vtkm::cont::ArrayHandle<vtkm::Float32> inputArray =
    //// LABEL CopyFlagOn
    vtkm::cont::make_ArrayHandle(dataBuffer, vtkm::CopyFlag::On);

  return inputArray;
  // This is safe.
}
////
//// END-EXAMPLE ArrayOutOfScope.cxx
////

void ArrayHandleFromVector()
{
  BadDataLoad();
}

void CheckSafeDataLoad()
{
  vtkm::cont::ArrayHandle<vtkm::Float32> inputArray = SafeDataLoad();
  CheckArrayValues(inputArray);
}

////
//// BEGIN-EXAMPLE SimpleArrayPortal.cxx
////
template<typename T>
class SimpleScalarArrayPortal
{
public:
  using ValueType = T;

  // There is no specification for creating array portals, but they generally
  // need a constructor like this to be practical.
  VTKM_EXEC_CONT
  SimpleScalarArrayPortal(ValueType* array, vtkm::Id numberOfValues)
    : Array(array)
    , NumberOfValues(numberOfValues)
  {
  }

  VTKM_EXEC_CONT
  SimpleScalarArrayPortal()
    : Array(NULL)
    , NumberOfValues(0)
  {
  }

  VTKM_EXEC_CONT
  vtkm::Id GetNumberOfValues() const { return this->NumberOfValues; }

  VTKM_EXEC_CONT
  ValueType Get(vtkm::Id index) const { return this->Array[index]; }

  VTKM_EXEC_CONT
  void Set(vtkm::Id index, ValueType value) const { this->Array[index] = value; }

private:
  ValueType* Array;
  vtkm::Id NumberOfValues;
};
////
//// END-EXAMPLE SimpleArrayPortal.cxx
////

////
//// BEGIN-EXAMPLE ArrayPortalToIterators.cxx
////
template<typename PortalType>
VTKM_CONT std::vector<typename PortalType::ValueType> CopyArrayPortalToVector(
  const PortalType& portal)
{
  using ValueType = typename PortalType::ValueType;
  std::vector<ValueType> result(
    static_cast<std::size_t>(portal.GetNumberOfValues()));

  vtkm::cont::ArrayPortalToIterators<PortalType> iterators(portal);

  std::copy(iterators.GetBegin(), iterators.GetEnd(), result.begin());

  return result;
}
////
//// END-EXAMPLE ArrayPortalToIterators.cxx
////

void TestArrayPortalVectors()
{
  vtkm::cont::ArrayHandle<vtkm::Float32> inputArray = SafeDataLoad();
  std::vector<vtkm::Float32> buffer =
    CopyArrayPortalToVector(inputArray.ReadPortal());

  VTKM_TEST_ASSERT(static_cast<vtkm::Id>(buffer.size()) ==
                     inputArray.GetNumberOfValues(),
                   "Vector was sized wrong.");

  for (vtkm::Id index = 0; index < inputArray.GetNumberOfValues(); index++)
  {
    VTKM_TEST_ASSERT(
      test_equal(buffer[static_cast<std::size_t>(index)], TestValue(index)),
      "Bad data value.");
  }

  SimpleScalarArrayPortal<vtkm::Float32> portal(
    &buffer.at(0), static_cast<vtkm::Id>(buffer.size()));

  ////
  //// BEGIN-EXAMPLE ArrayPortalToIteratorBeginEnd.cxx
  ////
  std::vector<vtkm::Float32> myContainer(
    static_cast<std::size_t>(portal.GetNumberOfValues()));

  std::copy(vtkm::cont::ArrayPortalToIteratorBegin(portal),
            vtkm::cont::ArrayPortalToIteratorEnd(portal),
            myContainer.begin());
  ////
  //// END-EXAMPLE ArrayPortalToIteratorBeginEnd.cxx
  ////

  for (vtkm::Id index = 0; index < inputArray.GetNumberOfValues(); index++)
  {
    VTKM_TEST_ASSERT(
      test_equal(myContainer[static_cast<std::size_t>(index)], TestValue(index)),
      "Bad data value.");
  }
}

////
//// BEGIN-EXAMPLE ControlPortals.cxx
////
template<typename T, typename Storage>
void SortCheckArrayHandle(vtkm::cont::ArrayHandle<T, Storage> arrayHandle)
{
  using PortalType = typename vtkm::cont::ArrayHandle<T, Storage>::WritePortalType;
  using PortalConstType =
    typename vtkm::cont::ArrayHandle<T, Storage>::ReadPortalType;

  PortalType readwritePortal = arrayHandle.WritePortal();
  // This is actually pretty dumb. Sorting would be generally faster in
  // parallel in the execution environment using the device adapter algorithms.
  std::sort(vtkm::cont::ArrayPortalToIteratorBegin(readwritePortal),
            vtkm::cont::ArrayPortalToIteratorEnd(readwritePortal));

  PortalConstType readPortal = arrayHandle.ReadPortal();
  for (vtkm::Id index = 1; index < readPortal.GetNumberOfValues(); index++)
  {
    if (readPortal.Get(index - 1) > readPortal.Get(index))
    {
      //// PAUSE-EXAMPLE
      VTKM_TEST_FAIL("Sorting is wrong!");
      //// RESUME-EXAMPLE
      std::cout << "Sorting is wrong!" << std::endl;
      break;
    }
  }
}
////
//// END-EXAMPLE ControlPortals.cxx
////

void TestControlPortalsExample()
{
  SortCheckArrayHandle(SafeDataLoad());
}

////
//// BEGIN-EXAMPLE ExecutionPortals.cxx
////
template<typename InputPortalType, typename OutputPortalType>
struct DoubleFunctor : public vtkm::exec::FunctorBase
{
  InputPortalType InputPortal;
  OutputPortalType OutputPortal;

  VTKM_CONT
  DoubleFunctor(InputPortalType inputPortal, OutputPortalType outputPortal)
    : InputPortal(inputPortal)
    , OutputPortal(outputPortal)
  {
  }

  VTKM_EXEC
  void operator()(vtkm::Id index) const
  {
    this->OutputPortal.Set(index, 2 * this->InputPortal.Get(index));
  }
};

template<typename T, typename Device>
void DoubleArray(vtkm::cont::ArrayHandle<T> inputArray,
                 vtkm::cont::ArrayHandle<T> outputArray,
                 Device)
{
  vtkm::Id numValues = inputArray.GetNumberOfValues();

  vtkm::cont::Token token;
  auto inputPortal = inputArray.PrepareForInput(Device{}, token);
  auto outputPortal = outputArray.PrepareForOutput(numValues, Device{}, token);
  // Token is now attached to inputPortal and outputPortal. Those two portals
  // are guaranteed to be valid until token goes out of scope at the end of
  // this function.

  DoubleFunctor<decltype(inputPortal), decltype(outputPortal)> functor(inputPortal,
                                                                       outputPortal);

  vtkm::cont::DeviceAdapterAlgorithm<Device>::Schedule(functor, numValues);
}
////
//// END-EXAMPLE ExecutionPortals.cxx
////

void TestExecutionPortalsExample()
{
  vtkm::cont::ArrayHandle<vtkm::Float32> inputArray = SafeDataLoad();
  CheckArrayValues(inputArray);
  vtkm::cont::ArrayHandle<vtkm::Float32> outputArray;
  DoubleArray(inputArray, outputArray, vtkm::cont::DeviceAdapterTagSerial());
  CheckArrayValues(outputArray, 2);
}

void Test()
{
  BasicConstruction();
  ArrayHandleFromCArray();
  ArrayHandleFromVector();
  AllocateAndFillArrayHandle();
  CheckSafeDataLoad();
  TestArrayPortalVectors();
  TestControlPortalsExample();
  TestExecutionPortalsExample();
}

} // anonymous namespace

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