#include "eventBus.h"

#include <ranges>

using namespace std;
using namespace quokka_gfx;
//------------------------------------------------------------
/* Constructor */
Event::Event(const HashID &type) noexcept
{
  m_type = type;
}
//------------------------------------------------------------
/* Get the type ID of the event */
HashID Event::getType() const
{
  return m_type;
}
//------------------------------------------------------------

/* --- */

//------------------------------------------------------------
/* Default destructor */
Listener::~Listener()
{
  unsubscribeAll();
}
//------------------------------------------------------------
/* Post an event to the listener */
void Listener::pushEvent(std::shared_ptr<Event> event)
{
  scoped_lock lock(m_receivedEventsMutex);
  m_receivedEvents.push(std::move(event));
}
//------------------------------------------------------------
/* Get the number of received events */
size_t Listener::getEventCount() const
{
  scoped_lock lock(m_receivedEventsMutex);
  return m_receivedEvents.size();
}
//------------------------------------------------------------
/* Get the next event from the queue */
std::shared_ptr<Event> Listener::popEvent()
{
  scoped_lock lock(m_receivedEventsMutex);
  if (m_receivedEvents.empty())
    return nullptr;

  auto event = m_receivedEvents.front();
  m_receivedEvents.pop();
  return event;
}
//------------------------------------------------------------
/* Post an event to the event bus */
void Listener::postEvent(const std::shared_ptr<Event> &event) const
{
  EventBus::getInstance().notify(event);
}
//------------------------------------------------------------
/* Check if subscribed to a specific event type */
bool Listener::isSubscribed(const HashID &eventType) const
{
  return EventBus::getInstance().isSubscribed(eventType, this);
}
//------------------------------------------------------------
/* Subscribe to a specific event type*/
void Listener::subscribe(const HashID &eventType)
{
  EventBus::getInstance().subscribe(eventType, this);
}
//------------------------------------------------------------
/* Unsubscribe from a specific event type */
void Listener::unsubscribe(const HashID &eventType)
{
  EventBus::getInstance().unsubscribe(eventType, this);
}
//------------------------------------------------------------
/* Unsubscribe from all events */
void Listener::unsubscribeAll()
{
  EventBus::getInstance().unsubscribeAll(this);
}
//------------------------------------------------------------

/* --- */

//------------------------------------------------------------
/* Get the singleton instance */
EventBus &EventBus::getInstance()
{
  static auto instance = std::unique_ptr<EventBus>(new EventBus());
  return *instance;
}
//------------------------------------------------------------
/* Notify all listeners subscribed to the event type */
void EventBus::notify(const std::shared_ptr<Event> &event) const
{
  scoped_lock lock(m_listenersMutex);

  // No listeners subscribed to this event type, nothing to do
  if (!m_listeners.contains(event->getType()))
    return;

  // Notify all listeners subscribed to this event type
  for (const auto listener : m_listeners.at(event->getType()))
    listener->pushEvent(event);
}
//------------------------------------------------------------
/* Check if a listener is subscribed to a specific event type */
bool EventBus::isSubscribed(const HashID &eventType, const Listener *listener) const
{
  scoped_lock lock(m_listenersMutex);

  // Event type not registered, listener cannot be subscribed
  if (!m_listeners.contains(eventType))
    return false;

  // Event type already registered, check if the listener is in the list
  const auto &listeners = m_listeners.at(eventType);
  return ranges::find(listeners, listener) != listeners.end();
}
//------------------------------------------------------------
/* Subscribe a listener to a specific event type */
void EventBus::subscribe(const HashID &eventType, Listener *listener)
{
  scoped_lock lock(m_listenersMutex);

  // Event type not registered yet, create a new entry with the listener
  if (!m_listeners.contains(eventType))
  {
    m_listeners[eventType] = { listener };
    return;
  }

  // Event type already registered, add the listener if not already subscribed
  auto &listeners = m_listeners[eventType];
  if (ranges::find(listeners, listener) == listeners.end())
    listeners.push_back(listener);
}
//------------------------------------------------------------
/* Unsubscribe a listener from a specific event type */
void EventBus::unsubscribe(const HashID &eventType, Listener *listener)
{
  scoped_lock lock(m_listenersMutex);

  // Event type not registered, nothing to do
  if (!m_listeners.contains(eventType))
    return;

  // Event type registered, remove the listener if subscribed
  auto      &listeners = m_listeners[eventType];
  const auto it        = ranges::find(listeners, listener);
  if (it != listeners.end())
    listeners.erase(it);
}
//------------------------------------------------------------
/* Unsubscribe a listener from all event types */
void EventBus::unsubscribeAll(Listener *listener)
{
  scoped_lock lock(m_listenersMutex);

  // Iterate through all event types and remove the listener from each list
  for (const auto &eventType : m_listeners | views::keys)
    unsubscribe(eventType, listener);
}
//------------------------------------------------------------
