#include <vtkm/internal/FunctionInterface.h>

#include <vtkm/StaticAssert.h>

#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/DeviceAdapter.h>
#include <vtkm/cont/ErrorBadType.h>
#include <vtkm/cont/VariantArrayHandle.h>

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

#include <string.h>

namespace
{

void BasicFunctionInterface()
{
  ////
  //// BEGIN-EXAMPLE DefineFunctionInterface.cxx
  ////
  // FunctionInterfaces matching some common POSIX functions.
  vtkm::internal::FunctionInterface<size_t(const char*)> strlenInterface;

  vtkm::internal::FunctionInterface<char*(char*, const char* s2, size_t)>
    strncpyInterface;
  ////
  //// END-EXAMPLE DefineFunctionInterface.cxx
  ////

  ////
  //// BEGIN-EXAMPLE UseMakeFunctionInterface.cxx
  ////
  const char* s = "Hello World";
  static const size_t BUFFER_SIZE = 100;
  char* buffer = (char*)malloc(BUFFER_SIZE);

  strlenInterface = vtkm::internal::make_FunctionInterface<size_t>(s);

  strncpyInterface =
    vtkm::internal::make_FunctionInterface<char*>(buffer, s, BUFFER_SIZE);
  ////
  //// END-EXAMPLE UseMakeFunctionInterface.cxx
  ////

  std::cout << "Trying interfaces." << std::endl;

  ////
  //// BEGIN-EXAMPLE FunctionInterfaceArity.cxx
  ////
  VTKM_STATIC_ASSERT(vtkm::internal::FunctionInterface<size_t(const char*)>::ARITY ==
                     1);

  vtkm::IdComponent arity = strncpyInterface.GetArity(); // arity = 3
  ////
  //// END-EXAMPLE FunctionInterfaceArity.cxx
  ////

  VTKM_TEST_ASSERT(arity == 3, "Unexpected arity.");

  free(buffer);
}

////
//// BEGIN-EXAMPLE FunctionInterfaceGetParameter.cxx
////
template<typename FunctionSignature>
void GetFirstParameter(
  const vtkm::internal::FunctionInterface<FunctionSignature>& interface)
{
  // The following two uses of GetParameter are equivalent
  std::cout << vtkm::internal::ParameterGet<1>(interface) << std::endl;
}
////
//// END-EXAMPLE FunctionInterfaceGetParameter.cxx
////

void TryGetParameter()
{
  std::cout << "Getting parameters." << std::endl;

  GetFirstParameter(
    vtkm::internal::make_FunctionInterface<void>(std::string("foo")));
}

// SetParameter no longer supported
#if 0
////
//// BEGIN-EXAMPLE FunctionInterfaceSetParameter.cxx
////
void SetFirstParameterResolved(
  vtkm::internal::FunctionInterface<void(std::string)>& interface,
  const std::string& newFirstParameter)
{
  // The following two uses of SetParameter are equivalent
  interface.SetParameter<1>(newFirstParameter);
  interface.SetParameter(newFirstParameter, vtkm::internal::IndexTag<1>());
}

template<typename FunctionSignature, typename T>
void SetFirstParameterTemplated(
  vtkm::internal::FunctionInterface<FunctionSignature>& interface,
  T newFirstParameter)
{
  // The following two uses of SetParameter are equivalent
  interface.template SetParameter<1>(newFirstParameter);
  interface.SetParameter(newFirstParameter, vtkm::internal::IndexTag<1>());
}
////
//// END-EXAMPLE FunctionInterfaceSetParameter.cxx
////

void TrySetParameter()
{
  std::cout << "Setting parameters." << std::endl;

  vtkm::internal::FunctionInterface<void(std::string)> functionInterface;

  SetFirstParameterResolved(functionInterface, std::string("foo"));
  VTKM_TEST_ASSERT(functionInterface.GetParameter<1>() == "foo",
                   "Did not set string.");

  SetFirstParameterTemplated(functionInterface, std::string("bar"));
  VTKM_TEST_ASSERT(functionInterface.GetParameter<1>() == "bar",
                   "Did not set string.");
}
#endif

// Invoke no longer supported
#if 0
void BasicInvoke()
{
  ////
  //// BEGIN-EXAMPLE FunctionInterfaceBasicInvoke.cxx
  ////
  vtkm::internal::FunctionInterface<size_t(const char*)> strlenInterface;
  strlenInterface.SetParameter<1>("Hello world");

  strlenInterface.InvokeCont(strlen);

  size_t length = strlenInterface.GetReturnValue(); // length = 11
  ////
  //// END-EXAMPLE FunctionInterfaceBasicInvoke.cxx
  ////

  VTKM_TEST_ASSERT(length == 11, "Bad length.");
}

namespace TransformedInvokeNamespace
{

////
//// BEGIN-EXAMPLE FunctionInterfaceTransformInvoke.cxx
////
// Our transform converts C strings to integers, leaves everything else alone.
struct TransformFunctor
{
  template<typename T>
  VTKM_CONT const T& operator()(const T& x) const
  {
    return x;
  }

  VTKM_CONT
  vtkm::Int32 operator()(const char* x) const { return atoi(x); }
};

// The function we are invoking simply compares two numbers.
struct IsSameFunctor
{
  template<typename T1, typename T2>
  VTKM_CONT bool operator()(const T1& x, const T2& y) const
  {
    return x == y;
  }
};

void TryTransformedInvoke()
{
  vtkm::internal::FunctionInterface<bool(const char*, vtkm::Int32)>
    functionInterface = vtkm::internal::make_FunctionInterface<bool>(
      (const char*)"42", (vtkm::Int32)42);

  functionInterface.InvokeCont(IsSameFunctor(), TransformFunctor());

  bool isSame = functionInterface.GetReturnValue(); // isSame = true
  ////
  //// PAUSE-EXAMPLE
  ////
  VTKM_TEST_ASSERT(isSame, "Did not get right return value.");
  ////
  //// RESUME-EXAMPLE
  ////
}
////
//// END-EXAMPLE FunctionInterfaceTransformInvoke.cxx
////

} // namespace TransformedInvokeNamespace

using namespace TransformedInvokeNamespace;

namespace ReturnContainerNamespace
{

////
//// BEGIN-EXAMPLE FunctionInterfaceReturnContainer.cxx
////
template<typename ResultType, bool Valid>
struct PrintReturnFunctor;

template<typename ResultType>
struct PrintReturnFunctor<ResultType, true>
{
  VTKM_CONT
  void operator()(
    const vtkm::internal::FunctionInterfaceReturnContainer<ResultType>& x) const
  {
    std::cout << x.Value << std::endl;
  }
};

template<typename ResultType>
struct PrintReturnFunctor<ResultType, false>
{
  VTKM_CONT
  void operator()(
    const vtkm::internal::FunctionInterfaceReturnContainer<ResultType>&) const
  {
    std::cout << "No return type." << std::endl;
  }
};

template<typename FunctionInterfaceType>
void PrintReturn(const FunctionInterfaceType& functionInterface)
{
  using ResultType = typename FunctionInterfaceType::ResultType;
  using ReturnContainerType =
    vtkm::internal::FunctionInterfaceReturnContainer<ResultType>;

  PrintReturnFunctor<ResultType, ReturnContainerType::VALID> printReturn;
  printReturn(functionInterface.GetReturnValueSafe());
}
////
//// END-EXAMPLE FunctionInterfaceReturnContainer.cxx
////

} // namespace ReturnContainerNamespace

void TryPrintReturn()
{
  vtkm::internal::FunctionInterface<size_t(const char*)> strlenInterface;
  strlenInterface.SetParameter<1>("Hello world");
  strlenInterface.InvokeCont(strlen);
  ReturnContainerNamespace::PrintReturn(strlenInterface);

  ReturnContainerNamespace::PrintReturn(
    vtkm::internal::make_FunctionInterface<void>((const char*)"Hello world"));
}
#endif

// Append and Replace no longer supported
#if 0
void Append()
{
  ////
  //// BEGIN-EXAMPLE FunctionInterfaceAppend.cxx
  ////
  using vtkm::internal::FunctionInterface;
  using vtkm::internal::make_FunctionInterface;

  using InitialFunctionInterfaceType =
    FunctionInterface<void(std::string, vtkm::Id)>;
  InitialFunctionInterfaceType initialFunctionInterface =
    make_FunctionInterface<void>(std::string("Hello World"), vtkm::Id(42));

  using AppendedFunctionInterfaceType1 =
    FunctionInterface<void(std::string, vtkm::Id, std::string)>;
  AppendedFunctionInterfaceType1 appendedFunctionInterface1 =
    initialFunctionInterface.Append(std::string("foobar"));
  // appendedFunctionInterface1 has parameters ("Hello World", 42, "foobar")

  using AppendedFunctionInterfaceType2 =
    InitialFunctionInterfaceType::AppendType<vtkm::Float32>::type;
  AppendedFunctionInterfaceType2 appendedFunctionInterface2 =
    initialFunctionInterface.Append(vtkm::Float32(3.141));
  // appendedFunctionInterface2 has parameters ("Hello World", 42, 3.141)
  ////
  //// END-EXAMPLE FunctionInterfaceAppend.cxx
  ////

  std::cout << "Checking appended interface 1." << std::endl;
  VTKM_TEST_ASSERT(appendedFunctionInterface1.GetParameter<1>() ==
                     std::string("Hello World"),
                   "Bad value in interface.");
  VTKM_TEST_ASSERT(appendedFunctionInterface1.GetParameter<2>() == 42,
                   "Bad value in interface.");
  VTKM_TEST_ASSERT(appendedFunctionInterface1.GetParameter<3>() ==
                     std::string("foobar"),
                   "Bad value in interface.");

  std::cout << "Checking appended interface 2." << std::endl;
  VTKM_TEST_ASSERT(appendedFunctionInterface2.GetParameter<1>() ==
                     std::string("Hello World"),
                   "Bad value in interface.");
  VTKM_TEST_ASSERT(appendedFunctionInterface2.GetParameter<2>() == 42,
                   "Bad value in interface.");
  VTKM_TEST_ASSERT(appendedFunctionInterface2.GetParameter<3>() ==
                     vtkm::Float32(3.141),
                   "Bad value in interface.");
}

void Replace()
{
  ////
  //// BEGIN-EXAMPLE FunctionInterfaceReplace.cxx
  ////
  using vtkm::internal::FunctionInterface;
  using vtkm::internal::make_FunctionInterface;

  using InitialFunctionInterfaceType =
    FunctionInterface<void(std::string, vtkm::Id)>;
  InitialFunctionInterfaceType initialFunctionInterface =
    make_FunctionInterface<void>(std::string("Hello World"), vtkm::Id(42));

  using ReplacedFunctionInterfaceType1 =
    FunctionInterface<void(vtkm::Float32, vtkm::Id)>;
  ReplacedFunctionInterfaceType1 replacedFunctionInterface1 =
    initialFunctionInterface.Replace<1>(vtkm::Float32(3.141));
  // replacedFunctionInterface1 has parameters (3.141, 42)

  using ReplacedFunctionInterfaceType2 =
    InitialFunctionInterfaceType::ReplaceType<2, std::string>::type;
  ReplacedFunctionInterfaceType2 replacedFunctionInterface2 =
    initialFunctionInterface.Replace<2>(std::string("foobar"));
  // replacedFunctionInterface2 has parameters ("Hello World", "foobar")
  ////
  //// END-EXAMPLE FunctionInterfaceReplace.cxx
  ////

  std::cout << "Checking replaced interface 1." << std::endl;
  VTKM_TEST_ASSERT(replacedFunctionInterface1.GetParameter<1>() ==
                     vtkm::Float32(3.141),
                   "Bad value in interface.");
  VTKM_TEST_ASSERT(replacedFunctionInterface1.GetParameter<2>() == 42,
                   "Bad value in interface.");

  std::cout << "Checking replaced interface 2." << std::endl;
  VTKM_TEST_ASSERT(replacedFunctionInterface2.GetParameter<1>() == "Hello World",
                   "Bad value in interface.");
  VTKM_TEST_ASSERT(replacedFunctionInterface2.GetParameter<2>() == "foobar",
                   "Bad value in interface.");
}

void NextFunctionChainCall(
  const vtkm::internal::FunctionInterface<void(vtkm::Id*, vtkm::Id)>& parameters)
{
  vtkm::Id expectedValue = TestValue(0, vtkm::Id());

  vtkm::Id* array = parameters.GetParameter<1>();
  vtkm::Id numValues = parameters.GetParameter<2>();

  std::cout << "Checking values." << std::endl;
  for (vtkm::Id index = 0; index < numValues; index++)
  {
    VTKM_TEST_ASSERT(array[index] == expectedValue, "Bad value.");
  }
}

////
//// BEGIN-EXAMPLE FunctionInterfaceAppendAndReplace.cxx
////
template<typename FunctionInterfaceType>
void FunctionCallChain(const FunctionInterfaceType& parameters, vtkm::Id arraySize)
{
  // In this hypothetical function call chain, this function replaces the
  // first parameter with an array of that type and appends the array size
  // to the end of the parameters.

  using ArrayValueType =
    typename FunctionInterfaceType::template ParameterType<1>::type;

  // Allocate and initialize array.
  ArrayValueType value = parameters.template GetParameter<1>();
  ArrayValueType* array = new ArrayValueType[arraySize];
  for (vtkm::Id index = 0; index < arraySize; index++)
  {
    array[index] = value;
  }

  // Call next function with modified parameters.
  NextFunctionChainCall(parameters.template Replace<1>(array).Append(arraySize));

  // Clean up.
  delete[] array;
}
////
//// END-EXAMPLE FunctionInterfaceAppendAndReplace.cxx
////

void TryAppendReplace()
{
  std::cout << "Using replace and append in function call chain." << std::endl;
  FunctionCallChain(
    vtkm::internal::make_FunctionInterface<void>(TestValue(0, vtkm::Id())), 10);
}
#endif

namespace StaticTransformNamespace
{

////
//// BEGIN-EXAMPLE FunctionInterfaceStaticTransform.cxx
////
struct ParametersToPointersFunctor
{
  template<typename T, vtkm::IdComponent Index>
  struct ReturnType
  {
    using type = const T*;
  };

  template<typename T, vtkm::IdComponent Index>
  VTKM_CONT const T* operator()(const T& x, vtkm::internal::IndexTag<Index>) const
  {
    return &x;
  }
};

template<typename FunctionInterfaceType>
VTKM_CONT typename FunctionInterfaceType::template StaticTransformType<
  ParametersToPointersFunctor>::type
ParametersToPointers(FunctionInterfaceType& functionInterface)
{
  return functionInterface.StaticTransformCont(ParametersToPointersFunctor());
}
////
//// END-EXAMPLE FunctionInterfaceStaticTransform.cxx
////

} // namespace StaticTransformNamespace

using namespace StaticTransformNamespace;

void TryStaticTransform()
{
  vtkm::internal::FunctionInterface<void(vtkm::Float32, vtkm::Int32)>
    originalFunctionInterface = vtkm::internal::make_FunctionInterface<void>(
      TestValue(1, vtkm::Float32()), TestValue(2, vtkm::Int32()));

  vtkm::internal::FunctionInterface<void(const vtkm::Float32*, const vtkm::Int32*)>
    transformedFunctionInterface = ParametersToPointers(originalFunctionInterface);

  VTKM_TEST_ASSERT(
    test_equal(*vtkm::internal::ParameterGet<1>(transformedFunctionInterface),
               TestValue(1, vtkm::Float32())),
    "Bad value in pointer.");
  VTKM_TEST_ASSERT(
    test_equal(*vtkm::internal::ParameterGet<2>(transformedFunctionInterface),
               TestValue(2, vtkm::Int32())),
    "Bad value in pointer.");
}

// DynamicTransform no longer supported
#if 0
namespace DynamicTransformNamespace
{

////
//// BEGIN-EXAMPLE FunctionInterfaceDynamicTransform.cxx
////
struct UnpackNumbersTransformFunctor
{
  template<typename InputType, typename ContinueFunctor, vtkm::IdComponent Index>
  VTKM_CONT void operator()(const InputType& input,
                            const ContinueFunctor& continueFunction,
                            vtkm::internal::IndexTag<Index>) const
  {
    continueFunction(input);
  }

  template<typename ContinueFunctor, vtkm::IdComponent Index>
  VTKM_CONT void operator()(const std::string& input,
                            const ContinueFunctor& continueFunction,
                            vtkm::internal::IndexTag<Index>) const
  {
    if ((input[0] >= '0') && (input[0] <= '9'))
    {
      std::stringstream stream(input);
      vtkm::FloatDefault value;
      stream >> value;
      continueFunction(value);
    }
    else
    {
      continueFunction(input);
    }
  }
};

////
//// PAUSE-EXAMPLE
////
struct CheckFunctor
{
  VTKM_CONT
  void operator()(vtkm::FloatDefault value1, std::string value2) const
  {
    VTKM_TEST_ASSERT(test_equal(value1, 42), "Wrong converted value.");
    VTKM_TEST_ASSERT(value2 == "Hello World", "Wrong passed value");
  }

  template<typename T1, typename T2>
  VTKM_CONT void operator()(T1, T2) const
  {
    VTKM_TEST_FAIL("Called wrong form of CheckFunctor");
  }
};
////
//// RESUME-EXAMPLE
////
struct UnpackNumbersFinishFunctor
{
  template<typename FunctionInterfaceType>
  VTKM_CONT void operator()(FunctionInterfaceType& functionInterface) const
  {
    // Do something
    ////
    //// PAUSE-EXAMPLE
    ////
    functionInterface.InvokeCont(CheckFunctor());
    ////
    //// RESUME-EXAMPLE
    ////
  }
};

template<typename FunctionInterfaceType>
void DoUnpackNumbers(const FunctionInterfaceType& functionInterface)
{
  functionInterface.DynamicTransformCont(UnpackNumbersTransformFunctor(),
                                         UnpackNumbersFinishFunctor());
}
////
//// END-EXAMPLE FunctionInterfaceDynamicTransform.cxx
////

} // namespace DynamicTransformNamespace

using namespace DynamicTransformNamespace;

void TryDynamicTransform()
{
  vtkm::internal::FunctionInterface<void(std::string, std::string)>
    functionInterface = vtkm::internal::make_FunctionInterface<void>(
      std::string("42"), std::string("Hello World"));
  DoUnpackNumbers(functionInterface);
}
#endif

// DynamicTransform function no longer supported
#if 0
namespace DynamicTransformFunctorNamespace
{

////
//// BEGIN-EXAMPLE DynamicTransform.cxx
////
template<typename Device>
struct ArrayCopyFunctor
{
  template<typename Signature>
  VTKM_CONT void operator()(
    vtkm::internal::FunctionInterface<Signature> functionInterface) const
  {
    functionInterface.InvokeCont(*this);
  }

  template<typename T, typename SIn, typename SOut>
  VTKM_CONT void operator()(const vtkm::cont::ArrayHandle<T, SIn>& input,
                            vtkm::cont::ArrayHandle<T, SOut>& output) const
  {
    vtkm::cont::Algorithm::Copy(input, output);
  }

  template<typename TIn, typename SIn, typename TOut, typename SOut>
  VTKM_CONT void operator()(const vtkm::cont::ArrayHandle<TIn, SIn>&,
                            vtkm::cont::ArrayHandle<TOut, SOut>&) const
  {
    throw vtkm::cont::ErrorBadType("Arrays to copy must be the same type.");
  }
};

template<typename Device>
void CopyVariantArrays(vtkm::cont::VariantArrayHandle input,
                       vtkm::cont::VariantArrayHandle output,
                       Device)
{
  vtkm::internal::FunctionInterface<void(vtkm::cont::VariantArrayHandle,
                                         vtkm::cont::VariantArrayHandle)>
    functionInterface = vtkm::internal::make_FunctionInterface<void>(input, output);

  functionInterface.DynamicTransformCont(vtkm::cont::internal::DynamicTransform(),
                                         ArrayCopyFunctor<Device>());
}
////
//// END-EXAMPLE DynamicTransform.cxx
////

} // namespace DynamicTransformFunctorNamespace

using namespace DynamicTransformFunctorNamespace;

void TryDynamicTransformFunctor()
{
  static const vtkm::Id ARRAY_SIZE = 10;
  vtkm::Float32 buffer[ARRAY_SIZE];
  for (vtkm::Id index = 0; index < ARRAY_SIZE; index++)
  {
    buffer[index] = TestValue(index, vtkm::Float32());
  }

  vtkm::cont::ArrayHandle<vtkm::Float32> inputArray =
    vtkm::cont::make_ArrayHandle(buffer, ARRAY_SIZE);
  vtkm::cont::ArrayHandle<vtkm::Float32> outputArray;

  CopyVariantArrays(inputArray, outputArray, vtkm::cont::DeviceAdapterTagSerial());

  CheckPortal(outputArray.ReadPortal());
}
#endif

// FunctionInterface::ForEach no longer supported
#if 0
namespace ForEachNamespace
{

////
//// BEGIN-EXAMPLE FunctionInterfaceForEach.cxx
////
struct PrintArgumentFunctor
{
  template<typename T, vtkm::IdComponent Index>
  VTKM_CONT void operator()(const T& argument, vtkm::internal::IndexTag<Index>) const
  {
    std::cout << Index << ":" << argument << " ";
  }
};

template<typename FunctionInterfaceType>
VTKM_CONT void PrintArguments(const FunctionInterfaceType& functionInterface)
{
  std::cout << "( ";
  functionInterface.ForEachCont(PrintArgumentFunctor());
  std::cout << ")" << std::endl;
}
////
//// END-EXAMPLE FunctionInterfaceForEach.cxx
////

} // namespace ForEachNamespace

using namespace ForEachNamespace;

void TryPrintArguments()
{
  PrintArguments(vtkm::internal::make_FunctionInterface<void>(
    std::string("Hello"), 42, std::string("World"), 3.14));
}
#endif

void Test()
{
  BasicFunctionInterface();
  TryGetParameter();
  //  TrySetParameter();
  //  BasicInvoke();
  //  TryTransformedInvoke();
  //  TryPrintReturn();
  //  Append();
  //  Replace();
  //  TryAppendReplace();
  TryStaticTransform();
  //  TryDynamicTransform();
  //  TryDynamicTransformFunctor();
  //  TryPrintArguments();
}

} // anonymous namespace

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