// -*- mode: c++; -*-

#include <Rcpp/Lighter>

// In order to survive on CRAN, we have to cope with non-C++20 compilation
#if  __cplusplus >= 202002L

#include <spdlite/logger.h>     		// for spdlite
#if defined(RSPDLITE_NULL_SINK)
    #include <spdlite/sinks/null_sink.h>      	// for null_sink, if used
#endif
#if defined(RSPDLITE_FILE_SINK)
    #include <spdlite/sinks/file_sink.h>      	// for file_sink, if used
#endif

namespace rspdlite {

    using namespace spdlite;

    constexpr int max_args = 15;            	// Arbitrary but 'smallish', ends recursion

    // Recursively convert a vector of strings (a constraint we have for the R interface based
    // on its SEXP types and the .Call() API) to variadic args use to call the formatter. We
    // dispatch either to the C++20 std::format (as an opt-in) or use fmt::format as the default
    // just like spdlite does.
    template<class... Args>
    inline std::string forward_to_format(const std::string s,
                                         const std::vector<std::string>& v,
                                         const Args... args) {
        // Once all vector arguments are in Args, call format(); maximum size is checked
        // and enforced by caller formatter_() in src/formatter.cpp
        if (v.size() == sizeof...(args)) {
            #if defined(SPDLITE_USE_STD_FORMAT) && __cplusplus >= 202002L
                return std::vformat(std::string_view(s), std::make_format_args(args...));
            #elif __cplusplus >= 202002L
                return fmt::format(fmt::runtime(s), args...);
            #else
                #error "Compiling spdlite requires C++20"
            #endif
        }
        // If not all args used loop, recursively loop from first (with index zero) to last (with
        // index sizeof(args)) with "loop termination" when the preceding condition hits
        if constexpr(sizeof...(args) < max_args) {
            return forward_to_format(s, v, args..., v[sizeof...(args)]);
        }
        return std::string(""); // not reached
    }


    // Very minimal sink for R, in the rspdlite namespace of the package
    struct r_sink {
        explicit r_sink() {}
        void write(const log_msg &msg) {
            Rcpp::Rcout << std::string_view(msg.formatted.data(), msg.formatted.size());
        }
        void flush() {
            Rcpp::Rcout << std::flush;
        }
    };

    // double quoting trick to get content instead of macro when quoting macro for filename
    #if defined(RSPDLITE_NULL_SINK)
        inline spdlite::logger_st logger(spdlite::null_sink{}); // select null sink
    #elif defined(RSPDLITE_FILE_SINK)
        #define Q(x) #x
        #define QUOTE(x) Q(x)
        constexpr char* filename = QUOTE(RSPDLITE_FILE_SINK);
        inline spdlite::logger_st logger(spdlite::file_sink{filename, true});
        #undef Q
        #undef QUOTE
    #else
        inline spdlite::logger_st logger(rspdlite::r_sink{}); // default sink for R
    #endif

    template <typename... Args>
    inline void log_trace(format_string_t<Args...> fmt, Args&&... args) {
        logger.log(level::trace, fmt, std::forward<Args>(args)...);
    }
    template <typename... Args>
    inline void log_debug(format_string_t<Args...> fmt, Args&&... args) {
        logger.log(level::debug, fmt, std::forward<Args>(args)...);
    }
    template <typename... Args>
    inline void log_info(format_string_t<Args...> fmt, Args&&... args) {
        logger.log(level::info, fmt, std::forward<Args>(args)...);
    }
    template <typename... Args>
    inline void log_warn(format_string_t<Args...> fmt, Args&&... args) {
        logger.log(level::warn, fmt, std::forward<Args>(args)...);
    }
    template <typename... Args>
    inline void log_error(format_string_t<Args...> fmt, Args&&... args) {
        logger.log(level::err, fmt, std::forward<Args>(args)...);
    }
    template <typename... Args>
    inline void log_critical(format_string_t<Args...> fmt, Args&&... args) {
        logger.log(level::critical, fmt, std::forward<Args>(args)...);
    }

    inline void set_log_level(level lvl) { logger.set_log_level(lvl); }

    inline enum level get_log_level() { return logger.get_log_level(); }

    inline const char* levelToString(enum level lvl) {
        static constexpr std::array<const char*, 8> names = {
            "trace", "debug", "info", "warn", "error", "critical", "off", "n_levels"
        };
        return names[static_cast<size_t>(lvl)];
    }

    inline enum level stringToLevel(const std::string& str) {
        static const std::unordered_map<std::string, enum level> levelMap = {
            {"trace",    level::trace},
            {"debug",    level::debug},
            {"info",     level::info},
            {"warn",     level::warn},
            {"error",    level::err},
            {"critical", level::critical},
            {"off",      level::off},
            {"n_levels", level::n_levels}
        };
        auto it = levelMap.find(str);
        if (it != levelMap.end()) {
            return it->second;
        }
        Rcpp::stop("Unknown level ", str);
        return level::n_levels; // not reached
   }

   inline const char* timeprecisionToString(enum time_precision prc) {
        static constexpr std::array<const char*, 4> names = { "none", "ms", "us", "ns" };
        return names[static_cast<size_t>(prc)];
   }

   inline enum time_precision stringToTimeprecision(const std::string& str) {
        static const std::unordered_map<std::string, enum time_precision> levelMap = {
            {"none", time_precision::none},
            {"ms",   time_precision::ms},
            {"us",   time_precision::us},
            {"ns",   time_precision::ns},
        };
        auto it = levelMap.find(str);
        if (it != levelMap.end()) {
            return it->second;
        }
        Rcpp::stop("Unknown time precision ", str);
        return time_precision::none; // not reached
   }

   // also see pair of setter / getter above using 'enum level'
   inline void        set_level(const std::string& s) { logger.set_log_level(stringToLevel(s));       }
   inline std::string get_level()                     { return levelToString(logger.get_log_level()); }

   inline void        set_name(const std::string& s) { logger.set_name(s);                            }
   inline std::string get_name()                     { return std::string(logger.get_name());         }
}


#else
    // C++17 and below fallback to permit compilation
    namespace rspdlite {
        constexpr int max_args = 15;            	// Arbitrary but 'smallish', ends recursion
        enum class level : int { off = 0, on = 1, info = 2 };
        inline std::string forward_to_format(const std::string, const std::vector<std::string>&) {
             Rcpp::message(Rcpp::wrap("No spdlite support without C++20. Sorry."));
             return std::string("");
        }
        inline enum level stringToLevel(std::string) { return level::info; }
        inline std::string levelToString(enum level) { return std::string("info"); }
        struct dummy_logger {
            inline void trace(std::string_view) {}
            inline void debug(std::string_view) {}
            inline void info(std::string_view) {}
            inline void warn(std::string_view) {}
            inline void error(std::string_view) {}
            inline void critical(std::string_view) {}
            inline void set_log_level(enum level) {}
            inline enum level get_log_level() { return level::info; }
            inline void set_level(std::string) {}
            inline std::string get_level() { return std::string(); }
            inline void set_name(std::string) {}
            inline std::string get_name() { return std::string(); }
        };
        dummy_logger logger;
    }
#endif
