#ifndef PASTIS_ASSERT_HPP
#define PASTIS_ASSERT_HPP

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

class AssertError
{
 private:
  const std::string m_file;
  const int m_line;
  const std::string m_function;
  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'
       << "*** " << rang::fgB::red << assert_error.m_message << rang::fg::reset
       << rang::style::reset << '\n';

    return os;
  }

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

  ~AssertError() = default;
};

#pragma GCC diagnostic ignored "-Wattributes"
inline bool
__attribute__((analyzer_noreturn))
_pastis_assert(const bool& assert)
{
  return assert;
}
#pragma GCC diagnostic pop

#ifdef NDEBUG

// Useless test is there to check syntax even in optimized mode. Costs nothing.
#define Assert(assertion)                       \
  if (not _pastis_assert(assertion)) {}

#else // NDEBUG

#define Assert(assertion)                       \
  if (not _pastis_assert(assertion)) {          \
    throw AssertError(__FILE__,                 \
                      __LINE__,                 \
                      __PRETTY_FUNCTION__,      \
                      (#assertion));            \
  }

#endif // NDEBUG

// stores 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 // PASTIS_ASSERT_HPP