#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
{

////
//// 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);
    handle = concreteArray;
  }
  else if (type == "int")
  {
    vtkm::cont::ArrayHandle<vtkm::Int32> concreteArray =
      vtkm::cont::make_ArrayHandle(reinterpret_cast<const vtkm::Int32*>(buffer),
                                   length);
    handle = concreteArray;
  }
  return handle;
}
////
//// END-EXAMPLE CreateVariantArrayHandle.cxx
////

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

  vtkm::Int32 idBuffer[10];
  handle = LoadVariantArray(idBuffer, 10, "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 scalarDynamicHandle(
    vtkm::cont::make_ArrayHandle(scalarBuffer));

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

  // This returns 1.
  vtkm::IdComponent scalarComponents = scalarDynamicHandle.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 vectorDynamicHandle(
    vtkm::cont::make_ArrayHandle(vectorBuffer));

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

  // This returns 3.
  vtkm::IdComponent vectorComponents = vectorDynamicHandle.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_ArrayHandle(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 QueryCastVariantArrayHandle()
{
  ////
  //// BEGIN-EXAMPLE QueryVariantArrayHandle.cxx
  ////
  std::vector<vtkm::Float32> scalarBuffer(10);
  vtkm::cont::ArrayHandle<vtkm::Float32> concreteHandle =
    vtkm::cont::make_ArrayHandle(scalarBuffer);
  vtkm::cont::VariantArrayHandle variantHandle(concreteHandle);

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

  // 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 AsVirtualVariantArrayHandle.cxx
  ////
  vtkm::cont::ArrayHandleVirtual<vtkm::Float32> virtualArray =
    variantHandle.AsVirtual<vtkm::Float32>();
  ////
  //// END-EXAMPLE AsVirtualVariantArrayHandle.cxx
  ////


  ////
  //// BEGIN-EXAMPLE CastVariantArrayHandle.cxx
  ////
  variantHandle.CopyTo(concreteHandle);
  ////
  //// END-EXAMPLE CastVariantArrayHandle.cxx
  ////

  VTKM_TEST_ASSERT(concreteHandle.GetNumberOfValues() == 10, "Unexpected length");
}

////
//// 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);
}
