#pragma once

#include "RuntimeEvent.h"

#include <chrono>
#include <cstddef>
#include <deque>
#include <mutex>
#include <vector>

struct RuntimeEventQueueMetrics
{
	std::size_t depth = 0;
	std::size_t capacity = 0;
	std::size_t droppedCount = 0;
	std::size_t coalescedCount = 0;
	double oldestEventAgeMilliseconds = 0.0;
};

class RuntimeEventQueue
{
public:
	explicit RuntimeEventQueue(std::size_t capacity = 1024) :
		mCapacity(capacity)
	{
	}

	bool Push(RuntimeEvent event)
	{
		std::lock_guard<std::mutex> lock(mMutex);
		if (mEvents.size() >= mCapacity)
		{
			++mDroppedCount;
			return false;
		}

		mEvents.push_back(std::move(event));
		return true;
	}

	bool TryPop(RuntimeEvent& event)
	{
		std::lock_guard<std::mutex> lock(mMutex);
		if (mEvents.empty())
			return false;

		event = std::move(mEvents.front());
		mEvents.pop_front();
		return true;
	}

	std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
	{
		std::vector<RuntimeEvent> events;

		std::lock_guard<std::mutex> lock(mMutex);
		const std::size_t count = maxEvents == 0 || maxEvents > mEvents.size() ? mEvents.size() : maxEvents;
		events.reserve(count);
		for (std::size_t index = 0; index < count; ++index)
		{
			events.push_back(std::move(mEvents.front()));
			mEvents.pop_front();
		}

		return events;
	}

	RuntimeEventQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
	{
		std::lock_guard<std::mutex> lock(mMutex);

		RuntimeEventQueueMetrics metrics;
		metrics.depth = mEvents.size();
		metrics.capacity = mCapacity;
		metrics.droppedCount = mDroppedCount;
		if (!mEvents.empty())
		{
			const auto age = now - mEvents.front().createdAt;
			metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
		}
		return metrics;
	}

	std::size_t Depth() const
	{
		std::lock_guard<std::mutex> lock(mMutex);
		return mEvents.size();
	}

	std::size_t Capacity() const
	{
		return mCapacity;
	}

private:
	mutable std::mutex mMutex;
	std::deque<RuntimeEvent> mEvents;
	std::size_t mCapacity = 0;
	std::size_t mDroppedCount = 0;
};
