#include "logger.h"

#include <iostream>
#include <syncstream>

using namespace std;
using namespace core::logger;
//--------------------------------------------------------------
/* Constructor */
Logger::Logger(dBus::Bus &bus)
  : Node(bus)
{
  // Initialize the log printing loop
  runPrintingLoop();

  // Initialize the bus listener thread
  runBusListener();
}
//--------------------------------------------------------------
/* Default destructor */
Logger::~Logger()
{
  // Stop the bus listener thread if it's running
  stopBusListener();

  // Stop the log printing loop if it's running
  stopPrintingLoop();
}
//--------------------------------------------------------------
/* Run the loop that periodically prints log entries to the console */
void Logger::runPrintingLoop()
{
  /* Start the printing thread to periodically print log entries to the console.
   * The thread will continuously wait for messages and process them until a stop is requested */
  // clang-format off
  m_printingThread = std::jthread([this](const std::stop_token &stopToken)
  {
    while (!stopToken.stop_requested())
    {
      // Sleep for a short duration to avoid busy waiting and reduce CPU usage
      this_thread::sleep_for(100ms);

      // Print the log entries after processing the message
      if (m_logEntries.timer.isElapsed(LOG_PRINT_INTERVAL, true))
        printLogEntries();
    } 
  });
  // clang-format on
}
//--------------------------------------------------------------
/* Stop the log printing loop */
void Logger::stopPrintingLoop()
{
  // Stop printing thread loop
  m_printingThread.request_stop();

  // Despite a jthread will automatically join in its destructor, we
  // can explicitly join here to ensure that the thread has finished
  // executing before the Logger object is destroyed.
  // This is especially important if the thread is still running and
  // may access resources that are being cleaned up in the destructor.
  // By joining the thread, we can ensure a clean shutdown of the Logger
  // and avoid potential issues with dangling threads or accessing invalid resources.
  if (m_printingThread.joinable())
    m_printingThread.join();
}
//--------------------------------------------------------------
/* Print the log entries to the console */
void Logger::printLogEntries()
{
  // Move the log entries to a local variable and clear the
  // original vector to minimize the time the mutex is locked
  std::vector<std::string> logEntries;
  {
    scoped_lock lock(m_logEntries.mtx);
    logEntries = std::move(m_logEntries.entries);
    m_logEntries.entries.clear();
  }

  for (const auto &entry : logEntries)
  {
    // Use osyncstream to ensure thread-safe output to the console
    std::osyncstream syncOut(std::cout);
    syncOut << entry << std::endl;
  }
}
//--------------------------------------------------------------
/* Process a log message received from the bus */
void Logger::processUserMessage(const LogMessage_ptr &logMessage)
{
  // Sanity check
  if (!logMessage)
    return;

  // Add the log entry to the vector of log entries
  std::scoped_lock lock(m_logEntries.mtx);
  m_logEntries.entries.push_back(logMessage->toString());
}
//--------------------------------------------------------------
/* Process a system log message received from the bus */
void Logger::processSystemMessage(const std::string &logMessage)
{
  // Sanity check
  if (logMessage.empty())
    return;

  // Add the log entry to the vector of log entries
  std::scoped_lock lock(m_logEntries.mtx);
  m_logEntries.entries.push_back(logMessage);
}
//--------------------------------------------------------------
/* Run the bus listener thread */
void Logger::runBusListener()
{
  // Subscribe to the bus in broadcast mode to receive all messages posted to the bus
  m_bus.subscribeToBroadcast(this);

  /* Start the bus listener thread to process incoming messages related to requirements.
   * The thread will continuously wait for messages and process them until a stop is requested */
  // clang-format off
  m_busListenerThread = std::jthread([this](const std::stop_token &stopToken)
  {
    while (!stopToken.stop_requested())
    {
      // Wait for a message to be received
      syncWaitForMessage();

      // Process all received messages
      while (getMessageCount() > 0)
      {
        auto message = popNextMessage();
        if (message)
          processMessageBus(message);
      }
    } 
  });
  // clang-format on
}
//--------------------------------------------------------------
/* Stop the bus listener thread */
void Logger::stopBusListener()
{
  // Stop event thread loop
  m_busListenerThread.request_stop();

  // Wake up event thread if waiting
  notifyMessageQueue();

  // Join the bus listener thread to ensure it has finished
  // executing before the Logger object is destroyed
  if (m_busListenerThread.joinable())
    m_busListenerThread.join();
}
//--------------------------------------------------------------
/* Process a message received from the bus */
void Logger::processMessageBus(const Message_ptr &message)
{
  // Check the message type and process accordingly
  const auto messageType = message->getMessageTypeID();

  if (messageType == dBus::makeID("log.message"))
  {
    // Process log message
    processUserMessage(message->as<dBus::EventMessage<api::log::LogMessage>>());
  }
  else
  {
    // Process system log message (for any other message type, we will log it as a system message)
    processSystemMessage(message->toString());
  }
}
//--------------------------------------------------------------
