#ifndef LOG_HPP
#define LOG_HPP

#include <atomic>
#include <mutex>
#include <sstream>

enum LogLevel
{
    logNONE,
    logERROR,
    logWARNING,
    logINFO,
    logDEBUG
};

namespace osrm::util
{

class LogPolicy
{
  public:
    void Unmute();

    void Mute();

    bool IsMute() const;

    LogLevel GetLevel() const;
    void SetLevel(LogLevel level);
    void SetLevel(std::string const &level);

    static LogPolicy &GetInstance();
    static std::string GetLevels();

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

  private:
    LogPolicy() : m_is_mute(true), m_level(logINFO) {}
    std::atomic<bool> m_is_mute;
    LogLevel m_level;
};

class Log
{
  public:
    Log(LogLevel level_ = logINFO);
    Log(LogLevel level_, std::ostream &ostream);

    virtual ~Log();
    std::mutex &get_mutex();

    template <typename T> inline Log &operator<<(const T &data)
    {
        const auto &policy = LogPolicy::GetInstance();
        if (!policy.IsMute() && level <= policy.GetLevel())
        {
            stream << data;
        }
        return *this;
    }

    template <typename T> inline Log &operator<<(const std::atomic<T> &data)
    {
        const auto &policy = LogPolicy::GetInstance();
        if (!policy.IsMute() && level <= policy.GetLevel())
        {
            stream << T(data);
        }
        return *this;
    }

    using manip = std::ostream &(std::ostream &);

    inline Log &operator<<(manip &m)
    {
        const auto &policy = LogPolicy::GetInstance();
        if (!policy.IsMute() && level <= policy.GetLevel())
        {
            stream << m;
        }
        return *this;
    }

  private:
    void Init();

  protected:
    const LogLevel level;
    std::ostringstream buffer;
    std::ostream &stream;
};

/**
 * Modified logger - this one doesn't buffer - it writes directly to stdout,
 * and the final newline is only printed when the object is destructed.
 * Useful for logging situations where you don't want to newline right away
 */
class UnbufferedLog : public Log
{
  public:
    UnbufferedLog(LogLevel level_ = logINFO);
};
} // namespace osrm::util

#endif /* LOG_HPP */
