/* -*- c++ -*- */
/*
 * Copyright 2012-2013 Free Software Foundation, Inc.
 * Copyright 2021 Marcus Müller
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifndef INCLUDED_GR_LOGGER_H
#define INCLUDED_GR_LOGGER_H

/*!
 * \ingroup logging
 * \brief GNU Radio logging wrapper
 *
 */
#ifdef DISABLE_LOGGER_H
// pygccxml as of v2.2.1 has a difficult time parsing headers that
// include spdlog or format
// Since it only needs the top level header info, this is a hack to not
// transitively include anything logger related when parsing the
// headers
#include <memory>
namespace gr {
using logger_ptr = std::shared_ptr<void>;
}
#else

// Since this file is included in *all* gr::blocks, please make sure this list of includes
// keeps as short as possible; if anything is needed only by the implementation in
// buffer.cc, then only include it there
#include <gnuradio/api.h>
#include <spdlog/common.h>
#include <spdlog/fmt/fmt.h>
#include <memory>

#include <spdlog/spdlog.h>

#include <spdlog/sinks/dist_sink.h>

#include <boost/format.hpp>

namespace gr {
using log_level = spdlog::level::level_enum;

class GR_RUNTIME_API logging
{
public:
    logging(logging const&) = delete; // delete copy ctor, this is a singleton class
    void operator=(logging const&) = delete; // can't assign to singleton class
    static logging& singleton();             //! \brief get the singleton

    //! \brief get the default logging level
    inline log_level default_level() const { return _default_level; }

    //! \brief get the debug logging level
    inline log_level debug_level() const { return _debug_level; }
    spdlog::sink_ptr default_backend() const;
    //! \brief adds a logging sink
    void add_default_sink(const spdlog::sink_ptr& sink);
    //! \brief adds a debugging sink
    void add_debug_sink(const spdlog::sink_ptr& sink);
    //! \brief add a default-constructed console sink to the default logger
    void add_default_console_sink();
    //! \brief add a default-constructed console sink to the debugging logger
    void add_debug_console_sink();

    static constexpr const char* default_pattern = "%n :%l: %v";

private:
    logging();
    const log_level _default_level, _debug_level;
    std::shared_ptr<spdlog::sinks::dist_sink_mt> _default_backend, _debug_backend;
};

/*!
 * \brief GR_LOG macros
 * \ingroup logging
 *
 * These macros wrap the standard LOG4CPP_LEVEL macros.  The available macros
 * are:
 *  LOG_DEBUG
 *  LOG_INFO
 *  LOG_WARN
 *  LOG_TRACE
 *  LOG_ERROR
 *  LOG_ALERT
 *  LOG_CRIT
 *  LOG_FATAL
 *  LOG_EMERG
 */

/********************* Start  Classes and Methods for Python ******************/
/*!
 * \brief Logger class for referencing loggers in python.  Not
 * needed in C++ (use macros) Wraps and manipulates loggers for
 * python as python has no macros
 * \ingroup logging
 *
 */
class GR_RUNTIME_API logger
{
private:
    /*! \brief pointer to logger associated with this wrapper class */
    std::string _name;
    using underlying_logger_ptr = std::shared_ptr<spdlog::logger>;

public:
    /*!
     * \brief constructor Provide name of logger to associate with this class
     * \param logger_name Name of logger associated with class
     */
    logger(const std::string& logger_name);

    /*! \brief Destructor */
    // FIXME implement or = default
    ~logger() = default;

    underlying_logger_ptr d_logger;

    // Wrappers for logging macros
    /*! \brief inline function, wrapper to set the logger level */
    void set_level(const std::string& level);
    void set_level(const log_level level);

    /*! \brief inline function, wrapper to get the logger level */
    void get_level(std::string& level) const;
    const std::string get_string_level() const;
    log_level get_level() const;

    const std::string& name() const;
    void set_name(const std::string& name);

    /*! \brief inline function, wrapper for TRACE message */
    template <typename... Args>
    inline void trace(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->trace(msg, args...);
    }

    /*! \brief inline function, wrapper for DEBUG message */
    template <typename... Args>
    inline void debug(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->debug(msg, args...);
    }

    /*! \brief inline function, wrapper for INFO message */
    template <typename... Args>
    inline void info(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->info(msg, args...);
    }

    /*! \brief inline function, wrapper for INFO message, DEPRECATED */
    template <typename... Args>
    inline void notice(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->info(msg, args...);
    }

    /*! \brief inline function, wrapper for WARN message */
    template <typename... Args>
    inline void warn(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->warn(msg, args...);
    }

    /*! \brief inline function, wrapper for ERROR message */
    template <typename... Args>
    inline void error(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->error(msg, args...);
    }

    /*! \brief inline function, wrapper for CRITICAL message */
    template <typename... Args>
    inline void crit(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->critical(msg, args...);
    }

    /*! \brief inline function, wrapper for CRITICAL message, DEPRECATED */
    template <typename... Args>
    inline void alert(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->critical(msg, args...);
    }

    /*! \brief inline function, wrapper for CRITICAL message, DEPRECATED */
    template <typename... Args>
    inline void fatal(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->critical(msg, args...);
    }

    /*! \brief inline function, wrapper for CRITICAL message, DEPRECATED */
    template <typename... Args>
    inline void emerg(const spdlog::string_view_t& msg, const Args&... args)
    {
        d_logger->critical(msg, args...);
    }
};
using logger_ptr = std::shared_ptr<logger>;

/*!
 * Function to use the GR prefs files to get and setup the two
 * default loggers defined there. The loggers are unique to the
 * class in which they are called, and we pass it the \p name to
 * identify where the log message originates from. For a GNU Radio
 * block, we use 'alias()' for this value, and this is set up for us
 * automatically in gr::block.
 */
GR_RUNTIME_API bool
configure_default_loggers(gr::logger_ptr& l, gr::logger_ptr& d, const std::string& name);

} /* namespace gr */

// global logging shorthands

#define GR_LOG_TRACE(log, msg)     \
    {                              \
        log->d_logger->trace(msg); \
    }

#define GR_LOG_DEBUG(log, msg)     \
    {                              \
        log->d_logger->debug(msg); \
    }

#define GR_LOG_INFO(log, msg)     \
    {                             \
        log->d_logger->info(msg); \
    }

#define GR_LOG_NOTICE(log, msg)   \
    {                             \
        log->d_logger->info(msg); \
    }


#define GR_LOG_WARN(log, msg)     \
    {                             \
        log->d_logger->warn(msg); \
    }

#define GR_LOG_ERROR(log, msg)     \
    {                              \
        log->d_logger->error(msg); \
    }

#define GR_LOG_CRIT(log, msg)         \
    {                                 \
        log->d_logger->critical(msg); \
    }

#define GR_LOG_ALERT(log, msg)        \
    {                                 \
        log->d_logger->critical(msg); \
    }

#define GR_LOG_FATAL(log, msg)        \
    {                                 \
        log->d_logger->critical(msg); \
    }

#define GR_LOG_EMERG(log, msg)        \
    {                                 \
        log->d_logger->critical(msg); \
    }

// Helper class to allow passing of boost::format to fmt
template <>
struct fmt::formatter<boost::format> : formatter<string_view> {
    // parse is inherited from formatter<string_view>.
    template <typename FormatContext>
    auto format(const boost::format& bfmt, FormatContext& ctx)
        -> decltype(formatter<string_view>::format(bfmt.str(), ctx))
    {
        return formatter<string_view>::format(bfmt.str(), ctx);
    }
};

#endif

#endif /* INCLUDED_GR_LOGGER_H */