// Copyright 2011-2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef SERD_LOG_H
#define SERD_LOG_H

#include "serd/attributes.h"
#include "serd/caret.h"
#include "serd/status.h"
#include "serd/string_view.h"
#include "serd/world.h"
#include "zix/attributes.h"

#include <stdarg.h>
#include <stddef.h>

SERD_BEGIN_DECLS

/**
   @defgroup serd_logging Logging
   @ingroup serd_errors
   @{
*/

/// Log entry level, compatible with syslog
typedef enum {
  SERD_LOG_LEVEL_EMERGENCY, ///< Emergency, system is unusable
  SERD_LOG_LEVEL_ALERT,     ///< Action must be taken immediately
  SERD_LOG_LEVEL_CRITICAL,  ///< Critical condition
  SERD_LOG_LEVEL_ERROR,     ///< Error
  SERD_LOG_LEVEL_WARNING,   ///< Warning
  SERD_LOG_LEVEL_NOTICE,    ///< Normal but significant condition
  SERD_LOG_LEVEL_INFO,      ///< Informational message
  SERD_LOG_LEVEL_DEBUG,     ///< Debug message
} SerdLogLevel;

/**
   A structured log field.

   Fields are used to add metadata to log messages.  Syslog-compatible keys
   should be used where possible, otherwise, keys should be namespaced to
   prevent clashes.

   Serd itself uses the following keys:

   - ERRNO - The `errno` of the original system error if any (decimal string)
   - SERD_COL - The 1-based column number in the file (decimal string)
   - SERD_FILE - The file which caused this message (string)
   - SERD_LINE - The 1-based line number in the file (decimal string)
   - SERD_CHECK - The check/warning/etc that triggered this message (string)
*/
typedef struct {
  const char* ZIX_NONNULL key;   ///< Field name
  const char* ZIX_NONNULL value; ///< Field value
} SerdLogField;

/**
   Function for handling log messages.

   By default, the log is printed to `stderr`, but this can be overridden to
   instead send log messages to a user function of this type.

   @param handle Pointer to opaque user data.
   @param level Log level.
   @param n_fields Number of entries in `fields`.
   @param fields An array of `n_fields` extra log fields.
   @param message Log message.
*/
typedef SerdStatus (*SerdLogFunc)(void* ZIX_NULLABLE               handle,
                                  SerdLogLevel                     level,
                                  size_t                           n_fields,
                                  const SerdLogField* ZIX_NULLABLE fields,
                                  SerdStringView                   message);

/// A #SerdLogFunc that does nothing (for suppressing log output)
SERD_CONST_API SerdStatus
serd_quiet_log_func(void* ZIX_NULLABLE               handle,
                    SerdLogLevel                     level,
                    size_t                           n_fields,
                    const SerdLogField* ZIX_NULLABLE fields,
                    SerdStringView                   message);

/**
   Set a function to be called with log messages (typically errors).

   If no custom logging function is set, then messages are printed to stderr.

   @param world World that will send log entries to the given function.

   @param log_func Log function to call for every log message.  Each call to
   this function represents a complete log message with an implicit trailing
   newline.

   @param handle Opaque handle that will be passed to every invocation of
   `log_func`.
*/
SERD_API void
serd_set_log_func(SerdWorld* ZIX_NONNULL   world,
                  SerdLogFunc ZIX_NULLABLE log_func,
                  void* ZIX_NULLABLE       handle);

/**
   Write a message to the log with a `va_list`.

   This is the fundamental and most powerful function for writing entries to
   the log, the others are convenience wrappers that ultimately call this.

   This writes a single complete entry to the log, and so may not be used to
   print parts of a line like a more general printf-like function.  There
   should be no trailing newline in `fmt`.  Arguments following `fmt` should
   correspond to conversion specifiers in the format string as in printf from
   the standard C library.

   @param world World to log to.
   @param level Log level.
   @param n_fields Number of entries in `fields`.
   @param fields An array of `n_fields` extra log fields.
   @param fmt Format string.
   @param args Arguments for `fmt`.

   @return A status code, which is always #SERD_SUCCESS with the default log
   function.  If a custom log function is set with serd_set_log_func() and it
   returns an error, then that error is returned here.
*/
SERD_LOG_FUNC(5, 0)
SERD_API SerdStatus
serd_vxlogf(const SerdWorld* ZIX_NONNULL     world,
            SerdLogLevel                     level,
            size_t                           n_fields,
            const SerdLogField* ZIX_NULLABLE fields,
            const char* ZIX_NONNULL          fmt,
            va_list                          args);

/**
   Write a message to the log with extra fields.

   This is a convenience wrapper for serd_vxlogf() that takes the format
   arguments directly.
*/
SERD_LOG_FUNC(5, 6)
SERD_API SerdStatus
serd_xlogf(const SerdWorld* ZIX_NONNULL     world,
           SerdLogLevel                     level,
           size_t                           n_fields,
           const SerdLogField* ZIX_NULLABLE fields,
           const char* ZIX_NONNULL          fmt,
           ...);

/**
   Write a simple message to the log.

   This is a convenience wrapper for serd_vxlogf() which sets no extra fields.
*/
SERD_LOG_FUNC(3, 0)
SERD_API SerdStatus
serd_vlogf(const SerdWorld* ZIX_NONNULL world,
           SerdLogLevel                 level,
           const char* ZIX_NONNULL      fmt,
           va_list                      args);

/**
   Write a simple message to the log.

   This is a convenience wrapper for serd_vlogf() that takes the format
   arguments directly.
*/
SERD_LOG_FUNC(3, 4)
SERD_API SerdStatus
serd_logf(const SerdWorld* ZIX_NONNULL world,
          SerdLogLevel                 level,
          const char* ZIX_NONNULL      fmt,
          ...);

/**
   Write a message to the log with a caret position.

   This is a convenience wrapper for serd_vxlogf() which sets `SERD_FILE`,
   `SERD_LINE`, and `SERD_COL` to the position of the given caret.  Entries are
   typically printed with a GCC-style prefix like "file.ttl:16:4".
*/
SERD_LOG_FUNC(4, 0)
SERD_API SerdStatus
serd_vlogf_at(const SerdWorld* ZIX_NONNULL  world,
              SerdLogLevel                  level,
              const SerdCaret* ZIX_NULLABLE caret,
              const char* ZIX_NONNULL       fmt,
              va_list                       args);

/**
   Write a message to the log with a caret position.

   This is a convenience wrapper for serd_vlogf_at() that takes the format
   arguments directly.
*/
SERD_LOG_FUNC(4, 5)
SERD_API SerdStatus
serd_logf_at(const SerdWorld* ZIX_NONNULL  world,
             SerdLogLevel                  level,
             const SerdCaret* ZIX_NULLABLE caret,
             const char* ZIX_NONNULL       fmt,
             ...);

/**
   @}
*/

SERD_END_DECLS

#endif // SERD_LOG_H