#include "wxNode.h"

wxDEFINE_EVENT(EVT_DBUS_EVENT_MESSAGE, wxDBusCommandEvent);

using namespace std;
using namespace dBus;
//--------------------------------------------------------------
/* Constructor */
wxDBusCommandEvent::wxDBusCommandEvent(const dBus::HashID messageTypeID, MessagePtr message)
  : wxCommandEvent(EVT_DBUS_EVENT_MESSAGE, 0)
  , m_messageTypeID(messageTypeID)
  , m_message(std::move(message))
{
  // Nothing to do
}
//--------------------------------------------------------------
/* Clone method for wxWidgets event system */
wxEvent *wxDBusCommandEvent::Clone() const
{
  auto *clonedEvent = new wxDBusCommandEvent(m_messageTypeID, m_message);
  clonedEvent->SetEventObject(GetEventObject());     // Copy the event object
  return clonedEvent;
}
//--------------------------------------------------------------
/* Return the message type ID */
HashID wxDBusCommandEvent::getMessageType() const
{
  return m_messageTypeID;
}
//--------------------------------------------------------------
/* Set the message type ID */
void wxDBusCommandEvent::setDbusEventType(const dBus::HashID &messageTypeID)
{
  m_messageTypeID = messageTypeID;
}
//--------------------------------------------------------------
/* Set the message type ID based on event type string */
void wxDBusCommandEvent::makeDbusEventType(const std::string &eventType)
{
  m_messageTypeID = makeID(eventType);
}
//--------------------------------------------------------------
/* Check if the event has an associated message */
bool wxDBusCommandEvent::hasMessage() const
{
  return m_message != nullptr;
}
//--------------------------------------------------------------
/* Get the associated message casted to a specific type */
wxDBusCommandEvent::MessagePtr wxDBusCommandEvent::getMessage() const
{
  return m_message;
}
//--------------------------------------------------------------
/* Set the associated message for the event */
void wxDBusCommandEvent::setMessage(const MessagePtr &message)
{
  m_message = message;
}
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
/* Constructor */
dBus::wxNode::wxNode(wxWindow *owner, dBus::Bus &bus)
  : Node(bus)
  , m_owner(owner)
{
  // Sanity check
  if (!m_owner)
    throw std::invalid_argument("Owner window cannot be null");

  // Bind the custom event handler to the owner window
  m_owner->Bind(EVT_DBUS_EVENT_MESSAGE, &wxNode::on_DBusEvent, this);

  // Start the event thread to listen for messages from the bus
  startEventThread();
}
//--------------------------------------------------------------
/* Destructor */
dBus::wxNode::~wxNode()
{
  // Stop the event thread to clean up resources
  stopEventThread();

  // Unbind the custom event handler from the owner window
  if (m_owner)
    m_owner->Unbind(EVT_DBUS_EVENT_MESSAGE, &wxNode::on_DBusEvent, this);
}
//--------------------------------------------------------------
/* Start the event thread to listen for messages from the bus */
void dBus::wxNode::startEventThread()
{
  // Start thread to process events related to the channel.
  // The thread will run until stop is requested.
  // clang-format off
  m_eventThread = std::jthread([this](const std::stop_token &stopToken)
  {
    // Process events until stop is requested
    while (!stopToken.stop_requested())
    {
      eventThreadLoop();
    }
  });
  // clang-format on
}
//--------------------------------------------------------------
/* Stop the event thread */
void dBus::wxNode::stopEventThread()
{
  // Stop event thread loop
  m_eventThread.request_stop();

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

  // Ensure thread has fully stopped before unbinding
  if (m_eventThread.joinable())
    m_eventThread.join();
}
//--------------------------------------------------------------
/* The main loop of the event thread that listens for messages
 * from the bus and posts events to the wxWidgets event system */
void dBus::wxNode::eventThreadLoop()
{
  try
  {
    // Wait for a message to be received
    syncWaitForMessage();

    // Process all messages in the queue
    while (getMessageCount() > 0)
    {
      const auto message = popNextMessage();
      if (!message)
        continue;

      auto event = wxDBusCommandEvent(message->getMessageTypeID(), message);
      wxQueueEvent(m_owner, event.Clone());
    }
  }
  catch (const std::exception &e)
  {
    wxLogError("Exception in wxDBus event thread: %s", e.what());
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
}
//--------------------------------------------------------------
/* Handler for wxDBusCommandEvent events posted to the GUI,
 * extracts the message and calls on_receiveMessageFromBus */
void dBus::wxNode::on_DBusEvent(wxDBusCommandEvent &event)
{
  const auto &messageID = event.getMessageType();
  const auto &message   = event.getMessage();

  on_receiveMessageFromBus(messageID, message);
}
//--------------------------------------------------------------
