#ifndef RAPID_CXX_TEST_H
#define RAPID_CXX_TEST_H

#include <cassert>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "test_macros.h"

#if !defined(RAPID_CXX_TEST_NO_SYSTEM_HEADER) || !defined(__GNUC__)
#  pragma GCC system_header
#endif

#define RAPID_CXX_TEST_PP_CAT(x, y)   RAPID_CXX_TEST_PP_CAT_2(x, y)
#define RAPID_CXX_TEST_PP_CAT_2(x, y) x##y

#define RAPID_CXX_TEST_PP_STR(...)   RAPID_CXX_TEST_PP_STR_2(__VA_ARGS__)
#define RAPID_CXX_TEST_PP_STR_2(...) #__VA_ARGS__

#if defined(__GNUC__)
#  define TEST_FUNC_NAME()      __PRETTY_FUNCTION__
#  define RAPID_CXX_TEST_UNUSED __attribute__((unused))
#else
#  define TEST_FUNC_NAME() __func__
#  define RAPID_CXX_TEST_UNUSED
#endif

////////////////////////////////////////////////////////////////////////////////
//                          TEST_SUITE
////////////////////////////////////////////////////////////////////////////////
#define TEST_SUITE(Name)                                    \
  namespace Name                                            \
  {                                                         \
  inline ::rapid_cxx_test::test_suite& get_test_suite()     \
  {                                                         \
    static ::rapid_cxx_test::test_suite m_suite(#Name);     \
    return m_suite;                                         \
  }                                                         \
                                                            \
  inline int unit_test_main(int, char**)                    \
  {                                                         \
    ::rapid_cxx_test::test_runner runner(get_test_suite()); \
    return runner.run();                                    \
  }                                                         \
  }                                                         \
  int main(int argc, char** argv)                           \
  {                                                         \
    return Name::unit_test_main(argc, argv);                \
  }                                                         \
  namespace Name                                            \
  { /* namespace closed in TEST_SUITE_END */
#

////////////////////////////////////////////////////////////////////////////////
//                         TEST_SUITE_END
////////////////////////////////////////////////////////////////////////////////
#define TEST_SUITE_END() } /* namespace opened in TEST_SUITE(...) */
#

////////////////////////////////////////////////////////////////////////////////
//                          TEST_CASE
////////////////////////////////////////////////////////////////////////////////

#if !defined(__clang__)
#
#  define TEST_CASE(Name)                                                                              \
    void Name();                                                                                       \
    static void RAPID_CXX_TEST_PP_CAT(Name, _invoker)()                                                \
    {                                                                                                  \
      Name();                                                                                          \
    }                                                                                                  \
    static ::rapid_cxx_test::registrar RAPID_CXX_TEST_PP_CAT(rapid_cxx_test_registrar_, Name)(         \
      get_test_suite(),                                                                                \
      ::rapid_cxx_test::test_case(__FILE__, #Name, __LINE__, &RAPID_CXX_TEST_PP_CAT(Name, _invoker))); \
    void Name()
#
#else /* __clang__ */
#
#  define TEST_CASE(Name)                                                                                  \
    void Name();                                                                                           \
    static void RAPID_CXX_TEST_PP_CAT(Name, _invoker)()                                                    \
    {                                                                                                      \
      Name();                                                                                              \
    }                                                                                                      \
    _Pragma("clang diagnostic push")                                                                       \
      _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") static ::rapid_cxx_test::registrar     \
        RAPID_CXX_TEST_PP_CAT(rapid_cxx_test_registrar_, Name)(                                            \
          get_test_suite(),                                                                                \
          ::rapid_cxx_test::test_case(__FILE__, #Name, __LINE__, &RAPID_CXX_TEST_PP_CAT(Name, _invoker))); \
    _Pragma("clang diagnostic pop") void Name()
#
#endif /* !defined(__clang__) */

#define TEST_SET_CHECKPOINT() ::rapid_cxx_test::set_checkpoint(__FILE__, TEST_FUNC_NAME(), __LINE__)

#define RAPID_CXX_TEST_OUTCOME()

////////////////////////////////////////////////////////////////////////////////
//                              TEST_UNSUPPORTED
////////////////////////////////////////////////////////////////////////////////
#define TEST_UNSUPPORTED()                                                                        \
  do                                                                                              \
  {                                                                                               \
    TEST_SET_CHECKPOINT();                                                                        \
    ::rapid_cxx_test::test_outcome m_f(                                                           \
      ::rapid_cxx_test::failure_type::unsupported, __FILE__, TEST_FUNC_NAME(), __LINE__, "", ""); \
    ::rapid_cxx_test::get_reporter().report(m_f);                                                 \
    return;                                                                                       \
  } while (false)
#

////////////////////////////////////////////////////////////////////////////////
//                            BASIC ASSERTIONS
////////////////////////////////////////////////////////////////////////////////
#define TEST_WARN(...)                                                                                                \
  do                                                                                                                  \
  {                                                                                                                   \
    TEST_SET_CHECKPOINT();                                                                                            \
    ::rapid_cxx_test::test_outcome m_f(                                                                               \
      ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__, "TEST_WARN(" #__VA_ARGS__ ")", ""); \
    if (not(__VA_ARGS__))                                                                                             \
    {                                                                                                                 \
      m_f.type = ::rapid_cxx_test::failure_type::warn;                                                                \
    }                                                                                                                 \
    ::rapid_cxx_test::get_reporter().report(m_f);                                                                     \
  } while (false)
#

#define TEST_CHECK(...)                                                                                                \
  do                                                                                                                   \
  {                                                                                                                    \
    TEST_SET_CHECKPOINT();                                                                                             \
    ::rapid_cxx_test::test_outcome m_f(                                                                                \
      ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__, "TEST_CHECK(" #__VA_ARGS__ ")", ""); \
    if (not(__VA_ARGS__))                                                                                              \
    {                                                                                                                  \
      m_f.type = ::rapid_cxx_test::failure_type::check;                                                                \
    }                                                                                                                  \
    ::rapid_cxx_test::get_reporter().report(m_f);                                                                      \
  } while (false)
#

#define TEST_REQUIRE(...)                                 \
  do                                                      \
  {                                                       \
    TEST_SET_CHECKPOINT();                                \
    ::rapid_cxx_test::test_outcome m_f(                   \
      ::rapid_cxx_test::failure_type::none,               \
      __FILE__,                                           \
      TEST_FUNC_NAME(),                                   \
      __LINE__,                                           \
      "TEST_REQUIRE(" #__VA_ARGS__ ")",                   \
      "");                                                \
    if (not(__VA_ARGS__))                                 \
    {                                                     \
      m_f.type = ::rapid_cxx_test::failure_type::require; \
    }                                                     \
    ::rapid_cxx_test::get_reporter().report(m_f);         \
    if (m_f.type != ::rapid_cxx_test::failure_type::none) \
    {                                                     \
      return;                                             \
    }                                                     \
  } while (false)
#

#define TEST_ASSERT(...)                                                                                                \
  do                                                                                                                    \
  {                                                                                                                     \
    TEST_SET_CHECKPOINT();                                                                                              \
    ::rapid_cxx_test::test_outcome m_f(                                                                                 \
      ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__, "TEST_ASSERT(" #__VA_ARGS__ ")", ""); \
    if (not(__VA_ARGS__))                                                                                               \
    {                                                                                                                   \
      m_f.type = ::rapid_cxx_test::failure_type::assert;                                                                \
    }                                                                                                                   \
    ::rapid_cxx_test::get_reporter().report(m_f);                                                                       \
    if (m_f.type != ::rapid_cxx_test::failure_type::none)                                                               \
    {                                                                                                                   \
      std::abort();                                                                                                     \
    }                                                                                                                   \
  } while (false)
#

////////////////////////////////////////////////////////////////////////////////
//                    TEST_CHECK_NO_THROW / TEST_CHECK_THROW
////////////////////////////////////////////////////////////////////////////////
#ifndef TEST_HAS_NO_EXCEPTIONS

#  define TEST_CHECK_NO_THROW(...)                        \
    do                                                    \
    {                                                     \
      TEST_SET_CHECKPOINT();                              \
      ::rapid_cxx_test::test_outcome m_f(                 \
        ::rapid_cxx_test::failure_type::none,             \
        __FILE__,                                         \
        TEST_FUNC_NAME(),                                 \
        __LINE__,                                         \
        "TEST_CHECK_NO_THROW(" #__VA_ARGS__ ")",          \
        "");                                              \
      try                                                 \
      {                                                   \
        (static_cast<void>(__VA_ARGS__));                 \
      }                                                   \
      catch (...)                                         \
      {                                                   \
        m_f.type = ::rapid_cxx_test::failure_type::check; \
      }                                                   \
      ::rapid_cxx_test::get_reporter().report(m_f);       \
    } while (false)
#

#  define TEST_CHECK_THROW(Except, ...)                   \
    do                                                    \
    {                                                     \
      TEST_SET_CHECKPOINT();                              \
      ::rapid_cxx_test::test_outcome m_f(                 \
        ::rapid_cxx_test::failure_type::none,             \
        __FILE__,                                         \
        TEST_FUNC_NAME(),                                 \
        __LINE__,                                         \
        "TEST_CHECK_THROW(" #Except "," #__VA_ARGS__ ")", \
        "");                                              \
      try                                                 \
      {                                                   \
        (static_cast<void>(__VA_ARGS__));                 \
        m_f.type = ::rapid_cxx_test::failure_type::check; \
      }                                                   \
      catch (Except const&)                               \
      {}                                                  \
      ::rapid_cxx_test::get_reporter().report(m_f);       \
    } while (false)
#

#  define TEST_CHECK_THROW_RESULT(Except, Checker, ...)                       \
    do                                                                        \
    {                                                                         \
      TEST_SET_CHECKPOINT();                                                  \
      ::rapid_cxx_test::test_outcome m_f(                                     \
        ::rapid_cxx_test::failure_type::none,                                 \
        __FILE__,                                                             \
        TEST_FUNC_NAME(),                                                     \
        __LINE__,                                                             \
        "TEST_CHECK_THROW_RESULT(" #Except "," #Checker "," #__VA_ARGS__ ")", \
        "");                                                                  \
      try                                                                     \
      {                                                                       \
        (static_cast<void>(__VA_ARGS__));                                     \
        m_f.type = ::rapid_cxx_test::failure_type::check;                     \
      }                                                                       \
      catch (Except const& Caught)                                            \
      {                                                                       \
        Checker(Caught);                                                      \
      }                                                                       \
      ::rapid_cxx_test::get_reporter().report(m_f);                           \
    } while (false)
#

#else // TEST_HAS_NO_EXCEPTIONS

#  define TEST_CHECK_NO_THROW(...)                  \
    do                                              \
    {                                               \
      TEST_SET_CHECKPOINT();                        \
      ::rapid_cxx_test::test_outcome m_f(           \
        ::rapid_cxx_test::failure_type::none,       \
        __FILE__,                                   \
        TEST_FUNC_NAME(),                           \
        __LINE__,                                   \
        "TEST_CHECK_NO_THROW(" #__VA_ARGS__ ")",    \
        "");                                        \
      (static_cast<void>(__VA_ARGS__));             \
      ::rapid_cxx_test::get_reporter().report(m_f); \
    } while (false)
#

#  define TEST_CHECK_THROW(Except, ...)                 ((void) 0)
#  define TEST_CHECK_THROW_RESULT(Except, Checker, ...) ((void) 0)

#endif // !TEST_HAS_NO_EXCEPTIONS

////////////////////////////////////////////////////////////////////////////////
//                    TEST_REQUIRE_NO_THROW / TEST_REQUIRE_THROWs
////////////////////////////////////////////////////////////////////////////////
#ifndef TEST_HAS_NO_EXCEPTIONS

#  define TEST_REQUIRE_NO_THROW(...)                        \
    do                                                      \
    {                                                       \
      TEST_SET_CHECKPOINT();                                \
      ::rapid_cxx_test::test_outcome m_f(                   \
        ::rapid_cxx_test::failure_type::none,               \
        __FILE__,                                           \
        TEST_FUNC_NAME(),                                   \
        __LINE__,                                           \
        "TEST_REQUIRE_NO_THROW(" #__VA_ARGS__ ")",          \
        "");                                                \
      try                                                   \
      {                                                     \
        (static_cast<void>(__VA_ARGS__));                   \
      }                                                     \
      catch (...)                                           \
      {                                                     \
        m_f.type = ::rapid_cxx_test::failure_type::require; \
      }                                                     \
      ::rapid_cxx_test::get_reporter().report(m_f);         \
      if (m_f.type != ::rapid_cxx_test::failure_type::none) \
      {                                                     \
        return;                                             \
      }                                                     \
    } while (false)
#

#  define TEST_REQUIRE_THROW(Except, ...)                   \
    do                                                      \
    {                                                       \
      TEST_SET_CHECKPOINT();                                \
      ::rapid_cxx_test::test_outcome m_f(                   \
        ::rapid_cxx_test::failure_type::none,               \
        __FILE__,                                           \
        TEST_FUNC_NAME(),                                   \
        __LINE__,                                           \
        "TEST_REQUIRE_THROW(" #Except "," #__VA_ARGS__ ")", \
        "");                                                \
      try                                                   \
      {                                                     \
        (static_cast<void>(__VA_ARGS__));                   \
        m_f.type = ::rapid_cxx_test::failure_type::require; \
      }                                                     \
      catch (Except const&)                                 \
      {}                                                    \
      ::rapid_cxx_test::get_reporter().report(m_f);         \
      if (m_f.type != ::rapid_cxx_test::failure_type::none) \
      {                                                     \
        return;                                             \
      }                                                     \
    } while (false)
#

#else // TEST_HAS_NO_EXCEPTIONS

#  define TEST_REQUIRE_NO_THROW(...)                \
    do                                              \
    {                                               \
      TEST_SET_CHECKPOINT();                        \
      ::rapid_cxx_test::test_outcome m_f(           \
        ::rapid_cxx_test::failure_type::none,       \
        __FILE__,                                   \
        TEST_FUNC_NAME(),                           \
        __LINE__,                                   \
        "TEST_REQUIRE_NO_THROW(" #__VA_ARGS__ ")",  \
        "");                                        \
      (static_cast<void>(__VA_ARGS__));             \
      ::rapid_cxx_test::get_reporter().report(m_f); \
    } while (false)
#

#  define TEST_REQUIRE_THROW(Except, ...) ((void) 0)

#endif // !TEST_HAS_NO_EXCEPTIONS

////////////////////////////////////////////////////////////////////////////////
//                    TEST_ASSERT_NO_THROW / TEST_ASSERT_THROW
////////////////////////////////////////////////////////////////////////////////
#ifndef TEST_HAS_NO_EXCEPTIONS

#  define TEST_ASSERT_NO_THROW(...)                         \
    do                                                      \
    {                                                       \
      TEST_SET_CHECKPOINT();                                \
      ::rapid_cxx_test::test_outcome m_f(                   \
        ::rapid_cxx_test::failure_type::none,               \
        __FILE__,                                           \
        TEST_FUNC_NAME(),                                   \
        __LINE__,                                           \
        "TEST_ASSERT_NO_THROW(" #__VA_ARGS__ ")",           \
        "");                                                \
      try                                                   \
      {                                                     \
        (static_cast<void>(__VA_ARGS__));                   \
      }                                                     \
      catch (...)                                           \
      {                                                     \
        m_f.type = ::rapid_cxx_test::failure_type::assert;  \
      }                                                     \
      ::rapid_cxx_test::get_reporter().report(m_f);         \
      if (m_f.type != ::rapid_cxx_test::failure_type::none) \
      {                                                     \
        std::abort();                                       \
      }                                                     \
    } while (false)
#

#  define TEST_ASSERT_THROW(Except, ...)                    \
    do                                                      \
    {                                                       \
      TEST_SET_CHECKPOINT();                                \
      ::rapid_cxx_test::test_outcome m_f(                   \
        ::rapid_cxx_test::failure_type::none,               \
        __FILE__,                                           \
        TEST_FUNC_NAME(),                                   \
        __LINE__,                                           \
        "TEST_ASSERT_THROW(" #Except "," #__VA_ARGS__ ")",  \
        "");                                                \
      try                                                   \
      {                                                     \
        (static_cast<void>(__VA_ARGS__));                   \
        m_f.type = ::rapid_cxx_test::failure_type::assert;  \
      }                                                     \
      catch (Except const&)                                 \
      {}                                                    \
      ::rapid_cxx_test::get_reporter().report(m_f);         \
      if (m_f.type != ::rapid_cxx_test::failure_type::none) \
      {                                                     \
        std::abort();                                       \
      }                                                     \
    } while (false)
#

#else // TEST_HAS_NO_EXCEPTIONS

#  define TEST_ASSERT_NO_THROW(...)                 \
    do                                              \
    {                                               \
      TEST_SET_CHECKPOINT();                        \
      ::rapid_cxx_test::test_outcome m_f(           \
        ::rapid_cxx_test::failure_type::none,       \
        __FILE__,                                   \
        TEST_FUNC_NAME(),                           \
        __LINE__,                                   \
        "TEST_ASSERT_NO_THROW(" #__VA_ARGS__ ")",   \
        "");                                        \
      (static_cast<void>(__VA_ARGS__));             \
      ::rapid_cxx_test::get_reporter().report(m_f); \
    } while (false)
#

#  define TEST_ASSERT_THROW(Except, ...) ((void) 0)

#endif // !TEST_HAS_NO_EXCEPTIONS

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////

#define TEST_WARN_EQUAL_COLLECTIONS(...)                                         \
  do                                                                             \
  {                                                                              \
    TEST_SET_CHECKPOINT();                                                       \
    ::rapid_cxx_test::test_outcome m_f(                                          \
      ::rapid_cxx_test::failure_type::none,                                      \
      __FILE__,                                                                  \
      TEST_FUNC_NAME(),                                                          \
      __LINE__,                                                                  \
      "TEST_WARN_EQUAL_COLLECTIONS(" #__VA_ARGS__ ")",                           \
      "");                                                                       \
    if (not ::rapid_cxx_test::detail::check_equal_collections_impl(__VA_ARGS__)) \
    {                                                                            \
      m_f.type = ::rapid_cxx_test::failure_type::warn;                           \
    }                                                                            \
    ::rapid_cxx_test::get_reporter().report(m_f);                                \
  } while (false)
#

#define TEST_CHECK_EQUAL_COLLECTIONS(...)                                        \
  do                                                                             \
  {                                                                              \
    TEST_SET_CHECKPOINT();                                                       \
    ::rapid_cxx_test::test_outcome m_f(                                          \
      ::rapid_cxx_test::failure_type::none,                                      \
      __FILE__,                                                                  \
      TEST_FUNC_NAME(),                                                          \
      __LINE__,                                                                  \
      "TEST_CHECK_EQUAL_COLLECTIONS(" #__VA_ARGS__ ")",                          \
      "");                                                                       \
    if (not ::rapid_cxx_test::detail::check_equal_collections_impl(__VA_ARGS__)) \
    {                                                                            \
      m_f.type = ::rapid_cxx_test::failure_type::check;                          \
    }                                                                            \
    ::rapid_cxx_test::get_reporter().report(m_f);                                \
  } while (false)
#

#define TEST_REQUIRE_EQUAL_COLLECTIONS(...)                                      \
  do                                                                             \
  {                                                                              \
    TEST_SET_CHECKPOINT();                                                       \
    ::rapid_cxx_test::test_outcome m_f(                                          \
      ::rapid_cxx_test::failure_type::none,                                      \
      __FILE__,                                                                  \
      TEST_FUNC_NAME(),                                                          \
      __LINE__,                                                                  \
      "TEST_REQUIRE_EQUAL_COLLECTIONS(" #__VA_ARGS__ ")",                        \
      "");                                                                       \
    if (not ::rapid_cxx_test::detail::check_equal_collections_impl(__VA_ARGS__)) \
    {                                                                            \
      m_f.type = ::rapid_cxx_test::failure_type::require;                        \
    }                                                                            \
    ::rapid_cxx_test::get_reporter().report(m_f);                                \
    if (m_f.type != ::rapid_cxx_test::failure_type::none)                        \
    {                                                                            \
      return;                                                                    \
    }                                                                            \
  } while (false)
#

#define TEST_ASSERT_EQUAL_COLLECTIONS(...)                                       \
  do                                                                             \
  {                                                                              \
    TEST_SET_CHECKPOINT();                                                       \
    ::rapid_cxx_test::test_outcome m_f(                                          \
      ::rapid_cxx_test::failure_type::none,                                      \
      __FILE__,                                                                  \
      TEST_FUNC_NAME(),                                                          \
      __LINE__,                                                                  \
      "TEST_ASSERT_EQUAL_COLLECTIONS(" #__VA_ARGS__ ")",                         \
      "");                                                                       \
    if (not ::rapid_cxx_test::detail::check_equal_collections_impl(__VA_ARGS__)) \
    {                                                                            \
      m_f.type = ::rapid_cxx_test::failure_type::assert;                         \
    }                                                                            \
    ::rapid_cxx_test::get_reporter().report(m_f);                                \
    if (m_f.type != ::rapid_cxx_test::failure_type::none)                        \
    {                                                                            \
      ::std::abort();                                                            \
    }                                                                            \
  } while (false)
#

namespace rapid_cxx_test
{
typedef void (*invoker_t)();

////////////////////////////////////////////////////////////////////////////
struct test_case
{
  test_case()
      : file("")
      , func("")
      , line(0)
      , invoke(NULL)
  {}

  test_case(const char* file1, const char* func1, std::size_t line1, invoker_t invoke1)
      : file(file1)
      , func(func1)
      , line(line1)
      , invoke(invoke1)
  {}

  const char* file;
  const char* func;
  std::size_t line;
  invoker_t invoke;
};

////////////////////////////////////////////////////////////////////////////
struct failure_type
{
  enum enum_type
  {
    none,
    unsupported,
    warn,
    check,
    require,
    assert,
    uncaught_exception
  };
};

typedef failure_type::enum_type failure_type_t;

////////////////////////////////////////////////////////////////////////////
struct test_outcome
{
  test_outcome()
      : type(failure_type::none)
      , file("")
      , func("")
      , line(0)
      , expression("")
      , message("")
  {}

  test_outcome(failure_type_t type1,
               const char* file1,
               const char* func1,
               std::size_t line1,
               const char* expression1,
               const char* message1)
      : type(type1)
      , file(file1)
      , func(func1)
      , line(line1)
      , expression(expression1)
      , message(message1)
  {
    trim_func_string();
  }

  failure_type_t type;
  const char* file;
  const char* func;
  std::size_t line;
  const char* expression;
  const char* message;

private:
  void trim_file_string()
  {
    const char* f_start    = file;
    const char* prev_start = f_start;
    const char* last_start = f_start;
    char last;
    while ((last = *f_start) != '\0')
    {
      ++f_start;
      if (last == '/' && *f_start)
      {
        prev_start = last_start;
        last_start = f_start;
      }
    }
    file = prev_start;
  }
  void trim_func_string()
  {
    const char* void_loc = ::strstr(func, "void ");
    if (void_loc == func)
    {
      func += strlen("void ");
    }
    const char* namespace_loc = ::strstr(func, "::");
    if (namespace_loc)
    {
      func = namespace_loc + 2;
    }
  }
};

////////////////////////////////////////////////////////////////////////////
struct checkpoint
{
  const char* file;
  const char* func;
  std::size_t line;
};

namespace detail
{
inline checkpoint& global_checkpoint()
{
  static checkpoint cp = {"", "", 0};
  return cp;
}
} // namespace detail

////////////////////////////////////////////////////////////////////////////
inline void set_checkpoint(const char* file, const char* func, std::size_t line)
{
  checkpoint& cp = detail::global_checkpoint();
  cp.file        = file;
  cp.func        = func;
  cp.line        = line;
}

////////////////////////////////////////////////////////////////////////////
inline checkpoint const& get_checkpoint()
{
  return detail::global_checkpoint();
}

////////////////////////////////////////////////////////////////////////////
class test_suite
{
public:
  typedef test_case const* iterator;
  typedef iterator const_iterator;

public:
  test_suite(const char* xname)
      : m_name(xname)
      , m_tests()
      , m_size(0)
  {
    assert(xname);
  }

public:
  const char* name() const
  {
    return m_name;
  }

  std::size_t size() const
  {
    return m_size;
  }

  test_case const& operator[](std::size_t i) const
  {
    assert(i < m_size);
    return m_tests[i];
  }

  const_iterator begin() const
  {
    return m_tests;
  }

  const_iterator end() const
  {
    return m_tests + m_size;
  }

public:
  std::size_t register_test(test_case tc)
  {
    static std::size_t test_case_max = sizeof(m_tests) / sizeof(test_case);
    assert(m_size < test_case_max);
    m_tests[m_size] = tc;
    return m_size++;
  }

private:
  test_suite(test_suite const&);
  test_suite& operator=(test_suite const&);

private:
  const char* m_name;
  // Since fast compile times in a priority, we use simple containers
  // with hard limits.
  test_case m_tests[256];
  std::size_t m_size;
};

////////////////////////////////////////////////////////////////////////////
class registrar
{
public:
  registrar(test_suite& st, test_case tc)
  {
    st.register_test(tc);
  }
};

////////////////////////////////////////////////////////////////////////////
class test_reporter
{
public:
  test_reporter()
      : m_testcases(0)
      , m_testcase_failures(0)
      , m_unsupported(0)
      , m_assertions(0)
      , m_warning_failures(0)
      , m_check_failures(0)
      , m_require_failures(0)
      , m_uncaught_exceptions(0)
      , m_failure()
  {}

  void test_case_begin()
  {
    ++m_testcases;
    clear_failure();
  }

  void test_case_end()
  {
    if (m_failure.type != failure_type::none && m_failure.type != failure_type::unsupported)
    {
      ++m_testcase_failures;
    }
  }

#if defined(__GNUC__)
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wswitch-default"
#endif
  // Each assertion and failure is reported through this function.
  void report(test_outcome o)
  {
    ++m_assertions;
    switch (o.type)
    {
      case failure_type::none:
        break;
      case failure_type::unsupported:
        ++m_unsupported;
        m_failure = o;
        break;
      case failure_type::warn:
        ++m_warning_failures;
        report_error(o);
        break;
      case failure_type::check:
        ++m_check_failures;
        report_error(o);
        m_failure = o;
        break;
      case failure_type::require:
        ++m_require_failures;
        report_error(o);
        m_failure = o;
        break;
      case failure_type::assert:
        report_error(o);
        break;
      case failure_type::uncaught_exception:
        ++m_uncaught_exceptions;
        std::fprintf(
          stderr,
          "Test case FAILED with uncaught exception:\n"
          "    last checkpoint near %s::%lu %s\n\n",
          o.file,
          o.line,
          o.func);
        m_failure = o;
        break;
    }
  }
#if defined(__GNUC__)
#  pragma GCC diagnostic pop
#endif

  test_outcome current_failure() const
  {
    return m_failure;
  }

  void clear_failure()
  {
    m_failure.type       = failure_type::none;
    m_failure.file       = "";
    m_failure.func       = "";
    m_failure.line       = 0;
    m_failure.expression = "";
    m_failure.message    = "";
  }

  std::size_t test_case_count() const
  {
    return m_testcases;
  }

  std::size_t test_case_failure_count() const
  {
    return m_testcase_failures;
  }

  std::size_t unsupported_count() const
  {
    return m_unsupported;
  }

  std::size_t assertion_count() const
  {
    return m_assertions;
  }

  std::size_t warning_failure_count() const
  {
    return m_warning_failures;
  }

  std::size_t check_failure_count() const
  {
    return m_check_failures;
  }

  std::size_t require_failure_count() const
  {
    return m_require_failures;
  }

  std::size_t failure_count() const
  {
    return m_check_failures + m_require_failures + m_uncaught_exceptions;
  }

  // Print a summary of what was run and the outcome.
  void print_summary(const char* suitename) const
  {
    FILE* out                 = failure_count() ? stderr : stdout;
    std::size_t testcases_run = m_testcases - m_unsupported;
    std::fprintf(out, "Summary for testsuite %s:\n", suitename);
    std::fprintf(out, "    %lu of %lu test cases passed.\n", testcases_run - m_testcase_failures, testcases_run);
    std::fprintf(out,
                 "    %lu of %lu assertions passed.\n",
                 m_assertions - (m_warning_failures + m_check_failures + m_require_failures),
                 m_assertions);
    std::fprintf(out, "    %lu unsupported test case%s.\n", m_unsupported, (m_unsupported != 1 ? "s" : ""));
  }

private:
  test_reporter(test_reporter const&);
  test_reporter const& operator=(test_reporter const&);

  void report_error(test_outcome o) const
  {
    std::fprintf(
      stderr,
      "In %s:%lu Assertion %s failed.\n    in file: %s\n    %s\n",
      o.func,
      o.line,
      o.expression,
      o.file,
      o.message ? o.message : "");
  }

private:
  // counts of testcases, failed testcases, and unsupported testcases.
  std::size_t m_testcases;
  std::size_t m_testcase_failures;
  std::size_t m_unsupported;

  // counts of assertions and assertion failures.
  std::size_t m_assertions;
  std::size_t m_warning_failures;
  std::size_t m_check_failures;
  std::size_t m_require_failures;
  std::size_t m_uncaught_exceptions;

  // The last failure. This is cleared between testcases.
  test_outcome m_failure;
};

////////////////////////////////////////////////////////////////////////////
inline test_reporter& get_reporter()
{
  static test_reporter o;
  return o;
}

////////////////////////////////////////////////////////////////////////////
class test_runner
{
public:
  test_runner(test_suite& ts)
      : m_ts(ts)
  {}

public:
  int run()
  {
    // for each testcase
    for (test_suite::const_iterator b = m_ts.begin(), e = m_ts.end(); b != e; ++b)
    {
      test_case const& tc = *b;
      set_checkpoint(tc.file, tc.func, tc.line);
      get_reporter().test_case_begin();
#ifndef TEST_HAS_NO_EXCEPTIONS
      try
      {
#endif
        tc.invoke();
#ifndef TEST_HAS_NO_EXCEPTIONS
      }
      catch (...)
      {
        test_outcome o;
        o.type       = failure_type::uncaught_exception;
        o.file       = get_checkpoint().file;
        o.func       = get_checkpoint().func;
        o.line       = get_checkpoint().line;
        o.expression = "";
        o.message    = "";
        get_reporter().report(o);
      }
#endif
      get_reporter().test_case_end();
    }
    auto exit_code = get_reporter().failure_count() ? EXIT_FAILURE : EXIT_SUCCESS;
    if (exit_code == EXIT_FAILURE || get_reporter().unsupported_count())
    {
      get_reporter().print_summary(m_ts.name());
    }
    return exit_code;
  }

private:
  test_runner(test_runner const&);
  test_runner operator=(test_runner const&);

  test_suite& m_ts;
};

namespace detail
{
template <class Iter1, class Iter2>
bool check_equal_collections_impl(Iter1 start1, Iter1 const end1, Iter2 start2, Iter2 const end2)
{
  while (start1 != end1 && start2 != end2)
  {
    if (*start1 != *start2)
    {
      return false;
    }
    ++start1;
    ++start2;
  }
  return (start1 == end1 && start2 == end2);
}
} // namespace detail

} // namespace rapid_cxx_test

#if defined(__GNUC__)
#  pragma GCC diagnostic pop
#endif

#endif /* RAPID_CXX_TEST_H */
