// Broken - Lightweight unit testing for C++
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org>

#ifndef BROKEN_H_INCLUDED
#define BROKEN_H_INCLUDED

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utility>

// Hide everything when using Doxygen. Ideally this can be protected by a macro,
// but there is not globally and widely used one across multiple projects.

//! \cond

// Broken - API
// ============

namespace BrokenAPI {

//! Entry point of a unit test defined by `UNIT` macro.
typedef void (*Entry)(void);

enum Flags : unsigned {
  kFlagFinished = 0x1
};

struct Unit;

bool has_arg(const char* name) noexcept;

//! Register a new unit test (called automatically by `AutoUnit` and `UNIT`).
void add_unit(Unit* unit) noexcept;

//! Set output file to a `file`.
void set_output_file(FILE* file) noexcept;

//! Initialize `Broken` framework.
//!
//! Returns `true` if `run()` should be called.
int run(int argc, const char* argv[], Entry on_before_run = nullptr, Entry on_after_run = nullptr);

//! Log message, adds automatically new line if not present.
void info(const char* fmt, ...) noexcept;

//! Called on `EXPECT()` failure.
void fail(const char* file, int line, const char* expression, const char* fmt, ...) noexcept;

//! Test defined by `UNIT` macro.
struct Unit {
  Entry entry;
  const char* name;
  int priority;
  unsigned flags;
  Unit* next;
};

//! Automatic unit registration by using static initialization.
class AutoUnit : public Unit {
public:
  inline AutoUnit(Entry entry_, const char* name_, int priority_ = 0, int dummy_ = 0) noexcept {
    // Not used, only to trick `UNIT()` macro.
    (void)dummy_;

    this->entry = entry_;
    this->name = name_;
    this->priority = priority_;
    this->flags = 0;
    this->next = nullptr;
    add_unit(this);
  }
};

class Failure {
public:
  const char* _file = nullptr;
  const char* _expression = nullptr;
  int _line = 0;
  bool _handled = false;

  inline Failure(const char* file, int line, const char* expression) noexcept
    : _file(file),
      _expression(expression),
      _line(line) {}

  inline ~Failure() noexcept {
    if (!_handled)
      fail(_file, _line, _expression, nullptr);
  }

  template<typename... Args>
  inline void message(const char* fmt, Args&&... args) noexcept {
    fail(_file, _line, _expression, fmt, std::forward<Args>(args)...);
    _handled = true;
  }
};

template<typename Result>
static inline bool check(Result&& result) noexcept { return !!result; }

template<typename LHS, typename RHS>
static inline bool check_eq(LHS&& lhs, RHS&& rhs) noexcept { return lhs == rhs; }

template<typename LHS, typename RHS>
static inline bool check_ne(LHS&& lhs, RHS&& rhs) noexcept { return lhs != rhs; }

template<typename LHS, typename RHS>
static inline bool check_gt(LHS&& lhs, RHS&& rhs) noexcept { return lhs >  rhs; }

template<typename LHS, typename RHS>
static inline bool check_ge(LHS&& lhs, RHS&& rhs) noexcept { return lhs >= rhs; }

template<typename LHS, typename RHS>
static inline bool check_lt(LHS&& lhs, RHS&& rhs) noexcept { return lhs <  rhs; }

template<typename LHS, typename RHS>
static inline bool check_le(LHS&& lhs, RHS&& rhs) noexcept { return lhs <= rhs; }

template<typename Result>
static inline bool check_true(Result&& result) noexcept { return !!result; }

template<typename Result>
static inline bool check_false(Result&& result) noexcept { return !result; }

template<typename Result>
static inline bool check_null(Result&& result) noexcept { return result == nullptr; }

template<typename Result>
static inline bool check_not_null(Result&& result) noexcept { return result != nullptr; }

} // {BrokenAPI}

// Broken - Macros
// ===============

//! Internal macro used by `UNIT()`.
#define BROKEN_UNIT_INTERNAL(NAME, PRIORITY) \
  static void unit_##NAME##_entry(void); \
  static ::BrokenAPI::AutoUnit unit_##NAME##_autoinit(unit_##NAME##_entry, #NAME, PRIORITY); \
  static void unit_##NAME##_entry(void)

//! \def UNIT(NAME [, PRIORITY])
//!
//! Define a unit test with an optional priority.
//!
//! `NAME` can only contain ASCII characters, numbers and underscore. It has the same rules as identifiers in C and C++.
//!
//! `PRIORITY` specifies the order in which unit tests are run. Lesses value increases the priority. At the moment all
//!units are first sorted by priority and then by name - this makes the run always deterministic.
#define UNIT(NAME, ...) BROKEN_UNIT_INTERNAL(NAME, __VA_ARGS__ + 0)

//! #define INFO(FORMAT [, ...])
//!
//! Informative message printed to `stdout`.
#define INFO(...) ::BrokenAPI::info(__VA_ARGS__)

#define BROKEN_EXPECT_INTERNAL(file, line, expression, result) \
  for (bool _test_internal_result = (result); !_test_internal_result; _test_internal_result = true) \
    ::BrokenAPI::Failure(file, line, expression)

#define EXPECT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT(" #__VA_ARGS__ ")", !!(__VA_ARGS__))
#define EXPECT_EQ(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_EQ(" #__VA_ARGS__ ")", ::BrokenAPI::check_eq(__VA_ARGS__))
#define EXPECT_NE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NE(" #__VA_ARGS__ ")", ::BrokenAPI::check_ne(__VA_ARGS__))
#define EXPECT_GT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_GT(" #__VA_ARGS__ ")", ::BrokenAPI::check_gt(__VA_ARGS__))
#define EXPECT_GE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_GE(" #__VA_ARGS__ ")", ::BrokenAPI::check_ge(__VA_ARGS__))
#define EXPECT_LT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_LT(" #__VA_ARGS__ ")", ::BrokenAPI::check_lt(__VA_ARGS__))
#define EXPECT_LE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_LE(" #__VA_ARGS__ ")", ::BrokenAPI::check_le(__VA_ARGS__))
#define EXPECT_TRUE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_TRUE(" #__VA_ARGS__ ")", ::BrokenAPI::check_true(__VA_ARGS__))
#define EXPECT_FALSE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_FALSE(" #__VA_ARGS__ ")", ::BrokenAPI::check_false(__VA_ARGS__))
#define EXPECT_NULL(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NULL(" #__VA_ARGS__ ")", ::BrokenAPI::check_null(__VA_ARGS__))
#define EXPECT_NOT_NULL(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NOT_NULL(" #__VA_ARGS__ ")", ::BrokenAPI::check_not_null(__VA_ARGS__))

//! \endcond

#endif // BROKEN_H_INCLUDED
