// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

// .NAME TestCxxFeatures
// .SECTION Description
// Provides a reference for the set of C++ features that can be used
// by VTK.

//------------------------------------------------------------------------------

/* Check for known compilers.  */

#if defined(__HP_aCC)
#define VTK_CXX_ACC
#endif

#if defined(__SUNPRO_CC)
#define VTK_CXX_SUNPRO
#endif

//------------------------------------------------------------------------------

/* Check for known compiler limitations.  */

// Assume standard behavior if symbol is not already defined.
#if !defined(VTK_TYPENAME)
#define VTK_TYPENAME typename
#endif

// Assume standard behavior if symbol is not already defined.
#if !defined(VTK_CLASS_TEMPLATE_SPECIALIZATION)
#define VTK_CLASS_TEMPLATE_SPECIALIZATION template <>
#endif

//------------------------------------------------------------------------------

#include "vtkStringFormatter.h"
#include "vtkSystemIncludes.h"

//------------------------------------------------------------------------------

/* Test inclusion of typeinfo header.  */

#include <typeinfo>

//------------------------------------------------------------------------------

/* Test nested classes defined outside.  */

class NestedTestOuter
{
public:
  NestedTestOuter();
  ~NestedTestOuter();

private:
  class NestedTestInner;
  NestedTestInner* Inner;
};

class NestedTestOuter::NestedTestInner
{
public:
  NestedTestInner() {}
  ~NestedTestInner() {}
};

NestedTestOuter::NestedTestOuter()
{
  this->Inner = new NestedTestInner;
}

NestedTestOuter::~NestedTestOuter()
{
  delete this->Inner;
}

//------------------------------------------------------------------------------

/* Test full template specialization of functions.  */
template <class T>
int FullySpecializedFunction(T*)
{
  return 0;
}

template <>
int FullySpecializedFunction<int>(int*)
{
  return 1;
}

int TestFullySpecializedFunction()
{
  int result = 1;
  int should_be_0 = FullySpecializedFunction(static_cast<float*>(0));
  if (should_be_0 != 0)
  {
    std::cerr << "FullySpecializedFunction<float*>() returned " << should_be_0 << ", not 0.\n";
    result = 0;
  }
  int should_be_1 = FullySpecializedFunction(static_cast<int*>(0));
  if (should_be_1 != 1)
  {
    std::cerr << "FullySpecializedFunction(int*) returned " << should_be_1 << ", not 1.\n";
    result = 0;
  }
  return result;
}

//------------------------------------------------------------------------------

/* Test member template of non-template.  */

class NonTemplate
{
  void* Pointer;

public:
  template <class T>
  void Set(T* t)
  {
    this->Pointer = t;
  }
  template <class T>
  void Get(T*& t)
  {
    t = static_cast<T*>(this->Pointer);
  }
};

int TestNonTemplateMemberTemplate()
{
  int x = 123;
  int* px = 0;
  NonTemplate nt;
  nt.Set(&x);
  nt.Get(px);
  return (*px == 123);
}

//------------------------------------------------------------------------------

/* Test member template of template.  */

template <class T>
class OuterTemplate
{
  T* Pointer;

public:
  template <class U>
  void Set(U* u)
  {
    this->Pointer = u;
  }
  template <class U>
  void Get(U*& u)
  {
    u = static_cast<U*>(this->Pointer);
  }
};

int TestTemplateMemberTemplate()
{
  int x = 123;
  int* px = 0;
  OuterTemplate<void> nt;
  nt.Set(&x);
  nt.Get(px);
  return (*px == 123);
}

//------------------------------------------------------------------------------

/* Test full template specialization of classes.  */

template <class T>
struct FullySpecializedClass
{
  static int Method() { return 0; }
  typedef T Type;
};

VTK_CLASS_TEMPLATE_SPECIALIZATION
struct FullySpecializedClass<float>
{
  static int Method() { return 1; }
  typedef int Type;
};

template <class T>
int TestFullySpecializedClassTrait(T*)
{
  typedef VTK_TYPENAME FullySpecializedClass<T>::Type Type;
  if (static_cast<Type>(3.1) == 3.1)
  {
    return 0;
  }
  return 1;
}

int TestFullySpecializedClass()
{
  int result = 1;
  int should_be_0 = FullySpecializedClass<int>::Method();
  if (should_be_0 != 0)
  {
    std::cerr << "FullySpecializedClass<int>::Method() returned " << should_be_0 << ", not 0.\n";
    result = 0;
  }
  int should_be_1 = FullySpecializedClass<float>::Method();
  if (should_be_1 != 1)
  {
    std::cerr << "FullySpecializedClass<float>::Method() returned " << should_be_1 << ", not 1.\n";
    result = 0;
  }
  if (!TestFullySpecializedClassTrait(static_cast<float*>(0)))
  {
    std::cerr << "Trait lookup of float didn't produce int.";
    result = 0;
  }
  return result;
}

//------------------------------------------------------------------------------

/* Test if(int x = f()) style scoping.  */

int TestIfScopeHelper(int i)
{
  int result = 1;
  if (int x = i)
  {
    if (x != i)
    {
      std::cerr << "TestIfScope: x != " << i << "\n";
      result = 0;
    }
  }
  else
  {
    if (x != i)
    {
      std::cerr << "TestIfScope: x != " << i << "\n";
      result = 0;
    }
  }
  int x = result;
  return x;
}

int TestIfScope()
{
  int result = 1;
  if (!TestIfScopeHelper(1))
  {
    result = 0;
  }
  if (!TestIfScopeHelper(0))
  {
    result = 0;
  }
  return result;
}

//------------------------------------------------------------------------------

/* Test non-type template parameter.  */

template <int I>
struct NonTypeTemplate
{
  static int GetValue() { return I; }
};

int TestNonTypeTemplate()
{
  int result = 1;
  if (NonTypeTemplate<0>::GetValue() != 0)
  {
    std::cerr << "NonTypeTemplate<0>::GetValue() != 0\n";
    result = 0;
  }
  if (NonTypeTemplate<1>::GetValue() != 1)
  {
    std::cerr << "NonTypeTemplate<1>::GetValue() != 1\n";
    result = 0;
  }
  if (NonTypeTemplate<2>::GetValue() != 2)
  {
    std::cerr << "NonTypeTemplate<2>::GetValue() != 2\n";
    result = 0;
  }
  return result;
}

//------------------------------------------------------------------------------

/* Test mixed type and non-type template arguments in a non-trivial way.  */

template <class T, int N>
int TestMixedTypeTemplateFunction(T (*)[N])
{
  return N;
}
int TestMixedTypeTemplate()
{
  int x2[2];
  float x3[3];
  int result = 1;
  if (TestMixedTypeTemplateFunction(&x2) != 2)
  {
    std::cerr << "TestMixedTypeTemplateFunction(&x2) != 2\n";
    result = 0;
  }
  if (TestMixedTypeTemplateFunction(&x3) != 3)
  {
    std::cerr << "TestMixedTypeTemplateFunction(&x3) != 3\n";
    result = 0;
  }
  return result;
}

//------------------------------------------------------------------------------

class SafeBoolIdiomClass
{
private:
  struct SafeBoolDummy
  {
    void Dummy() {}
  };
  typedef void (SafeBoolDummy::*SafeBool)();

public:
  SafeBoolIdiomClass(int x)
    : Value(x)
  {
  }
  operator SafeBool() { return this->Value ? &SafeBoolDummy::Dummy : nullptr; }
  SafeBool operator!() { return this->Value ? nullptr : &SafeBoolDummy::Dummy; }

protected:
  int Value;
};

int TestSafeBoolIdiom()
{
  int result = 1;
  SafeBoolIdiomClass cTrue(1);
  SafeBoolIdiomClass cFalse(0);
  if (cTrue)
  {
  }
  else
  {
    std::cerr << "if(cTrue) evaluates to false.\n";
    result = 0;
  }
  if (!cTrue)
  {
    std::cerr << "if(!cTrue) evaluates to true.\n";
    result = 0;
  }
  if (cFalse)
  {
    std::cerr << "if(cFalse) evaluates to true.\n";
    result = 0;
  }
  if (!cFalse)
  {
  }
  else
  {
    std::cerr << "if(!cFalse) evaluates to false.\n";
    result = 0;
  }
  return result;
}

//------------------------------------------------------------------------------

/* Test use of exceptions.  */

#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4702) /* Unreachable code. */
#endif

class TestExceptionUnwind
{
  int* pvalue;

public:
  TestExceptionUnwind(int* p)
    : pvalue(p)
  {
  }
  ~TestExceptionUnwind() { *pvalue = 1; }
  void Use() {}
};

class ExceptionClass
{
};

void TestThrowException(int* p)
{
  TestExceptionUnwind unwind(p);
  unwind.Use();
  throw ExceptionClass();
}

int TestException()
{
  int value = 0;
  try
  {
    TestThrowException(&value);
  }
  catch (ExceptionClass&)
  {
    if (value)
    {
      return 1;
    }
    else
    {
      std::cerr << "TestExceptionUnwind object not destroyed!" << std::endl;
      return 0;
    }
  }
  catch (...)
  {
    std::cerr << "ExceptionClass not caught!" << std::endl;
    return 0;
  }
  std::cerr << "No exception caught!" << std::endl;
  return 0;
}

#if defined(_MSC_VER)
#pragma warning(pop)
#endif

//-------------------------------------------------------------------
// See if the following code works on all platforms
#if defined(_MSC_VER) && defined(_DEBUG)
/* MSVC debug hook to prevent dialogs when running from DART.  */
#include <crtdbg.h>
static int TestDriverDebugReport(int type, char* message, int* retVal)
{
  (void)type;
  (void)retVal;
  vtk::print(stderr, message);
  exit(1);
}
#endif

//------------------------------------------------------------------------------

/* Test setlocale  */
#include <locale.h>

#include <iostream>

int TestSetLocale()
{
  char* oldLocale = strdup(setlocale(LC_NUMERIC, nullptr));
  setlocale(LC_NUMERIC, "English");

  // restore the local
  if (oldLocale)
  {
    setlocale(LC_NUMERIC, oldLocale);
    free(oldLocale);
    return 1;
  }
  return 0;
}

//------------------------------------------------------------------------------

#define DO_TEST(x)                                                                                 \
  if (x())                                                                                         \
  {                                                                                                \
    std::cout << "Passed: " #x "\n";                                                               \
  }                                                                                                \
  else                                                                                             \
  {                                                                                                \
    std::cout << "Failed: " #x "\n";                                                               \
    result = 1;                                                                                    \
  }

int main()
{
  int result = 0;
  DO_TEST(TestFullySpecializedFunction);
  DO_TEST(TestNonTemplateMemberTemplate);
  DO_TEST(TestTemplateMemberTemplate);
  DO_TEST(TestFullySpecializedClass);
  DO_TEST(TestIfScope);
  DO_TEST(TestNonTypeTemplate);
  DO_TEST(TestMixedTypeTemplate);
  DO_TEST(TestSafeBoolIdiom);
  DO_TEST(TestException);
  DO_TEST(TestSetLocale);

#if defined(_MSC_VER) && defined(_DEBUG)
  // just call the code to shut up a linker warning
  int retVal = 0;
  if (result)
  {
    // really shouldn't be called unless something else failed
    // just want to make the compiler think it might get called
    // all this will be yanked once I see the results of this test
    TestDriverDebugReport(0, "a temp test", &retVal);
  }
#endif
  return result;
}
