/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/

#ifndef itkTestingMacros_h
#define itkTestingMacros_h

#include "itkMacro.h"

#include <cstring>

/** \namespace itk
 * \brief The "itk" namespace contains all Insight Segmentation and
 * Registration Toolkit (ITK) classes. There are several nested namespaces
 * within the itk:: namespace. */
namespace itk
{
// end namespace itk - this is here for documentation purposes
}

// DEPRECATED: These macros are left here for compatibility.
// In the future, they will be removed in favor of the "ITK_" prefixed
// versions.
#if defined(ITK_FUTURE_LEGACY_REMOVE)

#  if defined(__clang__) || defined(__GNUC__)
#    pragma GCC poison EXERCISE_BASIC_OBJECT_METHODS
#    pragma GCC poison TRY_EXPECT_EXCEPTION
#    pragma GCC poison TRY_EXPECT_NO_EXCEPTION
#    pragma GCC poison TEST_EXPECT_TRUE_STATUS_VALUE
#    pragma GCC poison TEST_EXPECT_TRUE
#    pragma GCC poison TEST_EXPECT_EQUAL_STATUS_VALUE
#    pragma GCC poison TEST_EXPECT_EQUAL
#    pragma GCC poison TEST_SET_GET
#    pragma GCC poison TEST_SET_GET_VALUE
#    pragma GCC poison TEST_SET_GET_NULL_VALUE
#    pragma GCC poison TEST_SET_GET_BOOLEAN
#  else
#    define EXERCISE_BASIC_OBJECT_METHODS "Replace EXERCISE_BASIC_OBJECT_METHODS with ITK_EXERCISE_BASIC_OBJECT_METHODS"
#    define TRY_EXPECT_EXCEPTION "Replace TRY_EXPECT_EXCEPTION with ITK_TRY_EXPECT_EXCEPTION"
#    define TRY_EXPECT_NO_EXCEPTION "Replace TRY_EXPECT_NO_EXCEPTION with ITK_TRY_EXPECT_NO_EXCEPTION"
#    define TEST_EXPECT_TRUE_STATUS_VALUE "Replace TEST_EXPECT_TRUE_STATUS_VALUE with ITK_TEST_EXPECT_TRUE_STATUS_VALUE"
#    define TEST_EXPECT_TRUE "Replace TEST_EXPECT_TRUE with ITK_TEST_EXPECT_TRUE"
#    define TEST_EXPECT_EQUAL_STATUS_VALUE \
      "Replace TEST_EXPECT_EQUAL_STATUS_VALUE with ITK_TEST_EXPECT_EQUAL_STATUS_VALUE"
#    define TEST_EXPECT_EQUAL "Replace TEST_EXPECT_EQUAL with ITK_TEST_EXPECT_EQUAL"
#    define TEST_SET_GET "Replace TEST_SET_GET with ITK_TEST_SET_GET"
#    define TEST_SET_GET_VALUE "Replace TEST_SET_GET_VALUE with ITK_TEST_SET_GET_VALUE"
#    define TEST_SET_GET_NULL_VALUE "Replace TEST_SET_GET_NULL_VALUE with ITK_TEST_SET_GET_NULL_VALUE"
#    define TEST_SET_GET_BOOLEAN "Replace TEST_SET_GET_BOOLEAN with ITK_TEST_SET_GET_BOOLEAN"
#  endif
#else
#  define EXERCISE_BASIC_OBJECT_METHODS ITK_EXERCISE_BASIC_OBJECT_METHODS
#  define TRY_EXPECT_EXCEPTION ITK_TRY_EXPECT_EXCEPTION
#  define TRY_EXPECT_NO_EXCEPTION ITK_TRY_EXPECT_NO_EXCEPTION
#  define TEST_EXPECT_TRUE_STATUS_VALUE ITK_TEST_EXPECT_TRUE_STATUS_VALUE
#  define TEST_EXPECT_TRUE ITK_TEST_EXPECT_TRUE
#  define TEST_EXPECT_EQUAL_STATUS_VALUE ITK_TEST_EXPECT_EQUAL_STATUS_VALUE
#  define TEST_EXPECT_EQUAL ITK_TEST_EXPECT_EQUAL
#  define TEST_SET_GET ITK_TEST_SET_GET
#  define TEST_SET_GET_VALUE ITK_TEST_SET_GET_VALUE
#  define TEST_SET_GET_NULL_VALUE ITK_TEST_SET_GET_NULL_VALUE
#  define TEST_SET_GET_BOOLEAN ITK_TEST_SET_GET_BOOLEAN
#endif

/* clang-format off */
#if defined(__GNUC__)
#define ITK_EXERCISE_BASIC_OBJECT_METHODS(object, ClassName, SuperclassName)                                           \
  object->Print(std::cout);                                                                                            \
  std::cout << "Name of Class = " << object->GetNameOfClass() << std::endl;                                            \
  ITK_MACROEND_NOOP_STATEMENT
#else // not GCC
#define ITK_EXERCISE_BASIC_OBJECT_METHODS(object, ClassName, SuperclassName)                                           \
  object->Print(std::cout);                                                                                            \
  std::cout << "Name of Class = " << object->Self::GetNameOfClass() << std::endl;                                            \
  std::cout << "Name of Superclass = " << object->Superclass::GetNameOfClass() << std::endl;                           \
  if (!std::strcmp(object->Self::GetNameOfClass(), #ClassName))                                                              \
  {                                                                                                                    \
    std::cout << "Class name is correct" << std::endl;                                                                 \
  }                                                                                                                    \
  else                                                                                                                 \
  {                                                                                                                    \
    std::cerr << "Class name provided does not match object's NameOfClass" << std::endl;                               \
    return EXIT_FAILURE;                                                                                               \
  }                                                                                                                    \
  if (!std::strcmp(object->Superclass::GetNameOfClass(), #SuperclassName))                                             \
  {                                                                                                                    \
    std::cout << "Superclass name is correct" << std::endl;                                                            \
  }                                                                                                                    \
  else                                                                                                                 \
  {                                                                                                                    \
    std::cerr << "Superclass name provided does not match object's Superclass::NameOfClass" << std::endl;              \
    return EXIT_FAILURE;                                                                                               \
  }                                                                                                                    \
  ITK_MACROEND_NOOP_STATEMENT
#endif // GCC
/* clang-format on */

#define ITK_TRY_EXPECT_EXCEPTION(command)                             \
  try                                                                 \
  {                                                                   \
    std::cout << "Trying " << #command << std::endl;                  \
    command;                                                          \
    std::cerr << "Failed to catch expected exception" << std::endl;   \
    std::cerr << "  In " __FILE__ ", line " << __LINE__ << std::endl; \
    return EXIT_FAILURE;                                              \
  }                                                                   \
  catch (const itk::ExceptionObject & excp)                           \
  {                                                                   \
    std::cout << "Caught expected exception" << std::endl;            \
    std::cout << excp << std::endl;                                   \
  }                                                                   \
  ITK_MACROEND_NOOP_STATEMENT


#define ITK_TRY_EXPECT_NO_EXCEPTION(command)                          \
  try                                                                 \
  {                                                                   \
    std::cout << "Trying " << #command << std::endl;                  \
    command;                                                          \
  }                                                                   \
  catch (const itk::ExceptionObject & excp)                           \
  {                                                                   \
    std::cerr << excp << std::endl;                                   \
    std::cerr << "  In " __FILE__ ", line " << __LINE__ << std::endl; \
    return EXIT_FAILURE;                                              \
  }                                                                   \
  ITK_MACROEND_NOOP_STATEMENT

#define ITK_TEST_EXPECT_TRUE_STATUS_VALUE(command, statusVal)                   \
  {                                                                             \
    ITK_GCC_PRAGMA_PUSH                                                         \
    ITK_GCC_SUPPRESS_Wfloat_equal                                               \
    bool _ITK_TEST_EXPECT_TRUE_command(command);                                \
    ITK_GCC_PRAGMA_POP                                                          \
    if (!(_ITK_TEST_EXPECT_TRUE_command))                                       \
    {                                                                           \
      std::cerr << "Error in " << #command << std::endl;                        \
      std::cerr << "  In " __FILE__ ", line " << __LINE__ << std::endl;         \
      std::cerr << "Expected true" << std::endl;                                \
      std::cerr << "  but got  " << _ITK_TEST_EXPECT_TRUE_command << std::endl; \
      statusVal = EXIT_FAILURE;                                                 \
    }                                                                           \
  }                                                                             \
  ITK_MACROEND_NOOP_STATEMENT

#define ITK_TEST_EXPECT_TRUE(command)                                           \
  {                                                                             \
    ITK_GCC_PRAGMA_PUSH                                                         \
    ITK_GCC_SUPPRESS_Wfloat_equal                                               \
    bool _ITK_TEST_EXPECT_TRUE_command(command);                                \
    ITK_GCC_PRAGMA_POP                                                          \
    if (!(_ITK_TEST_EXPECT_TRUE_command))                                       \
    {                                                                           \
      std::cerr << "Error in " << #command << std::endl;                        \
      std::cerr << "  In " __FILE__ ", line " << __LINE__ << std::endl;         \
      std::cerr << "Expected true" << std::endl;                                \
      std::cerr << "  but got  " << _ITK_TEST_EXPECT_TRUE_command << std::endl; \
      return EXIT_FAILURE;                                                      \
    }                                                                           \
  }                                                                             \
  ITK_MACROEND_NOOP_STATEMENT


#define ITK_TEST_EXPECT_EQUAL_STATUS_VALUE(lh, rh, statusVal)           \
  {                                                                     \
    ITK_GCC_PRAGMA_PUSH                                                 \
    ITK_GCC_SUPPRESS_Wfloat_equal                                       \
    bool _ITK_TEST_EXPECT_EQUAL_result((lh) == (rh));                   \
    ITK_GCC_PRAGMA_POP                                                  \
    if (!(_ITK_TEST_EXPECT_EQUAL_result))                               \
    {                                                                   \
      std::cerr << "Error in " << #lh << " == " << #rh << std::endl;    \
      std::cerr << "\tIn " __FILE__ ", line " << __LINE__ << std::endl; \
      std::cerr << "\tlh: " << (lh) << std::endl;                       \
      std::cerr << "\trh: " << (rh) << std::endl;                       \
      std::cerr << "Expression is not equal" << std::endl;              \
      statusVal = EXIT_FAILURE;                                         \
    }                                                                   \
  }                                                                     \
  ITK_MACROEND_NOOP_STATEMENT

#define ITK_TEST_EXPECT_EQUAL(lh, rh)                                   \
  {                                                                     \
    ITK_GCC_PRAGMA_PUSH                                                 \
    ITK_GCC_SUPPRESS_Wfloat_equal                                       \
    bool _ITK_TEST_EXPECT_EQUAL_result((lh) == (rh));                   \
    ITK_GCC_PRAGMA_POP                                                  \
    if (!(_ITK_TEST_EXPECT_EQUAL_result))                               \
    {                                                                   \
      std::cerr << "Error in " << #lh << " == " << #rh << std::endl;    \
      std::cerr << "\tIn " __FILE__ ", line " << __LINE__ << std::endl; \
      std::cerr << "\tlh: " << (lh) << std::endl;                       \
      std::cerr << "\trh: " << (rh) << std::endl;                       \
      std::cerr << "Expression is not equal" << std::endl;              \
      return EXIT_FAILURE;                                              \
    }                                                                   \
  }                                                                     \
  ITK_MACROEND_NOOP_STATEMENT


#define ITK_TEST_SET_GET(variable, command)                           \
  if (variable != command)                                            \
  {                                                                   \
    std::cerr << "Error in " << #command << std::endl;                \
    std::cerr << "  In " __FILE__ ", line " << __LINE__ << std::endl; \
    std::cerr << "Expected " << variable.GetPointer() << std::endl;   \
    std::cerr << "but got  " << command << std::endl;                 \
    return EXIT_FAILURE;                                              \
  }                                                                   \
  ITK_MACROEND_NOOP_STATEMENT


#define ITK_TEST_SET_GET_VALUE(variable, command)                     \
  ITK_GCC_PRAGMA_PUSH                                                 \
  ITK_GCC_SUPPRESS_Wfloat_equal                                       \
  if (variable != command)                                            \
  {                                                                   \
    std::cerr << "Error in " << #command << std::endl;                \
    std::cerr << "  In " __FILE__ ", line " << __LINE__ << std::endl; \
    std::cerr << "Expected " << variable << std::endl;                \
    std::cerr << "but got  " << command << std::endl;                 \
    return EXIT_FAILURE;                                              \
  }                                                                   \
  ITK_GCC_PRAGMA_POP                                                  \
  ITK_MACROEND_NOOP_STATEMENT

#define ITK_TEST_SET_GET_NULL_VALUE(command)                          \
  if (nullptr != command)                                             \
  {                                                                   \
    std::cerr << "Error in " << #command << std::endl;                \
    std::cerr << "  In " __FILE__ ", line " << __LINE__ << std::endl; \
    std::cerr << "Expected "                                          \
              << "nullptr" << std::endl;                              \
    std::cerr << "but got  " << command << std::endl;                 \
    return EXIT_FAILURE;                                              \
  }                                                                   \
  ITK_MACROEND_NOOP_STATEMENT

#define ITK_TEST_SET_GET_BOOLEAN(object, variable, value)                                               \
  object->Set##variable(false);                                                                         \
  object->Set##variable(true);                                                                          \
  if (object->Get##variable() != 1)                                                                     \
  {                                                                                                     \
    std::cerr << "Error in Set/Get" #variable << ", Get" #variable << " is " << object->Get##variable() \
              << " instead of 1" << std::endl;                                                          \
    return EXIT_FAILURE;                                                                                \
  }                                                                                                     \
  object->Set##variable(false);                                                                         \
  if (object->Get##variable() != 0)                                                                     \
  {                                                                                                     \
    std::cerr << "Error in Set/Get" #variable << ", Get" #variable << " is " << object->Get##variable() \
              << " instead of 0" << std::endl;                                                          \
    return EXIT_FAILURE;                                                                                \
  }                                                                                                     \
  object->variable##On();                                                                               \
  if (object->Get##variable() != 1)                                                                     \
  {                                                                                                     \
    std::cerr << "Error in On/Get" #variable << ", Get" #variable << " is " << object->Get##variable()  \
              << " instead of 1" << std::endl;                                                          \
    return EXIT_FAILURE;                                                                                \
  }                                                                                                     \
  object->variable##Off();                                                                              \
  if (object->Get##variable() != 0)                                                                     \
  {                                                                                                     \
    std::cerr << "Error in Off/Get" #variable << ", Get" #variable << " is " << object->Get##variable() \
              << " instead of 0" << std::endl;                                                          \
    return EXIT_FAILURE;                                                                                \
  }                                                                                                     \
  object->Set##variable(value)

/** The name of the executable, argv[0], is not always available as argv[0] may be null.
 * In that case, use the name of the test function.
 */
#define itkNameOfTestExecutableMacro(argv)                                                                             \
  [argv](const std::string & functionName) {                                                                           \
    return ((argv == nullptr) || (argv[0] == nullptr) || (argv[0][0] == '\0')) ? ("<" + functionName + " executable>") \
                                                                               : argv[0];                              \
  }(__func__)

#endif
