#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ArrayHandleCounting.h>
#include <vtkm/cont/ArrayHandleIndex.h>
#include <vtkm/cont/DeviceAdapter.h>
#include <vtkm/cont/VariantArrayHandle.h>
#include <vtkm/cont/internal/StorageError.h>

#include <vtkm/VecTraits.h>

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

namespace
{

constexpr vtkm::Id ARRAY_SIZE = 10;

////
//// BEGIN-EXAMPLE CreateVariantArrayHandle.cxx
////
VTKM_CONT
vtkm::cont::VariantArrayHandle LoadVariantArray(const void* buffer,
                                                vtkm::Id length,
                                                std::string type)
{
  vtkm::cont::VariantArrayHandle handle;
  if (type == "float")
  {
    vtkm::cont::ArrayHandle<vtkm::Float32> concreteArray =
      vtkm::cont::make_ArrayHandle(
        reinterpret_cast<const vtkm::Float32*>(buffer), length, vtkm::CopyFlag::On);
    handle = concreteArray;
  }
  else if (type == "int")
  {
    vtkm::cont::ArrayHandle<vtkm::Int32> concreteArray =
      vtkm::cont::make_ArrayHandle(
        reinterpret_cast<const vtkm::Int32*>(buffer), length, vtkm::CopyFlag::On);
    handle = concreteArray;
  }
  return handle;
}
////
//// END-EXAMPLE CreateVariantArrayHandle.cxx
////

void TryLoadVariantArray()
{
  vtkm::Float32 scalarBuffer[ARRAY_SIZE];
  vtkm::cont::VariantArrayHandle handle =
    LoadVariantArray(scalarBuffer, ARRAY_SIZE, "float");
  VTKM_TEST_ASSERT((handle.IsValueType<vtkm::Float32>()), "Type not right.");
  VTKM_TEST_ASSERT(!(handle.IsValueType<vtkm::Int32>()), "Type not right.");

  vtkm::Int32 idBuffer[ARRAY_SIZE];
  handle = LoadVariantArray(idBuffer, ARRAY_SIZE, "int");
  VTKM_TEST_ASSERT((handle.IsValueType<vtkm::Int32>()), "Type not right.");
  VTKM_TEST_ASSERT(!(handle.IsValueType<vtkm::Float32>()), "Type not right.");
}

void NonTypeQueriesVariantArrayHandle()
{
  ////
  //// BEGIN-EXAMPLE NonTypeQueriesVariantArrayHandle.cxx
  ////
  std::vector<vtkm::Float32> scalarBuffer(10);
  vtkm::cont::VariantArrayHandle scalarVariantHandle(
    vtkm::cont::make_ArrayHandleMove(std::move(scalarBuffer)));

  // This returns 10.
  vtkm::Id scalarArraySize = scalarVariantHandle.GetNumberOfValues();

  // This returns 1.
  vtkm::IdComponent scalarComponents = scalarVariantHandle.GetNumberOfComponents();
  //// PAUSE-EXAMPLE
  VTKM_TEST_ASSERT(scalarArraySize == 10, "Got wrong array size.");
  VTKM_TEST_ASSERT(scalarComponents == 1, "Got wrong vec size.");
  //// RESUME-EXAMPLE

  std::vector<vtkm::Vec3f_32> vectorBuffer(20);
  vtkm::cont::VariantArrayHandle vectorVariantHandle(
    vtkm::cont::make_ArrayHandleMove(std::move(vectorBuffer)));

  // This returns 20.
  vtkm::Id vectorArraySize = vectorVariantHandle.GetNumberOfValues();

  // This returns 3.
  vtkm::IdComponent vectorComponents = vectorVariantHandle.GetNumberOfComponents();
  //// PAUSE-EXAMPLE
  VTKM_TEST_ASSERT(vectorArraySize == 20, "Got wrong array size.");
  VTKM_TEST_ASSERT(vectorComponents == 3, "Got wrong vec size.");
  //// RESUME-EXAMPLE
  ////
  //// END-EXAMPLE NonTypeQueriesVariantArrayHandle.cxx
  ////
}

void VariantArrayHandleNewInstance()
{
  ////
  //// BEGIN-EXAMPLE VariantArrayHandleNewInstance.cxx
  ////
  std::vector<vtkm::Float32> scalarBuffer(10);
  vtkm::cont::VariantArrayHandle variantHandle(
    vtkm::cont::make_ArrayHandleMove(std::move(scalarBuffer)));

  // This creates a new empty array of type Float32.
  vtkm::cont::VariantArrayHandle newVariantArray = variantHandle.NewInstance();
  ////
  //// END-EXAMPLE VariantArrayHandleNewInstance.cxx
  ////

  VTKM_TEST_ASSERT(newVariantArray.GetNumberOfValues() == 0, "New array not empty.");
  VTKM_TEST_ASSERT(
    (newVariantArray.IsType<vtkm::cont::ArrayHandle<vtkm::Float32>>()),
    "New array is wrong type.");
}

void MyFunction(const vtkm::cont::ArrayHandle<vtkm::Float32>& array)
{
  VTKM_TEST_ASSERT(array.GetNumberOfValues() == ARRAY_SIZE);
  CheckPortal(array.ReadPortal());
}

void QueryCastVariantArrayHandle()
{
  ////
  //// BEGIN-EXAMPLE QueryVariantArrayHandle.cxx
  ////
  vtkm::cont::ArrayHandle<vtkm::Float32> inputArray;
  // Fill inputArray...
  //// PAUSE-EXAMPLE
  inputArray.Allocate(ARRAY_SIZE);
  SetPortal(inputArray.WritePortal());
  //// RESUME-EXAMPLE
  vtkm::cont::VariantArrayHandle variantHandle(inputArray);

  // This returns true
  bool isFloat32Array = variantHandle.IsType<decltype(inputArray)>();

  // This returns false
  bool isIdArray = variantHandle.IsType<vtkm::cont::ArrayHandle<vtkm::Id>>();
  ////
  //// END-EXAMPLE QueryVariantArrayHandle.cxx
  ////

  VTKM_TEST_ASSERT(isFloat32Array, "Didn't query right.");
  VTKM_TEST_ASSERT(!isIdArray, "Didn't query right.");

  ////
  //// BEGIN-EXAMPLE CastVariantArrayHandle.cxx
  ////
  vtkm::cont::ArrayHandle<vtkm::Float32> concreteArray;
  variantHandle.AsArrayHandle(concreteArray);
  ////
  //// END-EXAMPLE CastVariantArrayHandle.cxx
  ////
  VTKM_TEST_ASSERT(concreteArray.GetNumberOfValues() == ARRAY_SIZE,
                   "Unexpected length");
  CheckPortal(concreteArray.ReadPortal());

  ////
  //// BEGIN-EXAMPLE CastVariantArrayHandleDirect.cxx
  ////
  MyFunction(variantHandle.AsArrayHandle<vtkm::cont::ArrayHandle<vtkm::Float32>>());
  ////
  //// END-EXAMPLE CastVariantArrayHandleDirect.cxx
  ////

  ////
  //// BEGIN-EXAMPLE VariantArrayHandleCastArray.cxx
  ////
  vtkm::cont::ArrayHandleCast<vtkm::Float64, vtkm::cont::ArrayHandle<vtkm::Float32>>
    castArray;
  variantHandle.AsArrayHandle(castArray);
  ////
  //// END-EXAMPLE VariantArrayHandleCastArray.cxx
  ////

  ////
  //// BEGIN-EXAMPLE AsMultiplexerVariantArrayHandle.cxx
  ////
  vtkm::cont::ArrayHandleMultiplexer<
    vtkm::cont::ArrayHandle<vtkm::Float32>,
    vtkm::cont::ArrayHandleCast<vtkm::Float32,
                                vtkm::cont::ArrayHandle<vtkm::Float64>>>
    multiplexerArray;
  variantHandle.AsArrayHandle(multiplexerArray);
  ////
  //// END-EXAMPLE AsMultiplexerVariantArrayHandle.cxx
  ////
  VTKM_TEST_ASSERT(multiplexerArray.GetNumberOfValues() == ARRAY_SIZE);
  CheckPortal(multiplexerArray.ReadPortal());
}

////
//// BEGIN-EXAMPLE UsingCastAndCall.cxx
////
struct PrintArrayContentsFunctor
{
  template<typename T, typename S>
  VTKM_CONT void operator()(const vtkm::cont::ArrayHandle<T, S>& array) const
  {
    this->PrintArrayPortal(array.ReadPortal());
  }

private:
  template<typename PortalType>
  VTKM_CONT void PrintArrayPortal(const PortalType& portal) const
  {
    for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); index++)
    {
      // All ArrayPortal objects have ValueType for the type of each value.
      using ValueType = typename PortalType::ValueType;

      ValueType value = portal.Get(index);

      vtkm::IdComponent numComponents =
        vtkm::VecTraits<ValueType>::GetNumberOfComponents(value);
      for (vtkm::IdComponent componentIndex = 0; componentIndex < numComponents;
           componentIndex++)
      {
        std::cout << " "
                  << vtkm::VecTraits<ValueType>::GetComponent(value, componentIndex);
      }
      std::cout << std::endl;
    }
  }
};

template<typename VariantArrayType>
void PrintArrayContents(const VariantArrayType& array)
{
  array.CastAndCall(PrintArrayContentsFunctor());
}
////
//// END-EXAMPLE UsingCastAndCall.cxx
////

namespace second_def
{

////
//// BEGIN-EXAMPLE VariantArrayHandleBase.cxx
////
template<typename TypeList>
void PrintArrayContents(const vtkm::cont::VariantArrayHandleBase<TypeList>& array)
{
  array.CastAndCall(PrintArrayContentsFunctor());
}
////
//// END-EXAMPLE VariantArrayHandleBase.cxx
////

} // namespace second_def

void TryPrintArrayContents()
{
  vtkm::cont::ArrayHandleIndex implicitArray(10);

  vtkm::cont::ArrayHandle<vtkm::Id> concreteArray;
  vtkm::cont::Algorithm::Copy(implicitArray, concreteArray);

  vtkm::cont::VariantArrayHandle dynamicArray = concreteArray;

  second_def::PrintArrayContents(dynamicArray);

  ////
  //// BEGIN-EXAMPLE CastAndCallAllTypes.cxx
  ////
  PrintArrayContents(dynamicArray.ResetTypes(vtkm::TypeListAll()));
  ////
  //// END-EXAMPLE CastAndCallAllTypes.cxx
  ////

  ////
  //// BEGIN-EXAMPLE CastAndCallSingleType.cxx
  ////
  PrintArrayContents(dynamicArray.ResetTypes(vtkm::TypeListId()));
  ////
  //// END-EXAMPLE CastAndCallSingleType.cxx
  ////
}

void Test()
{
  TryLoadVariantArray();
  NonTypeQueriesVariantArrayHandle();
  VariantArrayHandleNewInstance();
  QueryCastVariantArrayHandle();
  TryPrintArrayContents();
}

} // anonymous namespace

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