#include "node.h"

#include "bus.h"

using namespace std;
using namespace dBus;
//--------------------------------------------------------------
/* Constructor */
Node::Node(Bus &bus)
  : m_bus(bus)
{
}
//--------------------------------------------------------------
/* Default destructor */
Node::~Node()
{
  unsubscribeAll();

  // Notify the node thread to stop waiting for messages and exit the loop (if applicable)
  notifyMessageQueue();
}
//--------------------------------------------------------------
/* Subscribe to a specific message type */
void Node::subscribe(const HashID &eventType)
{
  m_bus.subscribe(eventType, this);
}
//--------------------------------------------------------------
/* Unsubscribe from a specific message type */
void Node::unsubscribe(const HashID &eventType)
{
  m_bus.unsubscribe(eventType, this);
}
//--------------------------------------------------------------
/* Unsubscribe from all message types */
void Node::unsubscribeAll()
{
  m_bus.unsubscribeAll(this);
}
//--------------------------------------------------------------
/* Check if is subscribed to a specific message type */
bool Node::isSubscribed(const HashID &eventType) const
{
  return m_bus.isSubscribed(eventType, this);
}
//--------------------------------------------------------------
/* Wait for a message to be received */
void Node::syncWaitForMessage()
{
  // Check if there are already messages in the queue before waiting
  // to avoid missing notifications if a message is received between
  // the last check and the wait call
  {
    std::scoped_lock lock(m_dBusMessages.mtx);
    if (!m_dBusMessages.messageQueue.empty())
      return;     // Messages already in the queue, no need to wait
  }

  // Wait until a message is received (the bus will call notifyMessageFromBus()
  // to wake up the node thread when a message is posted)
  m_nodeWaitFlag = false;
  m_nodeWaitFlag.wait(false);
}
//--------------------------------------------------------------
/* Post a message to the bus */
void Node::postMessage(const std::shared_ptr<Message> &message) const
{
  m_bus.post(message);
}
//--------------------------------------------------------------
/* Get the maximum age of messages in the queue */
std::chrono::microseconds Node::getMessageMaxAge() const
{
  std::scoped_lock lock(m_dBusMessages.mtx);

  // If the queue is empty, return a default value (e.g., zero)
  if (m_dBusMessages.messageQueue.empty())
    return {};

  // Get the age of the oldest message in the queue
  // We can assume that the messages are ordered by age, so we can check the front of the queue
  const auto &oldestMessage = m_dBusMessages.messageQueue.front();
  return oldestMessage->getAge();
}
//--------------------------------------------------------------
/* Get the number of received messages */
size_t Node::getMessageCount() const
{
  std::scoped_lock lock(m_dBusMessages.mtx);
  return m_dBusMessages.messageQueue.size();
}
//--------------------------------------------------------------
/* Get the next message from the queue */
std::shared_ptr<Message> Node::popNextMessage()
{
  std::scoped_lock lock(m_dBusMessages.mtx);
  if (m_dBusMessages.messageQueue.empty())
    return nullptr;

  auto event = m_dBusMessages.messageQueue.front();
  m_dBusMessages.messageQueue.pop();

  return event;
}
//--------------------------------------------------------------
/* Receive a message from the bus */
void Node::receiveMessageFromBus(std::shared_ptr<Message> message)
{
  std::scoped_lock lock(m_dBusMessages.mtx);
  m_dBusMessages.messageQueue.push(std::move(message));

  notifyMessageQueue();
}
//--------------------------------------------------------------
/* Notify the node of a new message from the bus */
void Node::notifyMessageQueue()
{
  m_nodeWaitFlag.store(true);
  m_nodeWaitFlag.notify_one();
}
//--------------------------------------------------------------
