// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD

/**
 * Diagnostic messages.
 */

#ifndef BFS_DIAG_H
#define BFS_DIAG_H

#include "bfs.h"
#include "bfstd.h"

#include <stdarg.h>

/**
 * Wrap a diagnostic format string so it looks like
 *
 *     bfs: func@src/file.c:0: Message
 */
// Use (format) ? "..." : "" so the format string is required
#define BFS_DIAG_FORMAT_(format) \
	((format) ? "%s: %s@%s:%d: " format "%s" : "")

/**
 * Add arguments to match a BFS_DIAG_FORMAT string.
 */
#define BFS_DIAG_ARGS_(...) \
	xgetprogname(), __func__, __FILE__, __LINE__, __VA_ARGS__ __VA_OPT__(,) "\n"

/**
 * Print a low-level diagnostic message to standard error.
 */
[[_printf(1, 2)]]
void bfs_diagf(const char *format, ...);

/**
 * Unconditional diagnostic message.
 */
#define bfs_diag(format, ...) \
	bfs_diagf(BFS_DIAG_FORMAT_(format), BFS_DIAG_ARGS_(__VA_ARGS__))

/**
 * Print a diagnostic message including the last error.
 */
#define bfs_ediag(format, ...) \
	BFS_VA_IF(format) \
		(bfs_diag(format ": %s", __VA_ARGS__ __VA_OPT__(,) errstr())) \
		(bfs_diag("%s", errstr()))

/**
 * Print a message to standard error and abort.
 */
[[_cold]]
[[_printf(1, 2)]]
[[_noreturn]]
void bfs_abortf(const char *format, ...);

/**
 * Unconditional abort with a message.
 */
#define bfs_abort(format, ...) \
	bfs_abortf(BFS_DIAG_FORMAT_(format), BFS_DIAG_ARGS_(__VA_ARGS__))

/**
 * Abort with a message including the last error.
 */
#define bfs_eabort(format, ...) \
	BFS_VA_IF(format) \
		(bfs_abort(format ": %s", __VA_ARGS__ __VA_OPT__(,) errstr())) \
		(bfs_abort("%s", errstr()))

/**
 * Abort in debug builds; no-op in release builds.
 */
#ifdef NDEBUG
#  define bfs_bug(...) ((void)0)
#  define bfs_ebug(...) ((void)0)
#else
#  define bfs_bug bfs_abort
#  define bfs_ebug bfs_eabort
#endif

/**
 * Unconditional assert.
 */
#define bfs_verify(cond, ...) \
	((cond) ? (void)0 : BFS_VA_IF(__VA_ARGS__) \
		(bfs_abort(__VA_ARGS__)) \
		(bfs_abort("Assertion failed: `%s`", #cond)))

/**
 * Unconditional assert, including the last error.
 */
#define bfs_everify(cond, ...) \
	((cond) ? (void)0 : BFS_VA_IF(__VA_ARGS__) \
		(bfs_eabort(__VA_ARGS__)) \
		(bfs_eabort("Assertion failed: `%s`", #cond)))

/**
 * Assert in debug builds; no-op in release builds.
 */
#ifdef NDEBUG
#  define bfs_assert(...) ((void)0)
#  define bfs_eassert(...) ((void)0)
#else
#  define bfs_assert bfs_verify
#  define bfs_eassert bfs_everify
#endif

struct bfs_ctx;
struct bfs_expr;

/**
 * Various debugging flags.
 */
enum debug_flags {
	/** Print cost estimates. */
	DEBUG_COST   = 1 << 0,
	/** Print executed command details. */
	DEBUG_EXEC   = 1 << 1,
	/** Print optimization details. */
	DEBUG_OPT    = 1 << 2,
	/** Print rate information. */
	DEBUG_RATES  = 1 << 3,
	/** Trace the filesystem traversal. */
	DEBUG_SEARCH = 1 << 4,
	/** Trace all stat() calls. */
	DEBUG_STAT   = 1 << 5,
	/** Print the parse tree. */
	DEBUG_TREE   = 1 << 6,
	/** All debug flags. */
	DEBUG_ALL    = (1 << 7) - 1,
};

/**
 * Convert a debug flag to a string.
 */
const char *debug_flag_name(enum debug_flags flag);

/**
 * Like perror(), but decorated like bfs_error().
 */
[[_cold]]
void bfs_perror(const struct bfs_ctx *ctx, const char *str);

/**
 * Shorthand for printing error messages.
 */
[[_cold]]
[[_printf(2, 3)]]
void bfs_error(const struct bfs_ctx *ctx, const char *format, ...);

/**
 * Shorthand for printing warning messages.
 *
 * @return Whether a warning was printed.
 */
[[_cold]]
[[_printf(2, 3)]]
bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...);

/**
 * Shorthand for printing debug messages.
 *
 * @return Whether a debug message was printed.
 */
[[_cold]]
[[_printf(3, 4)]]
bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, ...);

/**
 * bfs_error() variant that takes a va_list.
 */
[[_cold]]
[[_printf(2, 0)]]
void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args);

/**
 * bfs_warning() variant that takes a va_list.
 */
[[_cold]]
[[_printf(2, 0)]]
bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args);

/**
 * bfs_debug() variant that takes a va_list.
 */
[[_cold]]
[[_printf(3, 0)]]
bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args);

/**
 * Print the error message prefix.
 */
[[_cold]]
void bfs_error_prefix(const struct bfs_ctx *ctx);

/**
 * Print the warning message prefix.
 */
[[_cold]]
bool bfs_warning_prefix(const struct bfs_ctx *ctx);

/**
 * Print the debug message prefix.
 */
[[_cold]]
bool bfs_debug_prefix(const struct bfs_ctx *ctx, enum debug_flags flag);

/**
 * Highlight parts of the command line in an error message.
 */
[[_cold]]
void bfs_argv_error(const struct bfs_ctx *ctx, const bool args[]);

/**
 * Highlight parts of an expression in an error message.
 */
[[_cold]]
void bfs_expr_error(const struct bfs_ctx *ctx, const struct bfs_expr *expr);

/**
 * Highlight parts of the command line in a warning message.
 */
[[_cold]]
bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool args[]);

/**
 * Highlight parts of an expression in a warning message.
 */
[[_cold]]
bool bfs_expr_warning(const struct bfs_ctx *ctx, const struct bfs_expr *expr);

#endif // BFS_DIAG_H
