/**
 * Copyright 2013-2022 Software Radio Systems Limited
 *
 * This file is part of srsRAN.
 *
 * srsRAN is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * srsRAN is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * A copy of the GNU Affero General Public License can be found in
 * the LICENSE file in the top-level directory of this distribution
 * and at http://www.gnu.org/licenses/.
 *
 */

#ifndef SRSLOG_LOGGER_H
#define SRSLOG_LOGGER_H

#include "srsran/srslog/log_channel.h"

namespace srslog {

namespace detail {

/// The logger_impl template class contains the common functionality to loggers.
/// Its main responsibility is to control the logging level by individually
/// enabling or disabling the logging channels that form the logger.
/// NOTE: Thread safe class.
template <typename T, typename Enum>
class logger_impl : public T
{
  static_assert(std::is_enum<Enum>::value, "Expected enum type");

  using enum_base_type           = typename std::underlying_type<Enum>::type;
  static constexpr unsigned size = static_cast<enum_base_type>(Enum::LAST) - 1;

public:
  template <typename... Args>
  explicit logger_impl(std::string id, Args&&... args) :
    T{std::forward<Args>(args)...}, logger_id(std::move(id)), channels{&args...}
  {
    static_assert(sizeof...(args) == size, "Number of levels in enum does not match number of log channels");
  }

  logger_impl(const logger_impl& other) = delete;
  logger_impl& operator=(const logger_impl& other) = delete;

  /// Returns the id string of the logger.
  const std::string& id() const { return logger_id; }

  /// Change the logging level.
  void set_level(Enum lvl)
  {
    detail::scoped_lock lock(m);

    // Disable all channels ...
    if (lvl == Enum::none) {
      for (unsigned i = 0, e = channels.size(); i != e; ++i) {
        channels[i]->set_enabled(false);
      }
      return;
    }

    // ... otherwise do it selectively.
    for (unsigned i = 0, e = channels.size(); i != e; ++i) {
      channels[i]->set_enabled(static_cast<Enum>(i + 1) <= lvl);
    }
  }

  /// Set the specified context value to all the channels of the logger.
  void set_context(uint32_t x)
  {
    detail::scoped_lock lock(m);
    for (auto channel : channels) {
      channel->set_context(x);
    }
  }

  /// Set the maximum number of bytes to can be printed in a hex dump to all the
  /// channels of the logger.
  void set_hex_dump_max_size(int x)
  {
    detail::scoped_lock lock(m);
    for (auto channel : channels) {
      channel->set_hex_dump_max_size(x);
    }
  }

private:
  /// Comparison operator for enum types, used by the set_level method.
  friend bool operator<=(Enum lhs, Enum rhs)
  {
    return static_cast<enum_base_type>(lhs) <= static_cast<enum_base_type>(rhs);
  }

private:
  const std::string                    logger_id;
  const std::array<log_channel*, size> channels;
  mutable detail::mutex                m;
};

/// Type trait to detect if T is a logger.
template <typename T>
struct is_logger : std::false_type {};
template <typename T, typename Enum>
struct is_logger<logger_impl<T, Enum> > : std::true_type {};

} // namespace detail

/// The build_logger_type type alias allows developers to define new logger
/// types in an application.
///
/// To create a new logger type simply follow these steps:
///   1) Define an enum class where each element will represent a logging level.
///     Order the elements from highest to lowest logging level. First element
///     should be "none", which represents a disabled logger.
///     The last element should be called LAST as it is a sentinel value.
///   2) Define a struct composed by only log_channel references. Declare the
///     members in the same order as done in the enum.
///   3) Define the new logger type by using the build_logger_type alias. Pass
///     the previous defined types as template parameters.
///
/// Example to declare a logger with three logging levels: error, warning and
/// info, being error the highest logging level and info the lowest:
///   1) Define the logging level enum:
///     enum class three_level_logger_levels { none, error, warning, info, LAST
///     };
///   2) Define the struct of three channels (same order as in the enum):
///     struct three_level_logger {
///       log_channel &error;
///       log_channel &warning;
///       log_channel &info;
///     };
///   3) Define the new logger type:
///     using my_new_logger =
///         build_logger_type<three_level_logger, three_level_logger_levels>;
template <typename T, typename Enum>
using build_logger_type = detail::logger_impl<T, Enum>;

///
/// Common logger types.
///

/// Basic logger with four levels.
enum class basic_levels { none, error, warning, info, debug, LAST };
struct basic_logger_channels {
  log_channel& error;
  log_channel& warning;
  log_channel& info;
  log_channel& debug;
};
using basic_logger = build_logger_type<basic_logger_channels, basic_levels>;

/// Translates a string to the corresponding logger basic level.
inline basic_levels str_to_basic_level(std::string s)
{
  std::transform(s.begin(), s.end(), s.begin(), ::toupper);

  if ("NONE" == s) {
    return basic_levels::none;
  }
  if ("ERROR" == s) {
    return basic_levels::error;
  }
  if ("WARNING" == s) {
    return basic_levels::warning;
  }
  if ("INFO" == s) {
    return basic_levels::info;
  }
  if ("DEBUG" == s) {
    return basic_levels::debug;
  }
  return basic_levels::none;
}

/// Translates a logger basic level to the corresponding string.
inline const char* basic_level_to_string(basic_levels level)
{
  switch (level) {
    case basic_levels::debug:
      return "DEBUG";
    case basic_levels::info:
      return "INFO";
    case basic_levels::warning:
      return "WARNING";
    case basic_levels::error:
      return "ERROR";
    default:
      break;
  }
  return "NONE";
}

} // namespace srslog

#endif // SRSLOG_LOGGER_H
