#include "SystemOutputFramePool.h"

#include <cstdint>
#include <iostream>

namespace
{
int gFailures = 0;

void Expect(bool condition, const char* message)
{
	if (condition)
		return;

	std::cerr << "FAIL: " << message << "\n";
	++gFailures;
}

SystemOutputFramePoolConfig MakeConfig(std::size_t capacity = 2)
{
	SystemOutputFramePoolConfig config;
	config.width = 4;
	config.height = 3;
	config.pixelFormat = VideoIOPixelFormat::Bgra8;
	config.capacity = capacity;
	return config;
}

void TestAcquireHonorsCapacityAndFrameShape()
{
	SystemOutputFramePool pool(MakeConfig(2));

	OutputFrameSlot first;
	OutputFrameSlot second;
	OutputFrameSlot third;
	Expect(pool.AcquireFreeSlot(first), "first slot can be acquired");
	Expect(pool.AcquireFreeSlot(second), "second slot can be acquired");
	Expect(!pool.AcquireFreeSlot(third), "fixed capacity rejects third acquire");

	Expect(first.frame.bytes != nullptr, "acquired slot has system memory");
	Expect(first.frame.nativeBuffer == first.frame.bytes, "native buffer points at system memory");
	Expect(first.frame.nativeFrame == nullptr, "system frame has no native frame");
	Expect(first.frame.width == 4, "frame width is configured");
	Expect(first.frame.height == 3, "frame height is configured");
	Expect(first.frame.rowBytes == 16, "BGRA8 row bytes are inferred");
	Expect(first.frame.pixelFormat == VideoIOPixelFormat::Bgra8, "BGRA8 is the default output format");
	Expect(first.frame.bytes != second.frame.bytes, "each slot owns distinct memory");

	SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
	Expect(metrics.freeCount == 0, "all slots are in use");
	Expect(metrics.renderingCount == 2, "rendering slots are counted");
	Expect(metrics.acquiredCount == 2, "acquired slots are counted");
	Expect(metrics.acquireMissCount == 1, "capacity miss is counted");
}

void TestPhase77StateContract()
{
	SystemOutputFramePool pool(MakeConfig(1));

	SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
	Expect(metrics.freeCount == 1, "new pool starts with one free slot");
	Expect(metrics.renderingCount == 0, "new pool starts with no rendering slots");
	Expect(metrics.completedCount == 0, "new pool starts with no completed slots");
	Expect(metrics.scheduledCount == 0, "new pool starts with no scheduled slots");

	OutputFrameSlot slot;
	Expect(pool.AcquireRenderingSlot(slot), "free slot moves to rendering");
	metrics = pool.GetMetrics();
	Expect(metrics.freeCount == 0, "rendering slot leaves free pool");
	Expect(metrics.renderingCount == 1, "rendering slot is counted");

	Expect(pool.PublishCompletedSlot(slot), "rendering slot moves to completed");
	metrics = pool.GetMetrics();
	Expect(metrics.renderingCount == 0, "completed slot leaves rendering");
	Expect(metrics.completedCount == 1, "completed slot is counted");
	Expect(metrics.readyCount == 1, "completed slot is available to scheduler");

	OutputFrameSlot completed;
	Expect(pool.ConsumeCompletedSlot(completed), "completed slot can be dequeued for scheduling");
	metrics = pool.GetMetrics();
	Expect(metrics.completedCount == 1, "dequeued completed slot remains completed until scheduled");
	Expect(metrics.readyCount == 0, "dequeued completed slot leaves ready queue");

	Expect(pool.MarkScheduled(completed), "completed slot moves to scheduled");
	metrics = pool.GetMetrics();
	Expect(metrics.completedCount == 0, "scheduled slot leaves completed state");
	Expect(metrics.scheduledCount == 1, "scheduled slot is counted");

	Expect(pool.ReleaseScheduledSlot(completed), "scheduled slot returns to free");
	metrics = pool.GetMetrics();
	Expect(metrics.freeCount == 1, "released scheduled slot returns to free");
	Expect(metrics.scheduledCount == 0, "released scheduled slot leaves scheduled state");
}

void TestReadySlotsAreConsumedFifo()
{
	SystemOutputFramePool pool(MakeConfig(2));

	OutputFrameSlot first;
	OutputFrameSlot second;
	Expect(pool.AcquireFreeSlot(first), "first FIFO slot can be acquired");
	Expect(pool.AcquireFreeSlot(second), "second FIFO slot can be acquired");
	Expect(pool.PublishReadySlot(first), "first FIFO slot can be published");
	Expect(pool.PublishReadySlot(second), "second FIFO slot can be published");

	OutputFrameSlot consumed;
	Expect(pool.ConsumeReadySlot(consumed), "first ready slot can be consumed");
	Expect(consumed.index == first.index, "first published slot is consumed first");
	Expect(pool.MarkScheduled(consumed), "consumed slot can be marked scheduled");
	Expect(pool.ReleaseScheduledSlot(consumed), "scheduled slot can be released");

	Expect(pool.ConsumeReadySlot(consumed), "second ready slot can be consumed");
	Expect(consumed.index == second.index, "second published slot is consumed second");
	Expect(pool.ReleaseSlot(consumed), "consumed slot can be released without scheduling");

	SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
	Expect(metrics.freeCount == 2, "released slots return to free pool");
	Expect(metrics.readyCount == 0, "ready queue is empty after consumption");
}

void TestCompletedSlotCannotBeAcquiredUntilReleased()
{
	SystemOutputFramePool pool(MakeConfig(1));

	OutputFrameSlot slot;
	OutputFrameSlot extra;
	Expect(pool.AcquireRenderingSlot(slot), "single slot can be acquired for rendering");
	Expect(pool.PublishCompletedSlot(slot), "single slot can be published completed");
	Expect(!pool.AcquireRenderingSlot(extra), "completed slot is not available for rendering");

	OutputFrameSlot completed;
	Expect(pool.ConsumeCompletedSlot(completed), "completed slot can be dequeued");
	Expect(!pool.AcquireRenderingSlot(extra), "dequeued completed slot is still not free");
	Expect(pool.MarkScheduled(completed), "dequeued completed slot can be scheduled");
	Expect(!pool.AcquireRenderingSlot(extra), "scheduled slot is still not free");
	Expect(pool.ReleaseScheduledSlot(completed), "scheduled slot can be released");
	Expect(pool.AcquireRenderingSlot(extra), "released slot can be acquired again");
}

void TestReadySlotCanBeScheduledByBuffer()
{
	SystemOutputFramePool pool(MakeConfig(1));

	OutputFrameSlot slot;
	Expect(pool.AcquireFreeSlot(slot), "buffer schedule slot can be acquired");
	void* bytes = slot.frame.bytes;
	Expect(pool.PublishReadySlot(slot), "buffer schedule slot can be published");
	Expect(pool.MarkScheduledByBuffer(bytes), "ready slot can be marked scheduled by buffer");

	SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
	Expect(metrics.readyCount == 0, "scheduled-by-buffer removes slot from ready queue");
	Expect(metrics.scheduledCount == 1, "scheduled-by-buffer counts scheduled slot");

	Expect(pool.ReleaseSlotByBuffer(bytes), "scheduled slot can be released by buffer");
	metrics = pool.GetMetrics();
	Expect(metrics.freeCount == 1, "released-by-buffer slot returns to free pool");
}

void TestInvalidTransitionsAreRejected()
{
	SystemOutputFramePool pool(MakeConfig(1));

	OutputFrameSlot slot;
	Expect(pool.AcquireFreeSlot(slot), "transition slot can be acquired");
	Expect(!pool.MarkScheduled(slot), "acquired slot cannot be marked scheduled");
	Expect(pool.PublishReadySlot(slot), "acquired slot can be published");
	Expect(!pool.PublishReadySlot(slot), "ready slot cannot be published twice");
	Expect(pool.ReleaseSlot(slot), "ready slot can be released to free");
	Expect(!pool.ReleaseSlot(slot), "free slot cannot be released again");

	OutputFrameSlot next;
	Expect(pool.AcquireFreeSlot(next), "slot can be reacquired after release");
	Expect(next.index == slot.index, "same storage slot can be reused");
	Expect(next.generation != slot.generation, "stale handles are invalidated on reacquire");
	Expect(!pool.PublishReadySlot(slot), "stale handle cannot publish reacquired slot");
}

void TestPixelFormatAwareSizing()
{
	SystemOutputFramePoolConfig config;
	config.width = 7;
	config.height = 2;
	config.pixelFormat = VideoIOPixelFormat::V210;
	config.capacity = 1;

	SystemOutputFramePool pool(config);
	OutputFrameSlot slot;
	Expect(pool.AcquireFreeSlot(slot), "v210 slot can be acquired");
	Expect(slot.frame.pixelFormat == VideoIOPixelFormat::V210, "slot keeps configured pixel format");
	Expect(slot.frame.rowBytes == static_cast<long>(MinimumV210RowBytes(config.width)), "v210 row bytes are inferred");

	SystemOutputFramePoolConfig explicitConfig = config;
	explicitConfig.pixelFormat = VideoIOPixelFormat::Uyvy8;
	explicitConfig.rowBytes = 64;
	pool.Configure(explicitConfig);
	Expect(pool.AcquireFreeSlot(slot), "explicit row-byte slot can be acquired");
	Expect(slot.frame.pixelFormat == VideoIOPixelFormat::Uyvy8, "slot keeps reconfigured pixel format");
	Expect(slot.frame.rowBytes == 64, "explicit row bytes are preserved");
}

void TestEmptyReadyQueueUnderrunIsCounted()
{
	SystemOutputFramePool pool(MakeConfig(1));
	OutputFrameSlot slot;
	Expect(!pool.ConsumeReadySlot(slot), "empty ready queue cannot be consumed");

	SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
	Expect(metrics.readyUnderrunCount == 1, "ready underrun is counted");
}
}

int main()
{
	TestAcquireHonorsCapacityAndFrameShape();
	TestPhase77StateContract();
	TestReadySlotsAreConsumedFifo();
	TestCompletedSlotCannotBeAcquiredUntilReleased();
	TestReadySlotCanBeScheduledByBuffer();
	TestInvalidTransitionsAreRejected();
	TestPixelFormatAwareSizing();
	TestEmptyReadyQueueUnderrunIsCounted();

	if (gFailures != 0)
	{
		std::cerr << gFailures << " system output frame pool test failure(s).\n";
		return 1;
	}

	std::cout << "SystemOutputFramePool tests passed.\n";
	return 0;
}
