/***************************************************************************//**
 *
 * \file rosa/support/log.h
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief Facility for logging.
 *
 * \note One call for the various logging macros is supposed to be used
 * for registering one log entry. That goes natural with the non-stream
 * implementations, which accept one string as argument. A more flexible way
 * for printing log entries, for example for colorizing text, is to use macros
 * providing a log stream. It is important to note, however, that the stream
 * obtained from one macro evaluation fits for printing one log entry, without
 * nested/overlapping log entry emissions and the entry being closed with a
 * newline. Should this simple recommendation not being followed, the result
 * becomes hard to read due to missing line breaks and overlapping entries.
 *
 * \todo Thread-safety is another issue, which need to be addressed for proper
 * logging.
 *
 ******************************************************************************/

#ifndef ROSA_SUPPORT_LOG_H
#define ROSA_SUPPORT_LOG_H

#include "rosa/config/config.h"

#include "rosa/support/terminal_colors.h"

#include <iostream>
#include <string>

/* ****************************************************************************
 *                                Log Levels                                  *
 * ****************************************************************************/

namespace rosa {

/// Type-safe definition of log levels, use this in code.
/// \note Keep values in sync with the corresponding preprocessor definitions.
enum class LogLevel {
  Error,       ///< Log errors only
  Warning,     ///< Like `rosa::LogLevel::Error` and also log warnings
  Info,        ///< Like `rosa::LogLevel::Warning` and also log general infos
  Debug,       ///< Like `rosa::LogLevel::Info` and also log debug infos
  Trace,       ///< Like `rosa::LogLevel::Debug` and also log trace infos
  NumLogLevels ///< Number of log levels
};

/// Converts a `rosa::LogLevel` to its textual representation.
///
/// \param logLevel the `LogLevel` to convert
///
/// \return `string` representing `logLevel`
///
/// \pre `logLevel` is valid:\code
/// logLevel != LogLevel::NumLogLevels
/// \endcode
std::string logLevelToString(const LogLevel logLevel);

/// Prints colorized tag for the given `rosa::LogLevel`.
///
/// \param os `ostream` to print to
/// \param logLevel the `LogLevel` to print tag for
///
/// \return `os` after printing a tag for `logLevel`
///
/// \pre `logLevel` is valid:\code
/// logLevel != LogLevel::NumLogLevels
/// \endcode
std::ostream &operator<<(std::ostream &os, const LogLevel logLevel);

} // End namespace rosa

/// \name Valid log level constants
/// \note Only for preprocessor definitions in this file.
/// \note Keep the defintions in sync with the values of `rosa::LogLevel`.
///@{
#define ROSA_LOG_LEVEL_ERROR                                                   \
  0 ///< Value corresponding to `rosa::LogLevel::Error`
#define ROSA_LOG_LEVEL_WARNING                                                 \
  1 ///< Value corresponding to `rosa::LogLevel::Warning`
#define ROSA_LOG_LEVEL_INFO                                                    \
  2 ///< Value corresponding to `rosa::LogLevel::Info`
#define ROSA_LOG_LEVEL_DEBUG                                                   \
  3 ///< Value corresponding to `rosa::LogLevel::Debug`
#define ROSA_LOG_LEVEL_TRACE                                                   \
  4 ///< Value corresponding to `rosa::LogLevel::Trace`
///@}


/* ****************************************************************************
 *                           Logger Implementation                            *
 * ****************************************************************************/

/// Stream to print logs to
///
/// \todo Make it configurable, e.g. printing into a file.
#define ROSA_LOG_OSTREAM std::clog

/// Prints a log message to `ROSA_LOG_OSTREAM`.
///
/// \param level `LogLevel` of the log entry
/// \param output message to print
#define ROSA_LOG_IMPL(level, output)                                           \
  do {                                                                         \
    ROSA_LOG_OSTREAM << (level) << " " << __func__ << "@" << __FILENAME__      \
                     << ":" << __LINE__ << ": " << (output) << std::endl;      \
  } while (false)

/// Returns a stream to print a log message to.
///
/// \param level `LogLevel`of the log entry that is about to be printed
#define ROSA_LOG_STREAM_IMPL(level)                                            \
  ([](void) -> std::ostream & {                                                \
    return ROSA_LOG_OSTREAM << (level) << " " << __func__ << "@"               \
                            << __FILENAME__ << ":" << __LINE__ << ": ";        \
  }())

namespace rosa {
/// Dummy `std::ostream` printing to nowhere.
extern std::ostream LogSink;
} // End namespace rosa

/// An output stream ignoring all its input.
#define ROSA_LOG_STREAM_IGNORE rosa::LogSink

/* ****************************************************************************
 *                            Logging Interface                               *
 * ****************************************************************************/

/// \name Logging interface
///
/// Preprocesser macros for convenience.
///@{

/// \def LOG_ERROR_STREAM
/// \brief Returns a stream to print a log entry of level
/// `rosa::LogLevel::Error`.
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Error`.

/// \def LOG_ERROR(output)
/// \brief Prints a log entry of level `rosa::LogLevel::Error`.
/// \param output the message to print
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Error`.

/// \def LOG_WARNING_STREAM
/// \brief Returns a stream to print a log entry of level
/// `rosa::LogLevel::Warning`.
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Warning`.

/// \def LOG_WARNING(output)
/// \brief Prints a log entry of level `rosa::LogLevel::Warning`.
/// \param output the message to print
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Warning`.

/// \def LOG_INFO_STREAM
/// \brief Returns a stream to print a log entry of level
/// `rosa::LogLevel::Info`.
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Info`.

/// \def LOG_INFO(output)
/// \brief Prints a log entry of level `rosa::LogLevel::Info`.
/// \param output the message to print
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Info`.

/// \def LOG_DEBUG_STREAM
/// \brief Returns a stream to print a log entry of level
/// `rosa::LogLevel::Debug`.
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Debug`.

/// \def LOG_DEBUG(output)
/// \brief Prints a log entry of level `rosa::LogLevel::Debug`.
/// \param output the message to print
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Debug`.

/// \def LOG_TRACE_STREAM
/// \brief Returns a stream to print a log entry of level
/// `rosa::LogLevel::Trace`.
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Trace`.

/// \def LOG_TRACE(output)
/// \brief Prints a log entry of level `rosa::LogLevel::Trace`.
/// \param output the message to print
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// `rosa::LogLevel::Trace`.

///@}

// Define logging macros if logging is enabled.
#ifdef ROSA_LOG_LEVEL

#define LOG_ERROR_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Error)
#define LOG_ERROR(output) ROSA_LOG_IMPL(rosa::LogLevel::Error, output)

#if ROSA_LOG_LEVEL >= ROSA_LOG_LEVEL_WARNING
#define LOG_WARNING_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Warning)
#define LOG_WARNING(output) ROSA_LOG_IMPL(rosa::LogLevel::Warning, output)
#endif

#if ROSA_LOG_LEVEL >= ROSA_LOG_LEVEL_INFO
#define LOG_INFO_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Info)
#define LOG_INFO(output) ROSA_LOG_IMPL(rosa::LogLevel::Info, output)
#endif

#if ROSA_LOG_LEVEL >= ROSA_LOG_LEVEL_DEBUG
#define LOG_DEBUG_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Debug)
#define LOG_DEBUG(output) ROSA_LOG_IMPL(rosa::LogLevel::Debug, output)
#endif

#if ROSA_LOG_LEVEL >= ROSA_LOG_LEVEL_TRACE
#define LOG_TRACE_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Trace)
#define LOG_TRACE(output) ROSA_LOG_IMPL(rosa::LogLevel::Trace, output)
#endif

#endif // defined ROSA_LOG_LEVEL

// Define all disabled logging features as void.
#ifndef LOG_ERROR
#define LOG_ERROR_STREAM ROSA_LOG_STREAM_IGNORE
#define LOG_ERROR(output) ROSA_IGNORE_UNUSED(output)
#endif

#ifndef LOG_WARNING
#define LOG_WARNING_STREAM ROSA_LOG_STREAM_IGNORE
#define LOG_WARNING(output) ROSA_IGNORE_UNUSED(output)
#endif

#ifndef LOG_INFO
#define LOG_INFO_STREAM ROSA_LOG_STREAM_IGNORE
#define LOG_INFO(output) ROSA_IGNORE_UNUSED(output)
#endif

#ifndef LOG_DEBUG
#define LOG_DEBUG_STREAM ROSA_LOG_STREAM_IGNORE
#define LOG_DEBUG(output) ROSA_IGNORE_UNUSED(output)
#endif

#ifndef LOG_TRACE
#define LOG_TRACE_STREAM ROSA_LOG_STREAM_IGNORE
#define LOG_TRACE(output) ROSA_IGNORE_UNUSED(output)
#endif

#endif // ROSA_SUPPORT_LOG_H

