/**
 * @file Check.h
 * @author apio (cloudapio.eu)
 * @brief Always-enabled assertions.
 *
 * @copyright Copyright (c) 2022-2023, the Luna authors.
 *
 */

#pragma once
#include <luna/SourceLocation.h>

/**
 * @brief Called when a libluna assertion fails. Some targets already have implementations of this function, but
 * otherwise it must be implemented by the user.
 *
 * In the kernel, the implementation is in src/Log.cpp.
 * For POSIX systems, libluna provides an implementation in src/ImplPOSIX.cpp.
 *
 * @param location The source location at which the assertion failure occurred.
 * @param expr The expression or custom error message to display.
 * @return bool Unused, since the function never returns.
 */
[[noreturn]] extern bool __check_failed(SourceLocation location, const char* expr);

#ifndef STRINGIZE_VALUE_OF
#define STRINGIZE(x) #x
#define STRINGIZE_VALUE_OF(x) STRINGIZE(x)
#endif

/**
 * @brief Verify an expression is true, or crash at runtime with a custom error message.
 */
#define expect(expr, message)                                                                                          \
    do {                                                                                                               \
        if (!(expr)) [[unlikely]] { __check_failed(SourceLocation::current(), message); }                              \
    } while (0)

/**
 * @brief Verify an expression is true, or crash at runtime with a custom error message, indicating a different source
 * location (useful for reporting caller errors, see Result::release_value() for an example).
 */
#define expect_at(expr, location, message)                                                                             \
    do {                                                                                                               \
        if (!(expr)) [[unlikely]] { __check_failed(location, message); }                                               \
    } while (0)

/**
 * @brief Crash at runtime with a custom error message.
 */
#define fail(message) __check_failed(SourceLocation::current(), message)

/**
 * @brief Crash at runtime with a custom error message, indicating a different source location (useful for
 * reporting caller errors, see Result::release_value() for an example).
 */
#define fail_at(location, message) __check_failed(location, message)

/**
 * @brief Verify an expression is true, or crash at runtime.
 */
#define check(expr)                                                                                                    \
    do {                                                                                                               \
        if (!(expr)) [[unlikely]] { __check_failed(SourceLocation::current(), #expr); }                                \
    } while (0)

/**
 * @brief Verify an expression is true, or crash at runtime, indicating a different source location (useful for
 * reporting caller errors, see Result::release_value() for an example).
 */
#define check_at(expr, location)                                                                                       \
    do {                                                                                                               \
        if (!(expr)) [[unlikely]] { __check_failed(location, #expr); }                                                 \
    } while (0)

/**
 * @brief Mark a code section as unreachable, which will crash if reached.
 *
 * Often it may be more performant to use the compiler's __builtin_unreachable() for this purpose (when the code is
 * clearly unreachable but the compiler cannot figure it out on its own, see the implementation of _exit() in libc), if
 * there is just no possible way for execution flow to reach this code segment, but the compiler builtin results in
 * undefined behavior if reached. Therefore, if a bug may cause this unreachable code to be reached, you may prefer to
 * use this macro as it has well-defined behavior (crashes at runtime).
 */
#define unreachable() __check_failed(SourceLocation::current(), "Reached unreachable code")

/**
 * @brief Mark a code section as unimplemented, which will crash if reached.
 */
#define todo() __check_failed(SourceLocation::current(), "Reached a TODO!")