/** Copyright © Charliecloud contributors.

    Logging, some of which also exits with error if a value is not as expected.

    This interface provides eight (8) assert-like macros with cryptic
    three-letter names (i.e., optimized for brevity). These verify the
    truth/falseness of their argument, exiting an error message including
    source file and line number if not. They are macros because we want the
    file and line number of the calling location and C has no stack
    introspection.

    The three letters each encode a binary “argument”:

    1. Assert that the argument is true (`T`) or false (`Z` for zero).

       We use `Z` instead of `F` (for false) because this is usually used to
       error-check standard library functions or syscalls that return zero to
       indicate success, e.g. symlink(2), and I thought `F` might confusingly
       suggest failure or false == bad.

    2. Use a specified `printf(3)`-style format string (`f`) or the default
       error message (`_`). Note that the default is very unhelpful for users,
       so use it only for “pure” asserts that you believe are unlikely to be
       violated or reflect a bug that users can’t do anything about anyway.

    3. Include `errno` in the error message (`e`) or not (`_`). Note that if
       `errno` is zero, the message will include confusingly include
       “success”; be careful about whether `errno` is meaningful.

    For example:

      char *d = "/does/not/exist";

      T__ (d != NULL && d[0] != '/');
        -> ch-run[27720]: error: assertion failure: please report this bug (ch-run.c:128)

    This is a good example of a “pure” assertion.

      Z__ (d == NULL || d[0] == '/');
        -> ch-run[27720]: error: assertion failure: please report this bug (ch-run.c:128)

    Typically inadvisable (i.e., don’t use Z?? on expressions) because the
    implicit negation of the expression can be confusing.

      Z_e (chdir(d));
        -> ch-run[27720]: error: assertion failure: please report this bug: No such file or directory (ch-run.c:128 2 ENOENT)

    Typically inadviseable because `chdir(2)` failing is reasonably expected
    and likely due to external causes, so we should explain to the user what
    was being attempted.

      Zf_ (chdir(c), "can't cd: %s", d);
        -> ch-run[27720]: error: can't cd: /does/not/exist (ch-run.c:128)

    Also typically inadviseable because the *reason* for the `chdir(2)`
    failure (as encoded in `errno`) is likely to be important for debugging,
    so we should pass it on to the user.

      Zfe (chdir(d), "can't cd: %s", d);
        -> ch-run[27720]: error: can't cd: /does/not/exist: No such file or  directory (ch-run.c:128 2 ENOENT)

    Best practice for typical error conditions. This explains both what we
    were trying to do and what went wrong.

      errno = 0;

      Z_e (false);
        -> ch-run[27720]: error: assertion failure: please report this bug: Success (ch-run.c:128 0 0)

   Example of a maximally un-helpful error message.

   Note the style of including a space between the macro name and the opening
   parenthesis, to distinguish it from standard function call. */

#define _GNU_SOURCE
#pragma once

#include <errno.h>
#include <stddef.h>
#include <syslog.h>

#include "misc.h"


/** Macros **/

/** Verify that @p x is true (non-zero); otherwise, exit with a generic error
    message. */
#define T__(x)       do { \
                        if (!(x)) \
                           msg_fatal(__FILE__, __LINE__, -1, NULL); \
                     } while (0)

/** Verify that @p x is true (non-zero); otherwise, exit with an error message
    specified by a `printf(3)` format string in the second argument along with
    appropriate additional arguments. */
#define Tf_(x, ...)  do { \
                        if (!(x)) \
                           msg_fatal(__FILE__, __LINE__, -1, __VA_ARGS__); \
                     } while (0)

/** Verify that @p x is true (non-zero); otherwise, exit with a generic error
    message followed by errno and its stringified form. */
#define T_e(x)       do { \
                        if (!(x)) \
                           msg_fatal(__FILE__, __LINE__, errno, NULL); \
                     } while (0);

/** Verify that @p x is true (non-zero); otherwise, exit with an error message
    specified by a `printf(3)` format string in the second argument along with
    appropriate additional arguments, followed by errno and its stringified
    form. */
#define Tfe(x, ...)  do { \
                        if (!(x)) \
                           msg_fatal(__FILE__, __LINE__, errno, __VA_ARGS__); \
                     } while (0)

/** Verify that @p x is zero (false); otherwise, exit with a generic error
    message. */
#define Z__(x)       do { \
                        if (x) \
                           msg_fatal(__FILE__, __LINE__, -1, NULL); \
                     } while (0)

/** Verify that @p x is zero (false); otherwise, exit with an error message
    specified by a `printf(3)` format string in the second argument along with
    appropriate additional arguments. */
#define Zf_(x, ...)  do { \
                        if (x) \
                           msg_fatal(__FILE__, __LINE__, -1, __VA_ARGS__); \
                     } while (0)

/** Verify that @p x is zero (false); otherwise, exit with a generic error
    message followed by errno and its stringified form. */
#define Z_e(x)       do { \
                        if (x) \
                           msg_fatal(__FILE__, __LINE__, errno, NULL); \
                     } while (0)

/** Verify that @p x is zero (false); otherwise, exit with an error message
    specified by a `printf(3)` format string in the second argument along with
    appropriate additional arguments, followed by errno and its stringified
    form. */
#define Zfe(x, ...)  do { \
                        if (x) \
                           msg_fatal(__FILE__, __LINE__, errno, __VA_ARGS__); \
                     } while (0)

/* Log the current UIDs. */
#define LOG_IDS log_ids(__func__, __LINE__)

/* Use these instead of printf(3), sprintf(3), etc. to explain to the user
   what is going on. Do not use these for output. */
#define FATAL(e, ...) msg_fatal(      __FILE__, __LINE__, e,  __VA_ARGS__)
#define ERROR(e, ...) msg_error(      __FILE__, __LINE__, e,  __VA_ARGS__)
#define WARNING(...)  msg(LL_WARNING, __FILE__, __LINE__, -1, __VA_ARGS__)
#define INFO(...)     msg(LL_INFO,    __FILE__, __LINE__, -1, __VA_ARGS__)
#define VERBOSE(...)  msg(LL_VERBOSE, __FILE__, __LINE__, -1, __VA_ARGS__)
#define DEBUG(...)    msg(LL_DEBUG,   __FILE__, __LINE__, -1, __VA_ARGS__)
#define TRACE(...)    msg(LL_TRACE,   __FILE__, __LINE__, -1, __VA_ARGS__)

/* Syslog facility and level we use. */
#ifdef ENABLE_SYSLOG
#define SYSLOG_PRI (LOG_USER|LOG_INFO)
#endif

/* Size of “warnings” buffer, in bytes. We want this to be big enough that we
   don’t need to worry about running out of room. */
#define WARNINGS_SIZE (4*1024)


/** Types **/

enum log_level { LL_FATAL =   -3,
                 LL_STDERR =  -2,
                 LL_WARNING = -1,
                 LL_INFO =     0,  // minimum number of -v to print the msg
                 LL_VERBOSE =  1,
                 LL_DEBUG =    2,
                 LL_TRACE =    3 };

enum log_color_when { LL_COLOR_NULL = 0,
                      LL_COLOR_AUTO,
                      LL_COLOR_YES,
                      LL_COLOR_NO };

enum log_test { LL_TEST_NONE  = 0,
                LL_TEST_YES   = 1,
                LL_TEST_FATAL = 2 };


/** External variables **/

extern bool abort_fatal;
extern bool log_color_p;
extern enum log_level verbose;
extern char *warnings;
extern size_t warnings_offset;


/** Function prototypes **/

void log_ids(const char *func, int line);
void logging_init(enum log_color_when when, enum log_test test);
void msg(enum log_level level, const char *file, int line, int errno_,
         const char *fmt, ...)
        __attribute__ ((format (printf, 5, 6)));
void msg_error(const char *file, int line, int errno_,
               const char *fmt, ...)
              __attribute__ ((format (printf, 4, 5)));
noreturn msg_fatal(const char *file, int line, int errno_,
                   const char *fmt, ...)
                  __attribute__ ((format (printf, 4, 5)));
void warnings_reprint(void);

