#pragma once

#include "RuntimeEventCoalescingQueue.h"
#include "RuntimeEventDispatcher.h"

#include <cstddef>
#include <utility>
#include <vector>

class RuntimeEventTestHarness
{
public:
	explicit RuntimeEventTestHarness(std::size_t dispatchQueueCapacity = 64, std::size_t coalescingQueueCapacity = 64) :
		mDispatcher(dispatchQueueCapacity),
		mCoalescingQueue(coalescingQueueCapacity)
	{
		mDispatcher.SubscribeAll([this](const RuntimeEvent& event) {
			mSeenEvents.push_back(event);
		});
	}

	RuntimeEventDispatcher& Dispatcher()
	{
		return mDispatcher;
	}

	const RuntimeEventDispatcher& Dispatcher() const
	{
		return mDispatcher;
	}

	RuntimeEventCoalescingQueue& CoalescingQueue()
	{
		return mCoalescingQueue;
	}

	template <typename Payload>
	bool Publish(Payload payload, std::string source = {})
	{
		return mDispatcher.PublishPayload(std::move(payload), std::move(source));
	}

	bool Publish(RuntimeEvent event)
	{
		return mDispatcher.Publish(std::move(event));
	}

	template <typename Payload>
	RuntimeEventDispatchResult PublishAndDispatch(Payload payload, std::string source = {})
	{
		Publish(std::move(payload), std::move(source));
		return DispatchPending();
	}

	RuntimeEventDispatchResult DispatchPending(std::size_t maxEvents = 0)
	{
		return mDispatcher.DispatchPending(maxEvents);
	}

	template <typename Payload>
	bool PublishCoalesced(Payload payload, std::string source = {}, uint64_t sequence = 0)
	{
		return mCoalescingQueue.Push(MakeRuntimeEvent(std::move(payload), std::move(source), sequence));
	}

	std::size_t FlushCoalescedToDispatcher(std::size_t maxEvents = 0)
	{
		std::vector<RuntimeEvent> events = mCoalescingQueue.Drain(maxEvents);
		const std::size_t eventCount = events.size();
		for (RuntimeEvent& event : events)
			mDispatcher.Publish(std::move(event));
		return eventCount;
	}

	RuntimeEventDispatchResult FlushCoalescedAndDispatch(std::size_t maxEvents = 0)
	{
		FlushCoalescedToDispatcher(maxEvents);
		return DispatchPending();
	}

	const std::vector<RuntimeEvent>& SeenEvents() const
	{
		return mSeenEvents;
	}

	std::size_t SeenCount() const
	{
		return mSeenEvents.size();
	}

	std::size_t SeenCount(RuntimeEventType type) const
	{
		std::size_t count = 0;
		for (const RuntimeEvent& event : mSeenEvents)
		{
			if (event.type == type)
				++count;
		}
		return count;
	}

	const RuntimeEvent* LastSeen(RuntimeEventType type) const
	{
		for (auto it = mSeenEvents.rbegin(); it != mSeenEvents.rend(); ++it)
		{
			if (it->type == type)
				return &(*it);
		}
		return nullptr;
	}

	void ClearSeen()
	{
		mSeenEvents.clear();
	}

private:
	RuntimeEventDispatcher mDispatcher;
	RuntimeEventCoalescingQueue mCoalescingQueue;
	std::vector<RuntimeEvent> mSeenEvents;
};
