//===-- rosa/support/log.h --------------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/support/log.h
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \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 <ostream>
#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 \c rosa::LogLevel::Error and also log warnings
  Info,        ///< Like \c rosa::LogLevel::Warning and also log general infos
  Debug,       ///< Like \c rosa::LogLevel::Info and also log debug infos
  Trace,       ///< Like \c rosa::LogLevel::Debug and also log trace infos
  NumLogLevels ///< Number of log levels
};

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

/// Prints colorized tag for the given \c rosa::LogLevel.
///
/// \param [in,out] OS \c std::ostream to print to
/// \param logLevel \c rosa::LogLevel to print tag for
///
/// \return \p OS after printing a tag for \p logLevel
///
/// \pre \p 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 \c rosa::LogLevel.
///@{
#define ROSA_LOG_LEVEL_ERROR                                                   \
  0 ///< Value corresponding to \c rosa::LogLevel::Error
#define ROSA_LOG_LEVEL_WARNING                                                 \
  1 ///< Value corresponding to \c rosa::LogLevel::Warning
#define ROSA_LOG_LEVEL_INFO                                                    \
  2 ///< Value corresponding to \c rosa::LogLevel::Info
#define ROSA_LOG_LEVEL_DEBUG                                                   \
  3 ///< Value corresponding to \c rosa::LogLevel::Debug
#define ROSA_LOG_LEVEL_TRACE                                                   \
  4 ///< Value corresponding to \c 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 \c ROSA_LOG_OSTREAM.
///
/// \param level \c rosa::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) << '\n';           \
  } while (false)

/// Returns a stream to print a log message to.
///
/// \param level \c rosa::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 \c 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
/// \c rosa::LogLevel::Error.
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// \c rosa::LogLevel::Error.

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

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

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

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

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

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

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

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

/// \def LOG_TRACE(output)
/// \brief Prints a log entry of level \c rosa::LogLevel::Trace.
/// \param output the message to print
/// \note Takes effect only if RoSA is built with a log level not smaller than
/// \c 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_EXPR(output)
#endif

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

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

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

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

#endif // ROSA_SUPPORT_LOG_H
