#include "../include/logger.h"

#include <array>
#include <memory>
#include <sstream>
#include <thread>
#include <vector>

namespace leveldb_rs {

RsLogger::RsLogger(void* rust_logger) noexcept : rust_logger_(rust_logger) {}

void RsLogger::Logv(const char* format, std::va_list arguments) {
  // Make sure logline records the time as close to the Logv() call as possible.
  auto logline = std::unique_ptr<leveldb_rs_log_line_t, void (*)(leveldb_rs_log_line_t*)>(
      leveldb_rs_logline_create(rust_logger_), leveldb_rs_logline_destroy);
  if (logline == nullptr) {
    return;
  }

  // Record the thread ID
  std::ostringstream thread_stream;
  thread_stream << std::this_thread::get_id();
  std::string thread_id = thread_stream.str();
  if (thread_id.size() > kMaxThreadIdSize) {
    thread_id.resize(kMaxThreadIdSize);
  }

  int len;
  // Try with stack-allocated buffer first
  std::array<char, kStackBufferSize> stack_buffer;
  {
    va_list args_copy;
    va_copy(args_copy, arguments);
    len = std::vsnprintf(stack_buffer.data(), kStackBufferSize, format, args_copy);
    va_end(args_copy);
  }

  if (len < kStackBufferSize && len >= 0) {
    leveldb_rs_logline_process(logline.get(), thread_id.c_str(), stack_buffer.data());
  } else if (len >= kStackBufferSize) {
    // heap-allocated buffer
    int dynamic_buffer_size = len + 1; // with extra "\0" terminator in buffer
    std::vector<char> dynamic_buffer(dynamic_buffer_size);
    {
      va_list args_copy;
      va_copy(args_copy, arguments);
      len = std::vsnprintf(dynamic_buffer.data(), dynamic_buffer_size, format, args_copy);
      va_end(args_copy);
    }
    // The dynamically-allocated buffer was incorrectly sized. This should
    // not happen, assuming a correct implementation of std::(v)snprintf.
    // Fail in tests, recover by truncating the log message in production.
    assert(len == dynamic_buffer_size - 1);
    leveldb_rs_logline_process(logline.get(), thread_id.c_str(), dynamic_buffer.data());
  } else {
    // len < 0, format error
    // ignored, like other implementations
  }
}
} // namespace leveldb_rs

// Create a new logger instance with rust_logger pointer
extern "C" leveldb_logger_t* leveldb_rs_logger_create(void* rust_logger) { // NOLINT(modernize-use-trailing-return-type)
  assert(rust_logger != nullptr);
  auto rs_logger = std::make_unique<leveldb_rs::RsLogger>(rust_logger);
  auto logger = std::make_unique<leveldb_logger_t>();
  logger->rep = rs_logger.release();
  return logger.release();
}

// Destroy the logger instance and free associated resources
extern "C" void leveldb_rs_logger_destroy(leveldb_logger_t* logger) {
  if (logger != nullptr) {
    delete static_cast<leveldb_rs::RsLogger*>(logger->rep);
    delete logger;
  }
}
