#pragma once

#include "../defs.h"

#include <memory>
#include <mutex>
#include <queue>
#include <typeindex>
#include <unordered_map>
#include <vector>

namespace quokka_gfx
{
class EventBus;

//------------------------------------------------------------
// Base class for all events
class Event
{
  public:
  Event()                            = delete;      // Default constructor
  virtual ~Event()                   = default;     // Default destructor
  Event(const Event &obj)            = delete;      // Copy constructor
  Event(Event &&obj)                 = delete;      // Move constructor
  Event &operator=(const Event &obj) = delete;      // Copy assignment operator
  Event &operator=(Event &&obj)      = delete;      // Move assignment operator

  explicit Event(const HashID &type) noexcept;     // Constructor

  [[nodiscard]] HashID getType() const;     // Get the type ID of the event

  protected:
  HashID m_type;     // Type ID of the event
};
//------------------------------------------------------------

/* --- */

//------------------------------------------------------------
// Base class for all event listeners
class Listener
{
  friend class EventBus;

  public:
  Listener() = default;                                  // Default constructor
  virtual ~Listener();                                   // Default destructor
  Listener(const Listener &obj)            = delete;     // Copy constructor
  Listener(Listener &&obj)                 = delete;     // Move constructor
  Listener &operator=(const Listener &obj) = delete;     // Copy assignment operator
  Listener &operator=(Listener &&obj)      = delete;     // Move assignment operator

  void pushEvent(std::shared_ptr<Event> event);     // Post an event to the listener

  [[nodiscard]] size_t                 getEventCount() const;     // Get the number of received events
  [[nodiscard]] std::shared_ptr<Event> popEvent();                // Get the next event from the queue

  void postEvent(const std::shared_ptr<Event> &event) const;     // Post an event to the event bus

  [[nodiscard]] bool isSubscribed(const HashID &eventType) const;     // Check if subscribed to a specific event type
  void               subscribe(const HashID &eventType);              // Subscribe to a specific event type
  void               unsubscribe(const HashID &eventType);            // Unsubscribe from a specific event type
  void               unsubscribeAll();                                // Unsubscribe from all events

  protected:
  mutable std::mutex                 m_receivedEventsMutex;     // Mutex for thread-safe access to the event queue
  std::queue<std::shared_ptr<Event>> m_receivedEvents;          // Queue of received events
};
//------------------------------------------------------------

/* --- */

//------------------------------------------------------------
// Singleton EventBus for managing event distribution
class EventBus
{
  friend class Listener;

  public:
  static EventBus &getInstance();     // Get the singleton instance

  public:
  virtual ~EventBus()                      = default;     // Default destructor
  EventBus(const EventBus &obj)            = delete;      // Copy constructor
  EventBus(EventBus &&obj)                 = delete;      // Move constructor
  EventBus &operator=(const EventBus &obj) = delete;      // Copy assignment operator
  EventBus &operator=(EventBus &&obj)      = delete;      // Move assignment operator

  void notify(const std::shared_ptr<Event> &event) const;     // Notify all listeners subscribed to the event type

  protected:
  mutable std::recursive_mutex                        m_listenersMutex;     // Mutex for thread-safe access to the listeners map
  std::unordered_map<HashID, std::vector<Listener *>> m_listeners;          // Map of event type IDs to lists of subscribed listeners

  private:
  EventBus() = default;     // Default constructor

  [[nodiscard]] bool isSubscribed(const HashID &eventType, const Listener *listener) const;     // Check if a listener is subscribed to a specific event type
  void               subscribe(const HashID &eventType, Listener *listener);                    // Subscribe a listener to a specific event type
  void               unsubscribe(const HashID &eventType, Listener *listener);                  // Unsubscribe a listener from a specific event type
  void               unsubscribeAll(Listener *listener);                                        // Unsubscribe a listener from all event types
};
//------------------------------------------------------------
}     // namespace quokka_gfx