/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkLogger.h

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/**
 * @class vtkLogger
 * @brief logging framework for use in VTK and in applications based on VTK
 *
 * vtkLogger acts as the entry point to VTK's logging framework. The
 * implementation uses the loguru (https://github.com/emilk/loguru). vtkLogger
 * provides some static API to initialize and configure logging together with a
 * collection of macros that can be used to add items to the generated log.
 *
 * The logging framework is based on verbosity levels. Level 0-9 are supported
 * in addition to named levels such as ERROR, WARNING, and INFO. When a log for
 * a particular verbosity level is being generated, all log additions issued
 * with verbosity level less than or equal to the requested verbosity level will
 * get logged.
 *
 * When using any of the logging macros, it must be noted that unless a log output
 * is requesting that verbosity provided (or higher), the call is a no-op and
 * the message stream or printf-style arguments will not be evaluated.
 *
 * @section Setup Setup
 *
 * To initialize logging, in your application's `main()` you may call
 * `vtkLogger::Init(argv, argc)`. This is totally optional but useful to
 * time-stamp the  start of the  log. Furthermore, it can optionally detect
 * verbosity level on the command line as `-v` (or any another string pass as
 * the optional argument to `Init`) that will be used as the verbosity level for
 * logging on to `stderr`. By default, it is set to `0` (or `INFO`) unless
 * changed by calling `vtkLogger::SetStderrVerbosity`.
 *
 * In additional to logging to `stderr`, one can accumulate logs to one or more files using
 * `vtkLogger::LogToFile`. Each log file can be given its own verbosity level.
 *
 * For multithreaded applications, you may want to name each of the threads so
 * that the generated log can use human readable names for the threads. For
 * that, use `vtkLogger::SetThreadName`. Calling `vtkLogger::Init` will set the name
 * for the main thread.
 *
 * @section Logging Logging
 *
 * vtkLogger provides several macros (again, based on `loguru`) that can be
 * used to add the log. Both printf-style and stream-style is supported. All
 * printf-style macros are suffixed with `F` to distinguish them from the stream
 * macros. Another pattern in naming macros is the presence of `V`
 * e.g. `vtkVLog` vs `vtkLog`. A macro with the `V` prefix takes a fully
 * qualified verbosity enum e.g. `vtkLogger::VERBOSITY_INFO` or
 * `vtkLogger::VERBOSITY_0`, while the non-`V` variant takes the verbosity
 * name e.g. `INFO` or `0`.
 *
 * Following code snippet provides an overview of the available macros and their
 * usage.
 *
 * @code{.cpp}
 *
 *  // Optional, but useful to time-stamp the start of the log.
 *  // Will also detect verbosity level on the command line as -v.
 *
 *  vtkLogger::Init(argc, argv);
 *
 *  // Put every log message in "everything.log":
 *  vtkLogger::LogToFile("everything.log", vtkLogger::APPEND, vtkLogger::VERBOSITY_MAX);
 *
 *  // Only log INFO, WARNING, ERROR to "latest_readable.log":
 *  vtkLogger::LogToFile("latest_readable.log", vtkLogger::TRUNCATE, vtkLogger::VERBOSITY_INFO);
 *
 *  // Only show most relevant things on stderr:
 *  vtkLogger::SetStderrVerbosity(vtkLogger::VERBOSITY_1);
 *
 *  // add a line to log using the verbosity name.
 *  vtkLogF(INFO, "I'm hungry for some %.3f!", 3.14159);
 *  vtkLogF(0, "same deal");
 *
 *  // add a line to log using the verbosity enum.
 *  vtkVLogF(vtkLogger::VERBOSITY_INFO, "I'm hungry for some %.3f!", 3.14159);
 *  vtkVLogF(vtkLogger::VERBOSITY_0, "same deal");
 *
 *  // to add an identifier for a vtkObjectBase or subclass
 *  vtkLogF(INFO, "The object is %s", vtkLogIdentifier(vtkobject));
 *
 *  // add a line conditionally to log if the condition succeeds:
 *  vtkLogIfF(INFO, ptr == nullptr, "ptr is nullptr (some number: %.3f)", *  3.14159);
 *
 *  vtkLogScopeF(INFO, "Will indent all log messages within this scope.");
 *  // in a function, you may use vtkLogScopeFunction(INFO)
 *
 *  // scope can be explicitly started and closed by vtkLogStartScope (or
 *  // vtkLogStartScopef) and vtkLogEndScope
 *  vtkLogStartScope(INFO, "id-used-as-message");
 *  vtkLogStartScopeF(INFO, "id", "message-%d", 1);
 *  vtkLogEndScope("id");
 *  vtkLogEndScope("id-used-as-message");
 *
 *  // alternatively, you can use streams instead of printf-style
 *  vtkLog(INFO, "I'm hungry for some " << 3.14159 << "!");
 *  vtkLogIF(INFO, ptr == nullptr, "ptr is " << "nullptr");
 *
 * @endcode
 *
 * @section LoggingAndLegacyMacros Logging and VTK error macros
 *
 * VTK has long supported multiple macros to report errors, warnings and debug
 * messages through `vtkErrorMacro`, `vtkWarningMacro`, `vtkDebugMacro`, etc.
 * In addition to performing the traditional message reporting via
 * `vtkOutputWindow`, these macros also log to the logging sub-system with
 * appropriate verbosity levels.
 *
 * To avoid the vtkLogger and vtkOutputWindow both posting the message to the
 * standard output streams, vtkOutputWindow now supports an ability to specify
 * terminal display mode, via `vtkOutputWindow::SetDisplayMode`. If display mode
 * is `vtkOutputWindow::DEFAULT` then the output window will not
 * post messages originating from the standard error/warning/debug macros to the
 * standard output if VTK is built with logging support. If VTK is not built
 * with logging support, then vtkOutputWindow will post the messages to the
 * standard output streams, unless disabled explicitly.
 *
 * @section Callbacks Custom callbacks/handlers for log messages
 *
 * vtkLogger supports ability to register callbacks to call on each logged
 * message. This is useful to show the messages in application specific
 * viewports, e.g. a special message widget.
 *
 * To register a callback use `vtkLogger::AddCallback` and to remove a callback
 * use `vtkLogger::RemoveCallback` with the id provided when registering the
 * callback.
 *
 */

#ifndef vtkLogger_h
#define vtkLogger_h

#include "vtkObjectBase.h"
#include "vtkSetGet.h" // needed for macros

#include <string> // needed for std::string

#if defined(_MSC_VER)
#include <sal.h> // Needed for _In_z_ etc annotations
#endif

// this is copied from `loguru.hpp`
#if defined(__clang__) || defined(__GNUC__)
// Helper macro for declaring functions as having similar signature to printf.
// This allows the compiler to catch format errors at compile-time.
#define VTK_PRINTF_LIKE(fmtarg, firstvararg)                                                       \
  __attribute__((__format__(__printf__, fmtarg, firstvararg)))
#define VTK_FORMAT_STRING_TYPE const char*
#elif defined(_MSC_VER)
#define VTK_PRINTF_LIKE(fmtarg, firstvararg)
#define VTK_FORMAT_STRING_TYPE _In_z_ _Printf_format_string_ const char*
#else
#define VTK_PRINTF_LIKE(fmtarg, firstvararg)
#define VTK_FORMAT_STRING_TYPE const char*
#endif

class VTKCOMMONCORE_EXPORT vtkLogger : public vtkObjectBase
{
public:
  vtkBaseTypeMacro(vtkLogger, vtkObjectBase);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  enum Verbosity
  {
    // Used to mark an invalid verbosity. Do not log to this level.
    VERBOSITY_INVALID = -10, // Never do LOG_F(INVALID)

    // You may use VERBOSITY_OFF on g_stderr_verbosity, but for nothing else!
    VERBOSITY_OFF = -9, // Never do LOG_F(OFF)

    VERBOSITY_ERROR = -2,
    VERBOSITY_WARNING = -1,

    // Normal messages. By default written to stderr.
    VERBOSITY_INFO = 0,

    // Same as VERBOSITY_INFO in every way.
    VERBOSITY_0 = 0,

    // Verbosity levels 1-9 are generally not written to stderr, but are written to file.
    VERBOSITY_1 = +1,
    VERBOSITY_2 = +2,
    VERBOSITY_3 = +3,
    VERBOSITY_4 = +4,
    VERBOSITY_5 = +5,
    VERBOSITY_6 = +6,
    VERBOSITY_7 = +7,
    VERBOSITY_8 = +8,
    VERBOSITY_9 = +9,

    // trace level, same as VERBOSITY_9
    VERBOSITY_TRACE = +9,

    // Don not use higher verbosity levels, as that will make grepping log files harder.
    VERBOSITY_MAX = +9,
  };

  /**
   * Initializes logging. This should be called from the main thread, if at all.
   * Your application doesn't *need* to call this, but if you do:
   *  * signal handlers are installed
   *  * program arguments are logged
   *  * working directory is logged
   *  * optional -v verbosity flag is parsed
   *  * main thread name is set to "main thread"
   *  * explanation of the preamble (date, threadname, etc.) is logged.
   *
   * This method will look for arguments meant for logging subsystem and remove
   * them. Arguments meant for logging subsystem are:
   *
   * -v n Set stderr logging verbosity. Examples
   *    -v 3        Show verbosity level 3 and lower.
   *    -v 0        Only show INFO, WARNING, ERROR, FATAL (default).
   *    -v INFO     Only show INFO, WARNING, ERROR, FATAL (default).
   *    -v WARNING  Only show WARNING, ERROR, FATAL.
   *    -v ERROR    Only show ERROR, FATAL.
   *    -v FATAL    Only show FATAL.
   *    -v OFF      Turn off logging to stderr.
   *
   * You can set the default logging verbosity programmatically by calling
   * `vtkLogger::SetStderrVerbosity` before calling `vtkLogger::Init`. That
   * way, you can specify a default that the user can override using command
   * line arguments. Note that this does not affect file logging.
   *
   * You can also use something else instead of '-v' flag by the via
   * `verbosity_flag` argument. You can also set to nullptr to skip parsing
   * verbosity level from the command line arguments.
   * @{
   */
  static void Init(int& argc, char* argv[], const char* verbosity_flag = "-v");
  static void Init();
  /** @} */

  /**
   * Set the verbosity level for the output logged to stderr. Everything with a
   * verbosity equal or less than the level specified will be written to
   * stderr. Set to `VERBOSITY_OFF` to write nothing to stderr.
   * Default is 0.
   */
  static void SetStderrVerbosity(Verbosity level);

  /**
   * Set internal messages verbosity level. The library used by VTK, `loguru`
   * generates log messages during initialization and at exit. These are logged
   * as log level VERBOSITY_1, by default. One can change that using this
   * method. Typically, you want to call this before `vtkLogger::Init`.
   */
  static void SetInternalVerbosityLevel(Verbosity level);

  /**
   * Support log file modes: `TRUNCATE` truncates the file clearing any existing
   * contents while `APPEND` appends to the existing log file contents, if any.
   */
  enum FileMode
  {
    TRUNCATE,
    APPEND
  };

  /**
   * Enable logging to a file at the given path.
   * Any logging message with verbosity lower or equal to the given verbosity
   * will be included. This method will create all directories in the 'path' if
   * needed. To stop the file logging, call `EndLogToFile` with the same path.
   */
  static void LogToFile(const char* path, FileMode filemode, Verbosity verbosity);

  /**
   * Stop logging to a file at the given path.
   */
  static void EndLogToFile(const char* path);

  //@{
  /**
   * Get/Set the name to identify the current thread in the log output.
   */
  static void SetThreadName(const std::string& name);
  static std::string GetThreadName();
  //@}

  /**
   * Returns a printable string for a vtkObjectBase instance.
   */
  static std::string GetIdentifier(vtkObjectBase* obj);

  /**
   * The message structure that is passed to custom callbacks registered using
   * `vtkLogger::AddCallback`.
   */
  struct Message
  {
    // You would generally print a Message by just concatenating the buffers without spacing.
    // Optionally, ignore preamble and indentation.
    Verbosity verbosity;     // Already part of preamble
    const char* filename;    // Already part of preamble
    unsigned line;           // Already part of preamble
    const char* preamble;    // Date, time, uptime, thread, file:line, verbosity.
    const char* indentation; // Just a bunch of spacing.
    const char* prefix;      // Assertion failure info goes here (or "").
    const char* message;     // User message goes here.
  };

  //@{
  /**
   * Callback handle types.
   */
  using LogHandlerCallbackT = void (*)(void* user_data, const Message& message);
  using CloseHandlerCallbackT = void (*)(void* user_data);
  using FlushHandlerCallbackT = void (*)(void* user_data);
  //@}

  /**
   * Add a callback to call on each log message with a  verbosity less or equal
   * to the given one.  Useful for displaying messages in an application output
   * window, for example. The given `on_close` is also expected to flush (if
   * desired).
   *
   * Note that if logging is disabled at compile time, then these callback will
   * never be called.
   */
  static void AddCallback(const char* id, LogHandlerCallbackT callback, void* user_data,
    Verbosity verbosity, CloseHandlerCallbackT on_close = nullptr,
    FlushHandlerCallbackT on_flush = nullptr);

  /**
   * Remove a callback using the id specified.
   * Returns true if and only if the callback was found (and removed).
   */
  static bool RemoveCallback(const char* id);

  /**
   * Returns true if VTK is built with logging support enabled.
   */
  static bool IsEnabled();

  /**
   * Returns the maximum verbosity of all log outputs. A log item for a
   * verbosity higher than this will not be generated in any of the currently
   * active outputs.
   */
  static Verbosity GetCurrentVerbosityCutoff();

  /**
   * Convenience function to convert an integer to matching verbosity level. If
   * val is less than or equal to vtkLogger::VERBOSITY_INVALID, then
   * vtkLogger::VERBOSITY_INVALID is returned. If value is greater than
   * vtkLogger::VERBOSITY_MAX, then vtkLogger::VERBOSITY_MAX is returned.
   */
  static Verbosity ConvertToVerbosity(int value);

  /**
   * Convenience function to convert a string to matching verbosity level.
   * vtkLogger::VERBOSITY_INVALID will be return for invalid strings.
   * Accepted string values are OFF, ERROR, WARNING, INFO, TRACE, MAX, INVALID or ASCII
   * representation for an integer in the range [-9,9].
   */
  static Verbosity ConvertToVerbosity(const char* text);

  //@{
  /**
   * @internal
   *
   * Not intended for public use, please use the logging macros instead.
   */
  static void Log(Verbosity verbosity, const char* fname, unsigned int lineno, const char* txt);
  static void StartScope(
    Verbosity verbosity, const char* id, const char* fname, unsigned int lineno);
  static void EndScope(const char* id);
#if !defined(__WRAP__)
  static void LogF(Verbosity verbosity, const char* fname, unsigned int lineno,
    VTK_FORMAT_STRING_TYPE format, ...) VTK_PRINTF_LIKE(4, 5);
  static void StartScopeF(Verbosity verbosity, const char* id, const char* fname,
    unsigned int lineno, VTK_FORMAT_STRING_TYPE format, ...) VTK_PRINTF_LIKE(5, 6);

  class VTKCOMMONCORE_EXPORT LogScopeRAII
  {
  public:
    LogScopeRAII();
    LogScopeRAII(vtkLogger::Verbosity verbosity, const char* fname, unsigned int lineno,
      VTK_FORMAT_STRING_TYPE format, ...) VTK_PRINTF_LIKE(5, 6);
    ~LogScopeRAII();
#if defined(_MSC_VER) && _MSC_VER > 1800
    // see loguru.hpp for the reason why this is needed on MSVC
    LogScopeRAII(LogScopeRAII&& other)
      : Internals(other.Internals)
    {
      other.Internals = nullptr;
    }
#else
    LogScopeRAII(LogScopeRAII&&) = default;
#endif

  private:
    LogScopeRAII(const LogScopeRAII&) = delete;
    void operator=(const LogScopeRAII&) = delete;
    class LSInternals;
    LSInternals* Internals;
  };
#endif
  //@}
protected:
  vtkLogger();
  ~vtkLogger() override;

private:
  vtkLogger(const vtkLogger&) = delete;
  void operator=(const vtkLogger&) = delete;
  static vtkLogger::Verbosity InternalVerbosityLevel;
  static std::string ThreadName;
};

//@{
/**
 * Add to log given the verbosity level.
 * The text will be logged when the log verbosity is set to the specified level
 * or higher.
 *
 *     // using printf-style
 *     vtkLogF(INFO, "Hello %s", "world!");
 *     vtkVLogF(vtkLogger::VERBOSITY_INFO, "Hello %s", "world!");
 *
 *     // using streams
 *     vtkLog(INFO, "Hello " << "world!");
 *     vtkVLog(vtkLogger::VERBOSITY_INFO, << "Hello world!");
 *
 */
#define vtkVLogF(level, ...)                                                                       \
  ((level) > vtkLogger::GetCurrentVerbosityCutoff())                                               \
    ? (void)0                                                                                      \
    : vtkLogger::LogF(level, __FILE__, __LINE__, __VA_ARGS__)
#define vtkLogF(verbosity_name, ...) vtkVLogF(vtkLogger::VERBOSITY_##verbosity_name, __VA_ARGS__)
#define vtkVLog(level, x)                                                                          \
  if ((level) <= vtkLogger::GetCurrentVerbosityCutoff())                                           \
  {                                                                                                \
    vtkOStrStreamWrapper::EndlType endl;                                                           \
    vtkOStrStreamWrapper::UseEndl(endl);                                                           \
    vtkOStrStreamWrapper vtkmsg;                                                                   \
    vtkmsg << "" x;                                                                                \
    vtkLogger::Log(level, __FILE__, __LINE__, vtkmsg.str());                                       \
    vtkmsg.rdbuf()->freeze(0);                                                                     \
  }
#define vtkLog(verbosity_name, x) vtkVLog(vtkLogger::VERBOSITY_##verbosity_name, x)
//@}

//@{
/**
 * Add to log only when the `cond` passes.
 *
 *     // using printf-style
 *     vtkLogIfF(ERROR, ptr == nullptr, "`ptr` cannot be null!");
 *     vtkVLogIfF(vtkLogger::VERBOSITY_ERROR, ptr == nullptr, "`ptr` cannot be null!");
 *
 *     // using streams
 *     vtkLogIf(ERROR, ptr == nullptr, "`ptr` cannot be null!");
 *     vtkVLogIf(vtkLogger::VERBOSITY_ERROR, ptr == nullptr, << "`ptr` cannot be null!");
 *
 */
#define vtkVLogIfF(level, cond, ...)                                                               \
  ((level) > vtkLogger::GetCurrentVerbosityCutoff() || (cond) == false)                            \
    ? (void)0                                                                                      \
    : vtkLogger::LogF(level, __FILE__, __LINE__, __VA_ARGS__)

#define vtkLogIfF(verbosity_name, cond, ...)                                                       \
  vtkVLogIfF(vtkLogger::VERBOSITY_##verbosity_name, cond, __VA_ARGS__)

#define vtkVLogIf(level, cond, x)                                                                  \
  if ((level) <= vtkLogger::GetCurrentVerbosityCutoff() && (cond))                                 \
  {                                                                                                \
    vtkOStrStreamWrapper::EndlType endl;                                                           \
    vtkOStrStreamWrapper::UseEndl(endl);                                                           \
    vtkOStrStreamWrapper vtkmsg;                                                                   \
    vtkmsg << "" x;                                                                                \
    vtkLogger::Log(level, __FILE__, __LINE__, vtkmsg.str());                                       \
    vtkmsg.rdbuf()->freeze(0);                                                                     \
  }
#define vtkLogIf(verbosity_name, cond, x) vtkVLogIf(vtkLogger::VERBOSITY_##verbosity_name, cond, x)
//@}

#define VTKLOG_CONCAT_IMPL(s1, s2) s1##s2
#define VTKLOG_CONCAT(s1, s2) VTKLOG_CONCAT_IMPL(s1, s2)
#define VTKLOG_ANONYMOUS_VARIABLE(x) VTKLOG_CONCAT(x, __LINE__)

#define vtkVLogScopeF(level, ...)                                                                  \
  auto VTKLOG_ANONYMOUS_VARIABLE(msg_context) = ((level) > vtkLogger::GetCurrentVerbosityCutoff()) \
    ? vtkLogger::LogScopeRAII()                                                                    \
    : vtkLogger::LogScopeRAII(level, __FILE__, __LINE__, __VA_ARGS__)

#define vtkLogScopeF(verbosity_name, ...)                                                          \
  vtkVLogScopeF(vtkLogger::VERBOSITY_##verbosity_name, __VA_ARGS__)

#define vtkLogScopeFunction(verbosity_name) vtkLogScopeF(verbosity_name, "%s", __func__)
#define vtkVLogScopeFunction(level) vtkVLogScopeF(level, "%s", __func__)

//@{
/**
 * Explicitly mark start and end of log scope. This is useful in cases where the
 * start and end of the scope does not happen within the same C++ scope.
 */
#define vtkLogStartScope(verbosity_name, id)                                                       \
  vtkLogger::StartScope(vtkLogger::VERBOSITY_##verbosity_name, id, __FILE__, __LINE__)
#define vtkLogEndScope(id) vtkLogger::EndScope(id)

#define vtkLogStartScopeF(verbosity_name, id, ...)                                                 \
  vtkLogger::StartScopeF(vtkLogger::VERBOSITY_##verbosity_name, id, __FILE__, __LINE__, __VA_ARGS__)

#define vtkVLogStartScope(level, id) vtkLogger::StartScope(level, id, __FILE__, __LINE__)
#define vtkVLogStartScopeF(level, id, ...)                                                         \
  vtkLogger::StartScopeF(level, id, __FILE__, __LINE__, __VA_ARGS__)
//@}

/**
 * Convenience macro to generate an identifier string for any vtkObjectBase subclass.
 * @note do not store the returned value as it returns a char* pointer to a
 * temporary std::string that will be released as soon as it goes out of scope.
 */
#define vtkLogIdentifier(vtkobject) vtkLogger::GetIdentifier(vtkobject).c_str()

#endif
