#ifndef EVENTDISTRIBUTOR_HH
#define EVENTDISTRIBUTOR_HH

#include "Event.hh"

#include <array>
#include <cstdint>
#include <mutex>
#include <optional>
#include <utility>
#include <vector>

namespace openmsx {

class Reactor;
class EventListener;

class EventDistributor
{
public:
	/** Priorities from high to low, higher priority listeners can block
	  * events for lower priority listeners.
	  */
	enum class Priority : uint8_t {
		OTHER,
		HOTKEY_HIGH, // above IMGUI
		IMGUI,
		HOTKEY_LOW, // below IMGUI
		MSX,
		LOWEST, // should only be used internally in EventDistributor
	};
	friend auto operator<=>(Priority x, Priority y) { return std::to_underlying(x) <=> std::to_underlying(y); }

	explicit EventDistributor(Reactor& reactor);

	/**
	 * Registers a given object to receive certain events.
	 * @param type The type of the events you want to receive.
	 * @param listener Listener that will be notified when an event arrives.
	 * @param priority Listeners have a priority, higher priority listeners
	 *                 can block events for lower priority listeners.
	 */
	void registerEventListener(EventType type, EventListener& listener,
	                           Priority priority = Priority::OTHER);

	/**
	 * Unregisters a previously registered event listener.
	 * @param type The type of the events the listener should no longer receive.
	 * @param listener Listener to unregister.
	 */
	void unregisterEventListener(EventType type, EventListener& listener);

	/** Schedule the given event for delivery. Actual delivery happens
	  * when the deliverEvents() method is called. Events are always
	  * in the main thread.
	  */
	void distributeEvent(Event&& event);

	/** This actually delivers the events. It may only be called from the
	  * main loop in Reactor (and only from the main thread). Also see
	  * the distributeEvent() method.
	  * @param timeoutMs If provided, wait up to this many milliseconds for
	  *        SDL events before processing. Used when emulation is blocked
	  *        to avoid busy-waiting while remaining responsive to input.
	  */
	void deliverEvents(std::optional<int> timeoutMs = std::nullopt);

	[[nodiscard]] Reactor& getReactor() { return reactor; }

private:
	[[nodiscard]] bool isRegistered(EventType type, EventListener* listener) const;

private:
	Reactor& reactor;

	struct Entry {
		Priority priority;
		EventListener* listener;
	};
	using PriorityMap = std::vector<Entry>; // sorted on priority
	std::array<PriorityMap, size_t(EventType::NUM_EVENT_TYPES)> listeners;
	using EventQueue = std::vector<Event>;
	EventQueue scheduledEvents;
	std::mutex mutex; // lock data structures
};

} // namespace openmsx

#endif
