/*******************************************************************************
 *
 * File:     log.h
 *
 * Contents: Facility for logging
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 ******************************************************************************/

#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>

// NOTE: One call for the various logging macros below 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 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.
// FIXME: Thread-safety is another issue, which need to be addressed for proper
// logging.

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

namespace rosa {

// Type-safe definition of log levels, use this in code.
// NOTE: Keep values in sync with the corresponding preprocessor definitions
// below.
enum class LogLevel {
  Error,
  Warning,
  Info,
  Debug,
  Trace,
  // Number of log levels:
  NumLogLevels
};

// Converts a LogLevel to its string representation.
// PRE: logLevel != LogLevel::NumLogLevels
std::string logLevelToString(const LogLevel logLevel);

// Prints colorized tag for the given LogLevel.
// PRE: logLevel != LogLevel::NumLogLevels
std::ostream &operator<<(std::ostream &os, const LogLevel logLevel);

} // End namespace rosa

// Valid log levels, only for preprocessor definitions below.
// NOTE: Keep in sync with the values of enum class rosa::LogLevel.
#define ROSA_LOG_LEVEL_ERROR 0
#define ROSA_LOG_LEVEL_WARNING 1
#define ROSA_LOG_LEVEL_INFO 2
#define ROSA_LOG_LEVEL_DEBUG 3
#define ROSA_LOG_LEVEL_TRACE 4

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

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

// Simple logging implementation printing a string message.
#define ROSA_LOG_IMPL(level, output)                                           \
  do {                                                                         \
    ROSA_LOG_OSTREAM << (level) << " " << __func__ << "@" << __FILENAME__      \
                     << ":" << __LINE__ << ": " << (output) << std::endl;      \
  } while (false)

// Simple logging implementation providing a stream to print to.
#define ROSA_LOG_STREAM_IMPL(level)                                            \
  ([](void) -> std::ostream & {                                                \
    return ROSA_LOG_OSTREAM << (level) << " " << __func__ << "@"               \
                            << __FILENAME__ << ":" << __LINE__ << ": ";        \
  }())

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

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

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

// 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

