#ifndef LOG_H
#define LOG_H

#include "string.h"

#define LOG_LEVEL(l) (l <= LOG_LEVEL_COMPTIME)

/* Log level configuration */
#ifndef LOG_LEVEL_COMPTIME
# if RTC || PROFILING
#  define LOG_LEVEL_COMPTIME LOG_LEVEL_DEBUG
# else
#  define LOG_LEVEL_COMPTIME LOG_LEVEL_INFO
# endif
#endif

/* Source location configuration */
#ifndef LOG_INCLUDE_SOURCE_LOCATION
# define LOG_INCLUDE_SOURCE_LOCATION (DEBINFO)
#endif

#define LOG_LEVEL_NONE      -1
#define LOG_LEVEL_CRITICAL  0
#define LOG_LEVEL_ERROR     1
#define LOG_LEVEL_WARNING   2
#define LOG_LEVEL_INFO      3
#define LOG_LEVEL_DEBUG     4
#define LOG_LEVEL_COUNT     5

/* ========================== *
 * Callback interface
 * ========================== */

struct log_level_settings {
    struct string shorthand;
    u32 color;
};

struct log_event {
    i32 level;
    struct string msg;  /* Lifetime is only as long as the callback function call receiving the event */
    struct log_level_settings settings;

    /* These will be nulled if LOG_INCLUDE_SOURCE_LOCATION is disabled */
    struct string file;
    i32 line;
};

typedef void (log_event_callback_func)(struct log_event);

void log_register_callback(log_event_callback_func *func);

/* ========================== *
 * Logging macros
 * ========================== */

#define log_panic(msg) _log_panic(msg)

#if LOG_LEVEL(LOG_LEVEL_CRITICAL)
# if LOG_INCLUDE_SOURCE_LOCATION
#  define log_critical(msg) _log(LOG_LEVEL_CRITICAL, STR(__FILE__), __LINE__, msg)
#  define logf_critical(fmt, ...) _logf(LOG_LEVEL_CRITICAL, STR(__FILE__), __LINE__, STR(fmt) , ## __VA_ARGS__, FMT_END)
# else
#  define log_critical(msg) _log(LOG_LEVEL_CRITICAL, msg)
#  define logf_critical(fmt, ...) _logf(LOG_LEVEL_CRITICAL, STR(fmt) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_critical(msg)
# define logf_critical(fmt, ...)
#endif

#if LOG_LEVEL(LOG_LEVEL_ERROR)
# if LOG_INCLUDE_SOURCE_LOCATION
#  define log_error(msg) _log(LOG_LEVEL_ERROR, STR(__FILE__), __LINE__, msg)
#  define logf_error(fmt, ...) _logf(LOG_LEVEL_ERROR, STR(__FILE__), __LINE__, STR(fmt) , ## __VA_ARGS__, FMT_END)
# else
#  define log_error(msg) _log(LOG_LEVEL_ERROR, msg)
#  define logf_error(fmt, ...) _logf(LOG_LEVEL_ERROR, STR(fmt) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_error(msg)
# define logf_error(fmt, ...)
#endif

#if LOG_LEVEL(LOG_LEVEL_WARNING)
# if LOG_INCLUDE_SOURCE_LOCATION
#  define log_warning(msg) _log(LOG_LEVEL_WARNING, STR(__FILE__), __LINE__, msg)
#  define logf_warning(fmt, ...) _logf(LOG_LEVEL_WARNING, STR(__FILE__), __LINE__, STR(fmt) , ## __VA_ARGS__, FMT_END)
# else
#  define log_warning(msg) _log(LOG_LEVEL_WARNING, msg)
#  define logf_warning(fmt, ...) _logf(LOG_LEVEL_WARNING, STR(fmt) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_warning(msg)
# define logf_warning(fmt, ...)
#endif

#if LOG_LEVEL(LOG_LEVEL_DEBUG)
# if LOG_INCLUDE_SOURCE_LOCATION
#  define log_debug(msg) _log(LOG_LEVEL_DEBUG, STR(__FILE__), __LINE__, msg)
#  define logf_debug(fmt, ...) _logf(LOG_LEVEL_DEBUG, STR(__FILE__), __LINE__, STR(fmt) , ## __VA_ARGS__, FMT_END)
# else
#  define log_debug(msg) _log(LOG_LEVEL_DEBUG, msg)
#  define logf_debug(fmt, ...) _logf(LOG_LEVEL_DEBUG, STR(fmt) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_debug(msg)
# define logf_debug(fmt, ...)
#endif


#if LOG_LEVEL(LOG_LEVEL_INFO)
# if LOG_INCLUDE_SOURCE_LOCATION
#  define log_info(msg) _log(LOG_LEVEL_INFO, STR(__FILE__), __LINE__, msg)
#  define logf_info(fmt, ...) _logf(LOG_LEVEL_INFO, STR(__FILE__), __LINE__, STR(fmt) , ## __VA_ARGS__, FMT_END)
# else
#  define log_info(msg) _log(LOG_LEVEL_INFO, msg)
#  define logf_info(fmt, ...) _logf(LOG_LEVEL_INFO, STR(fmt) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_info(msg)
# define logf_info(fmt, ...)
#endif

/* ========================== *
 * Function declarations
 * ========================== */

struct log_startup_receipt { i32 _; };
struct log_startup_receipt log_startup(struct string logfile_path);

void _log_panic(struct string msg);

#if LOG_INCLUDE_SOURCE_LOCATION
void _log(i32 level, struct string file, u32 line, struct string msg);
#else
void _log(i32 level, struct string msg);
#endif

#if LOG_INCLUDE_SOURCE_LOCATION
void _logfv(i32 level, struct string file, u32 line, struct string fmt, va_list args);
#else
void _logfv(i32 level, struct string fmt, va_list args);
#endif

#if LOG_INCLUDE_SOURCE_LOCATION
void _logf(i32 level, struct string file, u32 line, struct string fmt, ...);
#else
void _logf(i32 level, struct string fmt, ...);
#endif

#endif
