/* SPDX-FileCopyrightText: 2018-2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup intern_clog
 *
 * C Logging Library (clog)
 * ========================
 *
 * Usage
 * -----
 *
 * - `CLG_LOGREF_DECLARE_GLOBAL` macro to declare #CLG_LogRef pointers.
 * - `CLOG_` prefixed macros for logging.
 *
 * Identifiers
 * -----------
 *
 * #CLG_LogRef holds an identifier which defines the category of the logger.
 *
 * You can define and use identifiers as needed, logging will lazily initialize them.
 *
 * By convention lower case dot separated identifiers are used, eg:
 * `module.sub_module`, this allows filtering by `module.*`,
 * see #CLG_type_filter_include, #CLG_type_filter_exclude
 *
 * There is currently no functionality to remove a category once it's created.
 *
 * Severity
 * --------
 *
 * - `INFO`: Simply log events, uses verbosity levels to control how much information to show.
 * - `WARN`: General warnings (which aren't necessary to show to users).
 * - `ERROR`: An error we can recover from, should not happen.
 * - `FATAL`: Similar to assert. This logs the message, then a stack trace and abort.
 * Verbosity Level
 * ---------------
 *
 * Usage:
 *
 * - 0: Always show (used for warnings, errors).
 *   Should never get in the way or become annoying.
 *
 * - 1: Top level module actions (eg: load a file, create a new window .. etc).
 *
 * - 2: Actions within a module (steps which compose an action, but don't flood output).
 *   Running a tool, full data recalculation.
 *
 * - 3: Detailed actions which may be of interest when debugging internal logic of a module
 *   These *may* flood the log with details.
 *
 * - 4+: May be used for more details than 3, should be avoided but not prevented.
 */

#ifndef __CLG_LOG_H__
#define __CLG_LOG_H__

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#ifdef __GNUC__
#  define _CLOG_ATTR_NONNULL(args...) __attribute__((nonnull(args)))
#else
#  define _CLOG_ATTR_NONNULL(...)
#endif

#ifdef __GNUC__
#  define _CLOG_ATTR_PRINTF_FORMAT(format_param, dots_param) \
    __attribute__((format(printf, format_param, dots_param)))
#else
#  define _CLOG_ATTR_PRINTF_FORMAT(format_param, dots_param)
#endif

#define STRINGIFY_ARG(x) "" #x
#define STRINGIFY_APPEND(a, b) "" a #b
#define STRINGIFY(x) STRINGIFY_APPEND("", x)

struct CLogContext;

/* Don't typedef enums. */
enum CLG_LogFlag {
  CLG_FLAG_USE = (1 << 0),
};

enum CLG_Severity {
  CLG_SEVERITY_INFO = 0,
  CLG_SEVERITY_WARN,
  CLG_SEVERITY_ERROR,
  CLG_SEVERITY_FATAL,
};
#define CLG_SEVERITY_LEN (CLG_SEVERITY_FATAL + 1)

/* Each logger ID has one of these. */
typedef struct CLG_LogType {
  struct CLG_LogType *next;
  char identifier[64];
  /** FILE output. */
  struct CLogContext *ctx;
  /** Control behavior. */
  int level;
  enum CLG_LogFlag flag;
} CLG_LogType;

typedef struct CLG_LogRef {
  const char *identifier;
  CLG_LogType *type;
  struct CLG_LogRef *next;
} CLG_LogRef;

void CLG_log_str(CLG_LogType *lg,
                 enum CLG_Severity severity,
                 const char *file_line,
                 const char *fn,
                 const char *message) _CLOG_ATTR_NONNULL(1, 3, 4, 5);
void CLG_logf(CLG_LogType *lg,
              enum CLG_Severity severity,
              const char *file_line,
              const char *fn,
              const char *format,
              ...) _CLOG_ATTR_NONNULL(1, 3, 4, 5) _CLOG_ATTR_PRINTF_FORMAT(5, 6);

/* Main initializer and destructor (per session, not logger). */
void CLG_init(void);
void CLG_exit(void);

void CLG_output_set(void *file_handle);
void CLG_output_use_basename_set(int value);
void CLG_output_use_timestamp_set(int value);
void CLG_error_fn_set(void (*error_fn)(void *file_handle));
void CLG_fatal_fn_set(void (*fatal_fn)(void *file_handle));
void CLG_backtrace_fn_set(void (*fatal_fn)(void *file_handle));

void CLG_type_filter_include(const char *type_filter, int type_filter_len);
void CLG_type_filter_exclude(const char *type_filter, int type_filter_len);

void CLG_level_set(int level);

void CLG_logref_init(CLG_LogRef *clg_ref);

int CLG_color_support_get(CLG_LogRef *clg_ref);

/** Declare outside function, declare as extern in header. */
#define CLG_LOGREF_DECLARE_GLOBAL(var, id) \
  static CLG_LogRef _static_##var = {id}; \
  CLG_LogRef *var = &_static_##var

/** Initialize struct once. */
#define CLOG_ENSURE(clg_ref) \
  ((clg_ref)->type ? (clg_ref)->type : (CLG_logref_init(clg_ref), (clg_ref)->type))

#define CLOG_CHECK(clg_ref, verbose_level, ...) \
  ((void)CLOG_ENSURE(clg_ref), \
   ((clg_ref)->type->flag & CLG_FLAG_USE) && ((clg_ref)->type->level >= verbose_level))

#define CLOG_AT_SEVERITY(clg_ref, severity, verbose_level, ...) \
  { \
    CLG_LogType *_lg_ty = CLOG_ENSURE(clg_ref); \
    if (((_lg_ty->flag & CLG_FLAG_USE) && (_lg_ty->level >= verbose_level)) || \
        (severity >= CLG_SEVERITY_WARN)) \
    { \
      CLG_logf(_lg_ty, severity, __FILE__ ":" STRINGIFY(__LINE__), __func__, __VA_ARGS__); \
    } \
  } \
  ((void)0)

#define CLOG_STR_AT_SEVERITY(clg_ref, severity, verbose_level, str) \
  { \
    CLG_LogType *_lg_ty = CLOG_ENSURE(clg_ref); \
    if (((_lg_ty->flag & CLG_FLAG_USE) && (_lg_ty->level >= verbose_level)) || \
        (severity >= CLG_SEVERITY_WARN)) \
    { \
      CLG_log_str(_lg_ty, severity, __FILE__ ":" STRINGIFY(__LINE__), __func__, str); \
    } \
  } \
  ((void)0)

#define CLOG_STR_AT_SEVERITY_N(clg_ref, severity, verbose_level, str) \
  { \
    CLG_LogType *_lg_ty = CLOG_ENSURE(clg_ref); \
    if (((_lg_ty->flag & CLG_FLAG_USE) && (_lg_ty->level >= verbose_level)) || \
        (severity >= CLG_SEVERITY_WARN)) \
    { \
      const char *_str = str; \
      CLG_log_str(_lg_ty, severity, __FILE__ ":" STRINGIFY(__LINE__), __func__, _str); \
      MEM_freeN((void *)_str); \
    } \
  } \
  ((void)0)

#define CLOG_INFO(clg_ref, level, ...) \
  CLOG_AT_SEVERITY(clg_ref, CLG_SEVERITY_INFO, level, __VA_ARGS__)
#define CLOG_WARN(clg_ref, ...) CLOG_AT_SEVERITY(clg_ref, CLG_SEVERITY_WARN, 0, __VA_ARGS__)
#define CLOG_ERROR(clg_ref, ...) CLOG_AT_SEVERITY(clg_ref, CLG_SEVERITY_ERROR, 0, __VA_ARGS__)
#define CLOG_FATAL(clg_ref, ...) CLOG_AT_SEVERITY(clg_ref, CLG_SEVERITY_FATAL, 0, __VA_ARGS__)

#define CLOG_STR_INFO(clg_ref, level, str) \
  CLOG_STR_AT_SEVERITY(clg_ref, CLG_SEVERITY_INFO, level, str)
#define CLOG_STR_WARN(clg_ref, str) CLOG_STR_AT_SEVERITY(clg_ref, CLG_SEVERITY_WARN, 0, str)
#define CLOG_STR_ERROR(clg_ref, str) CLOG_STR_AT_SEVERITY(clg_ref, CLG_SEVERITY_ERROR, 0, str)
#define CLOG_STR_FATAL(clg_ref, str) CLOG_STR_AT_SEVERITY(clg_ref, CLG_SEVERITY_FATAL, 0, str)

/* Allocated string which is immediately freed. */
#define CLOG_STR_INFO_N(clg_ref, level, str) \
  CLOG_STR_AT_SEVERITY_N(clg_ref, CLG_SEVERITY_INFO, level, str)
#define CLOG_STR_WARN_N(clg_ref, str) CLOG_STR_AT_SEVERITY_N(clg_ref, CLG_SEVERITY_WARN, 0, str)
#define CLOG_STR_ERROR_N(clg_ref, str) CLOG_STR_AT_SEVERITY_N(clg_ref, CLG_SEVERITY_ERROR, 0, str)
#define CLOG_STR_FATAL_N(clg_ref, str) CLOG_STR_AT_SEVERITY_N(clg_ref, CLG_SEVERITY_FATAL, 0, str)

#ifdef __cplusplus
}
#endif

#endif /* __CLG_LOG_H__ */
