#include "backend.h"

#include <yandex/maps/wiki/threadutils/threadedqueue.hpp>
#include <yandex/maps/pylogger/pylogger.h>
#include <yandex/maps/pylogger/helpers.h>
#include <maps/libs/log8/include/log8.h>

#include <boost/python.hpp>

#include <thread>

namespace {

struct LogMessage
{
    time_t time;
    maps::log8::Level level;
    std::string text;
};

namespace bp = boost::python;

maps::pylogger::LogLevel pythonLogLevel(const maps::log8::Level& level)
{
    switch (level) {
    case maps::log8::Level::DEBUG:
        return maps::pylogger::Debug;
    case maps::log8::Level::INFO:
        return maps::pylogger::Info;
    case maps::log8::Level::WARNING:
        return maps::pylogger::Warning;
    case maps::log8::Level::ERROR:
        return maps::pylogger::Error;
    case maps::log8::Level::FATAL:
        return maps::pylogger::Critical;
    default:
        return maps::pylogger::Debug;
    }
}

class PythonLogBackend: public maps::log8::Backend
{
public:
    explicit PythonLogBackend(std::string loggerName)
        : loggingModule_(bp::import("logging"))
        , loggerName_(std::move(loggerName))
        , loggerThread_(std::bind(&PythonLogBackend::loggerWorker, this))
    { }

    ~PythonLogBackend()
    {
        queue_.finish();
        maps::pylogger::PyThreadSaver ts;
        loggerThread_.join();
    }

    void put(const maps::log8::Message& msg) override
    {
        LogMessage m{msg.time(), msg.level(), msg.text()};
        queue_.push(m);
    }

private:
    void logMessage(const LogMessage& msg)
    {
        boost::python::object globals = loggingModule_.attr("__dict__");
        boost::python::object locals = boost::python::dict();
        locals["msg"] = msg.text;
        locals["level"] = static_cast<int>(pythonLogLevel(msg.level));
        locals["time"] = msg.time;

        std::string logCommand =
            "getLogger('" + loggerName_ + "').log("
            + "level, msg, extra={'ymlog_created': time})";
        boost::python::eval(logCommand.c_str(), globals, locals);
    }

    void loggerWorker()
    {
        try {
            maps::wiki::ThreadedQueue<LogMessage>::RawContainer curItems;
            do {
                queue_.popAll(curItems);

                {
                    maps::pylogger::PyGILStateLocker gl;
                    for (const auto& msg : curItems) {
                        logMessage(msg);
                    }
                }
            } while (!curItems.empty());
        } catch(...) { }
    }

private:
    maps::wiki::ThreadedQueue<LogMessage> queue_;
    bp::object loggingModule_;
    std::string loggerName_;
    std::thread loggerThread_;
};

}

namespace maps {
namespace wiki {

void setPythonLogBackend(std::string loggerName)
{
    log8::setBackend(
        std::shared_ptr<PythonLogBackend>(
            new PythonLogBackend(std::move(loggerName))));
}


void resetLogBackend()
{
    log8::setBackend(log8::toStderr());
}

}
}
