#include "logger.hh"
#include <chrono>
#include <iostream>
#include <fstream>

namespace {

using msecs = std::chrono::milliseconds;

/// Maximal message buffer size
constexpr int maxBufferSize = 600;

/// Waiting period in miliseconds for re-check if buffer is still empty
constexpr int waitForMessage = 50;

/// Maximal waiting period for message buffer mutex
constexpr int waitForBuffer = 40;

std::string datetimeString()
{
    time_t rawtime = time(nullptr);
    auto timeinfo = localtime(&rawtime);
    char buffer [32];
    strftime(buffer, 32, "%Y.%m.%d. %H:%M:%S", timeinfo);
    return std::string(buffer);
}

std::string logLevelToString(LogLevel level)
{
    switch (level) {
        case LogLevel::Trace: return "TRACE";
        case LogLevel::Debug: return "DEBUG";
        case LogLevel::Info: return "INFO";
        case LogLevel::Warn: return "WARN";
        case LogLevel::Error: return "ERROR";
        default: return "NONE";
    }
}

std::string createPrefix(LogLevel level)
{
   return "[" + logLevelToString(level) + "] " + datetimeString() + ": ";
}

} //namespace

Logger::Logger(const std::string& fileName, LogLevel level) :
    fileName_(fileName), level_(level), thread_(&Logger::writeLog, this),
    threadRunning_(true)
{}

Logger::~Logger()
{
    threadRunning_ = false;

    if (thread_.joinable())
        thread_.join();
}

void Logger::trace(const std::string& message)
{
    if (level_ > LogLevel::Trace) return;
    pushMessage(createPrefix(LogLevel::Trace) + message);
}

void Logger::debug(const std::string& message)
{
    if (level_ > LogLevel::Debug) return;
    pushMessage(createPrefix(LogLevel::Debug) + message);
}

void Logger::info(const std::string& message)
{
    if (level_ > LogLevel::Info) return;
    pushMessage(createPrefix(LogLevel::Info) + message);
}

void Logger::warn(const std::string& message)
{
    if (level_ > LogLevel::Warn) return;
    pushMessage(createPrefix(LogLevel::Warn) + message);
}

void Logger::error(const std::string& message)
{
    if (level_ > LogLevel::Error) return;
    pushMessage(createPrefix(LogLevel::Error) + message);
}

void Logger::pushMessage(const std::string& message)
{
    if (!threadRunning_) {
        std::cout
            << "Logger thread is not running to log message: "
            << std::endl << "   " << message << std::endl;
        return;
    }

    std::unique_lock<std::timed_mutex> lock(mutex_, std::defer_lock);

    if (!lock.try_lock_for(msecs(waitForBuffer))) {
        std::cout
            << "Unable to get ownership of buffer to push message: "
            << std::endl << "   " << message << std::endl;
        return;
    }

    if (buffer_.size() > maxBufferSize) {
        std::cout
            << "Message buffer size exceeded, can not push message: "
            << std::endl << "   " << message << std::endl;
        return;
    }

    buffer_.push(message);
}

void Logger::writeLog() noexcept
{
    while (threadRunning_) {
        while (!buffer_.empty()) {
            std::string message;

            {
                std::unique_lock<std::timed_mutex> lock(
                    mutex_, std::defer_lock);

                if (!lock.try_lock_for(msecs(waitForBuffer))) {
                    std::cout
                        << "Unable to get ownership "
                        << "of buffer to take the message";
                    break;
                }

                message = buffer_.front();
                buffer_.pop();
            }

            std::ofstream stream(
                fileName_, std::ofstream::out | std::ofstream::app);

            if(stream.is_open()) {
                stream << message << std::endl;
            } else {
                std::cout
                    << "Unable to open log file to log message: "
                    << std::endl << "   " << message << std::endl;
                break;
            }
        }

        std::this_thread::sleep_for(msecs(waitForMessage));
    }
}
