#include "Log.hh"

// Vivy::LogLevel utility implementation
namespace Vivy
{
const std::string_view
LogLevel::toStdStringView(const LogLevel::Level lvl) noexcept
{
    return LevelsStringViews[static_cast<Array::size_type>(lvl)];
}
}

// Vivy::LogSinkDispatcher and child classes implementation
namespace Vivy
{
LogSinkDispatcher::~LogSinkDispatcher() noexcept {}

const std::string_view
StderrLogSinkDispatcher::trunkFileName(const char *fileName) noexcept
{
    using size_type = std::string_view::size_type;

    static constexpr char basePrefix[]       = "/src/";
    static constexpr size_type basePrefixLen = (sizeof(basePrefix) / sizeof(char)) - 1;

    const std::string_view fileNameView{ fileName };
    const size_type basePath = fileNameView.rfind(basePrefix);
    return std::string_view{ fileNameView.data() + basePath + basePrefixLen,
                             fileNameView.data() + fileNameView.size() };
}

std::string
StderrLogSinkDispatcher::reduceFileName(const std::string_view fileName) noexcept
{
    return std::filesystem::path(fileName).lexically_normal();
}

StderrLogSinkDispatcher::StderrLogSinkDispatcher() noexcept
    : LogSinkDispatcher(std::string_view{ "stderr" })
{
}

void
StderrLogSinkDispatcher::handleLogMessage(const std::string_view category,
                                          const LogMessage &msg) noexcept
{
    std::cerr << "#(" << reduceFileName(trunkFileName(msg.getHeader().fileName)) << " +"
              << msg.getHeader().lineNumberInFile << " | "
              << LogLevel::toStdStringView(msg.getHeader().severity) << " -> " << category << ") "
              << msg.getTextBuffer() << '\n';
}
}

// Vivy::LogSink implementation
namespace Vivy
{
// Flush all messages before exiting
LogSink::~LogSink() noexcept { flush(); }

// Get the log message from the logger and add it to the sink's queue.
void
LogSink::recieveLogMessage(const Logger *const logger, LogMessage &&msg) noexcept
{
    const std::lock_guard<std::mutex> messageQueueLockGuard(messageQueueLock);
    messageQueue.emplace_back(std::make_tuple<const std::string_view, LogMessage>(
        logger->getCategoryView(), std::move(msg.sink())));
}

// Flush all LogMessages to all of the LogSinkDispatchers. Also clear the message queue.
void
LogSink::flush() noexcept
{
    const std::lock_guard<std::mutex> messageQueueLockGuard(messageQueueLock);
    for (std::shared_ptr<LogSinkDispatcher> dispatcher : logDispatchers) {
        for (const auto &[category, msg] : messageQueue)
            dispatcher->handleLogMessage(category, msg);
    }
    messageQueue.clear();
}

std::shared_ptr<LogSink>
LogSink::newSink() noexcept
{
    struct makeSharedEnabler : public LogSink {
        // NOTE: For make_shared with private CTor
    };
    return std::make_shared<makeSharedEnabler>();
}

void
LogSink::registerLogDispatcher(std::shared_ptr<LogSinkDispatcher> dispatcher) noexcept
{
    logDispatchers.push_back(dispatcher);
}

void
LogSink::registerLogger(std::shared_ptr<Logger> ptr) noexcept
{
    loggers.push_back(ptr);
}
}

// Vivy::Logger implementation
namespace Vivy
{
void
Logger::sendLogMessage(LogMessage &&msg) const noexcept
{
    parentLogSink->recieveLogMessage(this, std::move(msg));
}

LogMessage
Logger::logEvent(const char *fileName, const char *functionName, const int lineNumber,
                 const LogLevel::Level logSeverity) noexcept
{
    return LogMessage(this, LogMessage::Header{ .fileName         = fileName,
                                                .functionName     = functionName,
                                                .severity         = logSeverity,
                                                .lineNumberInFile = lineNumber });
}
}

// Vivy::LogMessage implementation
namespace Vivy
{
LogMessage::LogMessage(const Logger *const logger, const LogMessage::Header hdr) noexcept
    : messageHeader(hdr)
    , parentLogger(logger)
{
}

LogMessage::LogMessage(LogMessage &&other) noexcept
    : messageHeader(other.messageHeader)
    , parentLogger(other.parentLogger)
{
    std::memcpy(this, &other, sizeof(LogMessage));
    std::memset(&other, 0, sizeof(LogMessage));
}

LogMessage &&
LogMessage::sink() noexcept
{
    parentLogger = nullptr;
    return std::move(*this);
}

LogMessage &
LogMessage::operator<<(const std::string &msg) noexcept
{
    return (*this << msg.c_str());
}

LogMessage &
LogMessage::operator<<(const QVariant &variant) noexcept
{
    return (*this << variant.toString());
}

LogMessage &
LogMessage::operator<<(const QString &msg) noexcept
{
    return (*this << msg.toStdString());
}

LogMessage &
LogMessage::operator<<(const QFileInfo &fileInfo) noexcept
{
    return (*this << "QFileInfo{ " << fileInfo.absoluteFilePath() << "}");
}

LogMessage &
LogMessage::operator<<(const double *ptr) noexcept
{
    return (*this << "Pointer{ double, " << pointerToString<double>(ptr) << " }");
}

LogMessage &
LogMessage::operator<<(const unsigned char c) noexcept
{
    return (*this << static_cast<char>(c));
}

LogMessage &
LogMessage::operator<<(const unsigned int i) noexcept
{
    return (*this << static_cast<unsigned long>(i));
}

LogMessage &
LogMessage::operator<<(const unsigned long i) noexcept
{
    return (*this << std::to_string(i));
}

LogMessage &
LogMessage::operator<<(const unsigned long long i) noexcept
{
    return (*this << std::to_string(i));
}

LogMessage &
LogMessage::operator<<(const long long i) noexcept
{
    return (*this << std::to_string(i));
}

LogMessage &
LogMessage::operator<<(const int i) noexcept
{
    return (*this << static_cast<long>(i));
}

LogMessage &
LogMessage::operator<<(const long i) noexcept
{
    return (*this << std::to_string(i));
}

LogMessage &
LogMessage::operator<<(const std::string_view strv) noexcept
{
    for (std::size_t i = 0; (i < strv.size()) && (indexInArray < messageBufferLength - 1);
         ++i, ++indexInArray) {
        textBuffer[indexInArray] = strv[i];
    }
    textBuffer[indexInArray] = '\0';
    return *this;
}

LogMessage &
LogMessage::operator<<(const char *str) noexcept
{
    const std::size_t length = strlen(str);
    for (std::size_t i = 0; (i < length) && (indexInArray < messageBufferLength - 1);
         ++i, ++indexInArray) {
        textBuffer[indexInArray] = str[i];
    }
    textBuffer[indexInArray] = '\0';
    return *this;
}

LogMessage &
LogMessage::operator<<(const char c) noexcept
{
    if (indexInArray < messageBufferLength - 1) {
        textBuffer[indexInArray] = c;
        ++indexInArray;
        textBuffer[indexInArray] = '\0';
    }
    return *this;
}

const std::string_view
LogMessage::getTextBuffer() const noexcept
{
    const char *txt = textBuffer.data();
    return std::string_view{ txt, strlen(txt) };
}

LogMessage::~LogMessage() noexcept
{
    if (parentLogger)
        parentLogger->sendLogMessage(std::move(*this));
}
}