#ifndef PUGS_ASSERT_HPP
#define PUGS_ASSERT_HPP

#include <PugsMacros.hpp>

#include <iostream>
#include <rang.hpp>
#include <string>

class AssertError
{
 private:
  const std::string m_file;
  const int m_line;
  const std::string m_function;
  const std::string m_test;
  const std::string m_message;

 public:
  friend std::ostream&
  operator<<(std::ostream& os, const AssertError& assert_error)
  {
    os << '\n'
       << rang::style::bold << "---------- Assertion error -----------\n"
       << " at " << assert_error.m_file << ':' << assert_error.m_line << '\n'
       << " in " << assert_error.m_function << '\n'
       << " assertion (" << rang::fgB::red << assert_error.m_test << rang::fg::reset << ") failed!\n";
    if (not assert_error.m_message.empty()) {
      os << ' ' << rang::fgB::yellow << assert_error.m_message << rang::fg::reset << '\n';
    }
    os << "--------------------------------------" << rang::style::reset << '\n';

    return os;
  }

  AssertError(const AssertError&) = default;
  AssertError(std::string file, int line, std::string function, std::string test, std::string message = "")
    : m_file(file), m_line(line), m_function(function), m_test(test), m_message(message)
  {
    ;
  }

  ~AssertError() = default;
};

PRAGMA_DIAGNOSTIC_IGNORED_WATTRIBUTES
inline bool __attribute__((analyzer_noreturn)) _pugs_assert(const bool& assert)
{
  return assert;
}
PRAGMA_DIAGNOSTIC_POP

#ifdef NDEBUG

// Useless test is there to check syntax even in optimized mode. Costs nothing.
#define Assert(assertion, ...)                                            \
  if (not _pugs_assert(assertion)) {                                      \
    using vargs_t = decltype(std::make_tuple(__VA_ARGS__));               \
    static_assert(std::tuple_size_v<vargs_t> <= 1, "too many arguments"); \
  }

#else   // NDEBUG

#define Assert(assertion, ...)                                                              \
  if (not _pugs_assert(assertion)) {                                                        \
    using vargs_t = decltype(std::make_tuple(__VA_ARGS__));                                 \
    static_assert(std::tuple_size_v<vargs_t> <= 1, "too many arguments");                   \
    if constexpr (std::tuple_size_v<vargs_t> == 0) {                                        \
      throw AssertError(__FILE__, __LINE__, __PRETTY_FUNCTION__, #assertion);               \
    } else {                                                                                \
      throw AssertError(__FILE__, __LINE__, __PRETTY_FUNCTION__, #assertion, #__VA_ARGS__); \
    }                                                                                       \
  }

#endif   // NDEBUG

// store the current state of Assert macro. This is useful for
// instance to noexcept management of Assert throws
#ifdef NDEBUG
#define NO_ASSERT true
#else   // NDEBUG
#define NO_ASSERT false
#endif   // NDEBUG

#endif   // PUGS_ASSERT_HPP