#pragma once

#include "bmcweb_config.h"

#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <filesystem>
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>

namespace crow
{
enum class LogLevel
{
    Disabled = 0,
    Debug,
    Info,
    Warning,
    Error,
    Critical,
};

// Mapping of the external loglvl name to internal loglvl
constexpr std::array<std::pair<std::string_view, crow::LogLevel>, 7>
    mapLogLevelFromName{{{"disabled", crow::LogLevel::Disabled},
                         {"enabled", crow::LogLevel::Debug},
                         {"debug", crow::LogLevel::Debug},
                         {"info", crow::LogLevel::Info},
                         {"warning", crow::LogLevel::Warning},
                         {"error", crow::LogLevel::Error},
                         {"critical", crow::LogLevel::Critical}}};

constexpr crow::LogLevel getLogLevelFromName(std::string_view name)
{
    const auto* iter =
        std::find_if(begin(mapLogLevelFromName), end(mapLogLevelFromName),
                     [&name](const auto& v) { return v.first == name; });
    if (iter != end(mapLogLevelFromName))
    {
        return iter->second;
    }
    return crow::LogLevel::Disabled;
}

// configured bmcweb LogLevel
constexpr crow::LogLevel bmcwebCurrentLoggingLevel =
    getLogLevelFromName(bmcwebLoggingLevel);

class Logger
{
  private:
    //
    static std::string timestamp()
    {
        std::string date;
        date.resize(32, '\0');
        time_t t = time(nullptr);

        tm myTm{};

        gmtime_r(&t, &myTm);

        size_t sz = strftime(date.data(), date.size(), "%Y-%m-%d %H:%M:%S",
                             &myTm);
        date.resize(sz);
        return date;
    }

  public:
    Logger([[maybe_unused]] const std::string& prefix,
           [[maybe_unused]] const std::string& filename,
           [[maybe_unused]] const size_t line)
    {
        stringstream << "(" << timestamp() << ") [" << prefix << " "
                     << std::filesystem::path(filename).filename() << ":"
                     << line << "] ";
    }
    ~Logger()
    {
        stringstream << std::endl;
        std::cerr << stringstream.str();
    }

    Logger(const Logger&) = delete;
    Logger(Logger&&) = delete;
    Logger& operator=(const Logger&) = delete;
    Logger& operator=(const Logger&&) = delete;

    //
    template <typename T>
    Logger& operator<<([[maybe_unused]] const T& value)
    {
        // Somewhere in the code we're implicitly casting an array to a
        // pointer in logging code. It's non-trivial to find,
        // so disable the check here for now
        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
        stringstream << value;
        return *this;
    }

    constexpr static LogLevel getCurrentLogLevel()
    {
        return bmcwebCurrentLoggingLevel;
    }

    constexpr static bool isLoggingEnabled()
    {
        return getCurrentLogLevel() >= crow::LogLevel::Debug;
    }

    constexpr static bool checkLoggingLevel(const LogLevel level)
    {
        return isLoggingEnabled() && (getCurrentLogLevel() <= level);
    }

  private:
    //
    std::ostringstream stringstream;
};
} // namespace crow

// The logging functions currently use macros.  Now that we have c++20, ideally
// they'd use source_location with fixed functions, but for the moment, disable
// the check.

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define BMCWEB_LOG_CRITICAL                                                    \
    if constexpr (crow::Logger::checkLoggingLevel(crow::LogLevel::Critical))   \
    crow::Logger("CRITICAL", __FILE__, __LINE__)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define BMCWEB_LOG_ERROR                                                       \
    if constexpr (crow::Logger::checkLoggingLevel(crow::LogLevel::Error))      \
    crow::Logger("ERROR", __FILE__, __LINE__)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define BMCWEB_LOG_WARNING                                                     \
    if constexpr (crow::Logger::checkLoggingLevel(crow::LogLevel::Warning))    \
    crow::Logger("WARNING", __FILE__, __LINE__)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define BMCWEB_LOG_INFO                                                        \
    if constexpr (crow::Logger::checkLoggingLevel(crow::LogLevel::Info))       \
    crow::Logger("INFO", __FILE__, __LINE__)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define BMCWEB_LOG_DEBUG                                                       \
    if constexpr (crow::Logger::checkLoggingLevel(crow::LogLevel::Debug))      \
    crow::Logger("DEBUG", __FILE__, __LINE__)