#pragma once

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//

#include "revng/Support/ExplicitSpecializations.h"

#ifdef __cplusplus
extern "C" {
#endif

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#ifndef __has_builtin
#define __has_builtin(x) 0
#endif

#ifndef __has_attribute
#define __has_attribute(x) 0
#endif

#if !__has_builtin(__builtin_assume)
#define __builtin_assume(x)
#endif

#if !__has_builtin(__builtin_unreachable) && !defined(__GNUC__)
#define __builtin_unreachable(x)
#endif

#if defined(__clang__)

#define SILENCE_ASSUME_HEADER      \
  _Pragma("clang diagnostic push") \
  _Pragma("clang diagnostic ignored \"-Wassume\"")

#define SILENCE_ASSUME_FOOTER _Pragma("clang diagnostic pop")

#else

#define SILENCE_ASSUME_HEADER
#define SILENCE_ASSUME_FOOTER

#endif

/// Same as __builtin_assume but (temporarily) suppresses warnings about ignored
/// side-effects
#define silent_assume(what)                    \
  do {                                         \
    SILENCE_ASSUME_HEADER                      \
    __builtin_assume(static_cast<bool>(what)); \
    SILENCE_ASSUME_FOOTER                      \
  } while (0)

// We support C++11, C99 (with GNU attributes) or C11
#ifdef __cplusplus

#define boolcast(what) static_cast<bool>(what)
#define noret [[noreturn]]

#else

#define boolcast(what) (bool) (what)

#if __STDC_VERSION__ >= 201112L
#include <stdnoreturn.h>
#define noret noreturn
#elif __has_attribute(noreturn) || defined(__GNUC__)
#define noret __attribute__((noreturn))
#else
#warning "Can't mark functions as noreturn"
#endif

#endif

noret void revng_assert_fail(const char *AssertionBody,
                             const char *Message,
                             const char *File,
                             unsigned Line);
noret void revng_check_fail(const char *CheckBody,
                            const char *Message,
                            const char *File,
                            unsigned Line);
noret void revng_do_abort(const char *Message, const char *File, unsigned Line);

#undef noret

/// Aborts program execution with a message, in release mode too.
///
/// Use this macro to ensure program termination in case of an unexpected
/// situation.
#define revng_abort_impl(message)                \
  do {                                           \
    revng_do_abort(message, __FILE__, __LINE__); \
  } while (0)

/// Asserts \a what or aborts with \a message, in release mode too.
///
/// Use this macro to ensure program termination in case of an unexpected
/// situation.
#define revng_check_impl(what, message)                     \
  do {                                                      \
    bool Condition = boolcast(what);                        \
    if (!Condition) {                                       \
      revng_check_fail(#what, message, __FILE__, __LINE__); \
    }                                                       \
    silent_assume(Condition);                               \
  } while (0)

#ifndef NDEBUG

/// Marks a program path as unreachable. In debug mode, aborts with \a message.
///
/// Use this macro to catch bugs during development an tell the compiler that a
/// certain situation will never happen at run-time, which might open up to new
/// optimization opportunities.
#define revng_unreachable_impl(message) revng_abort(message)

/// Asserts \a what or, in debug mode, aborts with \a message.
///
/// Use this macro to catch bugs during development an tell the compiler that a
/// certain situation will never happen at run-time, which might open up to new
/// optimization opportunities.
#define revng_assert_impl(what, message)                     \
  do {                                                       \
    bool Condition = boolcast(what);                         \
    if (!Condition) {                                        \
      revng_assert_fail(#what, message, __FILE__, __LINE__); \
    }                                                        \
    silent_assume(Condition);                                \
  } while (0)

#else

#define revng_unreachable_impl(message) __builtin_unreachable()

#define revng_assert_impl(what, message) \
  do {                                   \
    (void) sizeof((what));               \
    silent_assume(what);                 \
  } while (0)

#endif

#define COMMA_IF_INVOKED(...) ,
#define CONCAT_TOKENS(a, b) a##b
#define CONCAT2(a, b) CONCAT_TOKENS(a, b)
#define CONCAT3(a, b, c) CONCAT2(CONCAT2(a, b), c)
#define CONCAT4(a, b, c, d) CONCAT2(CONCAT3(a, b, c), d)
#define CONCAT5(a, b, c, d, e) CONCAT2(CONCAT4(a, b, c, d), e)
#define GET_10TH(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) _10

/// Check if the argument list contains a comma
#define HAS_COMMA(...) GET_10TH(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)

/// Check if the argument list is empty
///
/// This is a workaround to avoid using ##__VA_ARGS__ (which eats the previous
/// comma if empty), since it's a GNU extension, and to void using default
/// arguments, since we want this header to be functioncal in C files too.
#define IS_EMPTY(...)                                        \
  HAS_COMMA(CONCAT5(IS_EMPTY_,                               \
                    HAS_COMMA(__VA_ARGS__),                  \
                    HAS_COMMA(COMMA_IF_INVOKED __VA_ARGS__), \
                    HAS_COMMA(__VA_ARGS__()),                \
                    HAS_COMMA(COMMA_IF_INVOKED __VA_ARGS__())))
// It's empty if the first three conditions are false and the last is true
#define IS_EMPTY_0001 ,

#define REVNG_ABORT_0(...) revng_abort_impl(__VA_ARGS__)
#define REVNG_ABORT_1(...) revng_abort_impl(NULL)
#define revng_abort(...) \
  CONCAT2(REVNG_ABORT_, IS_EMPTY(__VA_ARGS__))(__VA_ARGS__)

#define REVNG_UNREACHABLE_0(...) revng_unreachable_impl(__VA_ARGS__)
#define REVNG_UNREACHABLE_1(...) revng_unreachable_impl(NULL)
#define revng_unreachable(...) \
  CONCAT2(REVNG_UNREACHABLE_, IS_EMPTY(__VA_ARGS__))(__VA_ARGS__)

#define MACRO_OVERLOAD_1_OR_2(_1, _2, NAME, ...) NAME

#define revng_assert_impl_nomsg(what) revng_assert_impl(what, NULL)
#define revng_assert(...)                        \
  MACRO_OVERLOAD_1_OR_2(__VA_ARGS__,             \
                        revng_assert_impl,       \
                        revng_assert_impl_nomsg) \
  (__VA_ARGS__)

#define revng_check_impl_nomsg(what) revng_check_impl(what, NULL)
#define revng_check(...)                                                       \
  MACRO_OVERLOAD_1_OR_2(__VA_ARGS__, revng_check_impl, revng_check_impl_nomsg) \
  (__VA_ARGS__)

#ifdef __cplusplus
}
#endif
