/*
 * Copyright (C) by Argonne National Laboratory
 *     See COPYRIGHT in top-level directory
 */

#ifndef MPIR_ASSERT_H_INCLUDED
#define MPIR_ASSERT_H_INCLUDED

#include "mpir_type_defs.h"

/* modern versions of clang support lots of C11 features */
#if defined(__has_extension)
#if __has_extension(c_generic_selections)
#define HAVE_C11__GENERIC 1
#endif
#endif

/* prototypes for assertion implementation helpers */
int MPIR_Assert_fail(const char *cond, const char *file_name, int line_num);
int MPIR_Assert_fail_fmt(const char *cond, const char *file_name, int line_num, const char *fmt,
                         ...);

/* Instead of "MPIR_Assert(0)", use "MPIR_Assert_error(error_msg)" */
#define MPIR_Assert_error(errmsg) \
    MPIR_Assert_fail(errmsg, __FILE__, __LINE__)

/*
 * MPIR_Assert()
 *
 * Similar to assert() except that it performs an MPID_Abort() when the
 * assertion fails.  Also, for Windows, it doesn't popup a
 * message box on a remote machine.
 */
#if (defined(__COVERITY__) || defined(__KLOCWORK__))
#include <assert.h>
#define MPIR_Assert(a_) assert(a_);
#elif (!defined(NDEBUG) && defined(HAVE_ERROR_CHECKING))
#define MPIR_AssertDeclValue(_a,_b) _a = _b
#define MPIR_Assert(a_)                             \
    do {                                               \
        if (unlikely(!(a_))) {                         \
            MPIR_Assert_fail(#a_, __FILE__, __LINE__); \
        }                                              \
    } while (0)
#else
#define MPIR_Assert(a_)
/* Empty decls not allowed in C */
#define MPIR_AssertDeclValue(_a,_b) _a ATTRIBUTE((unused)) = _b
#endif

/*
 * MPIR_Assertp()
 *
 * Similar to MPIR_Assert() except that these assertions persist regardless of
 * NDEBUG or HAVE_ERROR_CHECKING.  MPIR_Assertp() may
 * be used for error checking in prototype code, although it should be
 * converted real error checking and reporting once the
 * prototype becomes part of the official and supported code base.
 */
#define MPIR_Assertp(a_)                                             \
    do {                                                             \
        if (unlikely(!(a_))) {                                       \
            MPIR_Assert_fail(#a_, __FILE__, __LINE__);               \
        }                                                            \
    } while (0)

/* Define the MPIR_Assert_fmt_msg macro.  This macro takes two arguments.  The
 * first is the condition to assert.  The second is a parenthesized list of
 * arguments suitable for passing directly to printf that will yield a relevant
 * error message.  The macro will first evaluate the condition.  If it evaluates
 * to false the macro will take four steps:
 *
 * 1) It will emit an "Assertion failed..." message in the valgrind output with
 *    a backtrace, if valgrind client requests are available and the process is
 *    running under valgrind.  It will also evaluate and print the supplied
 *    message.
 * 2) It will emit an "Assertion failed..." message via MPL_internal_error_printf.
 *    The supplied error message will also be evaluated and printed.
 * 3) It will similarly emit the assertion failure and caller supplied messages
 *    to the debug log, if enabled, via MPL_DBG_MSG_FMT.
 * 4) It will invoke MPID_Abort, just like the other MPIR_Assert* macros.
 *
 * If the compiler doesn't support (...)/__VA_ARGS__ in macros then the user
 * message will not be evaluated or printed.  If NDEBUG is defined or
 * HAVE_ERROR_CHECKING is undefined, this macro will expand to nothing, just
 * like MPIR_Assert.
 *
 * Example usage:
 *
 * MPIR_Assert_fmg_msg(foo > bar,("foo is larger than bar: foo=%d bar=%d",foo,bar));
 */
#if (!defined(NDEBUG) && defined(HAVE_ERROR_CHECKING))
#if defined(HAVE_MACRO_VA_ARGS)

/* newlines are added internally by the impl function, callers do not need to include them */
#define MPIR_Assert_fmt_msg(cond_,fmt_arg_parens_)                         \
    do {                                                                       \
        if (unlikely(!(cond_))) {                                              \
            MPIR_Assert_fail_fmt(#cond_, __FILE__, __LINE__,                   \
                                 fmt_msg_expand_ fmt_arg_parens_); \
        }                                                                      \
    } while (0)
/* helper to just expand the parens arg inline */
#define fmt_msg_expand_(...) __VA_ARGS__

#else /* defined(HAVE_MACRO_VA_ARGS) */

#define MPIR_Assert_fmt_msg(cond_,fmt_arg_parens_)                                                   \
    do {                                                                                                 \
        if (unlikely(!(cond_))) {                                                                        \
            MPIR_Assert_fail_fmt(#cond_, __FILE__, __LINE__,                                             \
                                 "%s", "macro __VA_ARGS__ not supported, unable to print user message"); \
        }                                                                                                \
    } while (0)

#endif
#else /* !defined(NDEBUG) && defined(HAVE_ERROR_CHECKING) */
#define MPIR_Assert_fmt_msg(cond_,fmt_arg_parens_)
#endif

#ifdef HAVE_C11__STATIC_ASSERT
#define MPIR_Static_assert(cond_,msg_) _Static_assert(cond_,msg_)
#endif
/* fallthrough to a run-time assertion */
#ifndef MPIR_Static_assert
#define MPIR_Static_assert(cond_,msg_) MPIR_Assert_fmt_msg((cond_), ("%s", (msg_)))
#endif

#endif /* MPIR_ASSERT_H_INCLUDED */
