#include "RenderOutputQueue.h"

#include <iostream>

namespace
{
int gFailures = 0;
int gReleasedFrames = 0;

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

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

RenderOutputFrame MakeFrame(uint64_t index)
{
	RenderOutputFrame frame;
	frame.frameIndex = index;
	frame.frame.nativeFrame = reinterpret_cast<void*>(static_cast<uintptr_t>(index + 1));
	return frame;
}

void CountReleasedFrame(VideoIOOutputFrame& frame)
{
	if (frame.nativeFrame != nullptr)
	{
		++gReleasedFrames;
		frame.nativeFrame = nullptr;
	}
}

RenderOutputFrame MakeOwnedFrame(uint64_t index)
{
	RenderOutputFrame frame = MakeFrame(index);
	frame.releaseFrame = CountReleasedFrame;
	return frame;
}

void TestQueuePreservesOrdering()
{
	VideoPlayoutPolicy policy;
	policy.maxReadyFrames = 3;
	RenderOutputQueue queue(policy);

	Expect(queue.Push(MakeFrame(1)), "first ready frame pushes");
	Expect(queue.Push(MakeFrame(2)), "second ready frame pushes");

	RenderOutputFrame frame;
	Expect(queue.TryPop(frame), "first ready frame pops");
	Expect(frame.frameIndex == 1, "queue pops first frame first");
	Expect(queue.TryPop(frame), "second ready frame pops");
	Expect(frame.frameIndex == 2, "queue pops second frame second");
}

void TestBoundedQueueDropsOldestFrame()
{
	VideoPlayoutPolicy policy;
	policy.maxReadyFrames = 2;
	RenderOutputQueue queue(policy);

	queue.Push(MakeFrame(1));
	queue.Push(MakeFrame(2));
	queue.Push(MakeFrame(3));

	RenderOutputQueueMetrics metrics = queue.GetMetrics();
	Expect(metrics.depth == 2, "bounded queue depth stays at capacity");
	Expect(metrics.droppedCount == 1, "bounded queue counts dropped oldest frame");

	RenderOutputFrame frame;
	Expect(queue.TryPop(frame), "bounded queue pops after drop");
	Expect(frame.frameIndex == 2, "oldest frame was dropped when queue overflowed");
}

void TestOverflowReleasesDroppedFrame()
{
	gReleasedFrames = 0;
	VideoPlayoutPolicy policy;
	policy.targetReadyFrames = 1;
	policy.maxReadyFrames = 1;
	RenderOutputQueue queue(policy);

	queue.Push(MakeOwnedFrame(1));
	queue.Push(MakeOwnedFrame(2));

	Expect(gReleasedFrames == 1, "overflow releases dropped ready frame");

	RenderOutputFrame frame;
	Expect(queue.TryPop(frame), "newest owned frame remains queued");
	Expect(frame.frameIndex == 2, "overflow keeps newest owned frame");
	Expect(gReleasedFrames == 1, "pop transfers ownership without releasing");
}

void TestDropOldestFrameReleasesFrame()
{
	gReleasedFrames = 0;
	VideoPlayoutPolicy policy;
	policy.maxReadyFrames = 2;
	RenderOutputQueue queue(policy);

	queue.Push(MakeOwnedFrame(1));
	queue.Push(MakeOwnedFrame(2));

	Expect(queue.DropOldestFrame(), "oldest ready frame can be explicitly dropped");
	Expect(gReleasedFrames == 1, "explicit drop releases oldest frame");

	RenderOutputQueueMetrics metrics = queue.GetMetrics();
	Expect(metrics.depth == 1, "explicit drop reduces queue depth");
	Expect(metrics.droppedCount == 1, "explicit drop increments dropped count");

	RenderOutputFrame frame;
	Expect(queue.TryPop(frame), "newest frame remains after explicit drop");
	Expect(frame.frameIndex == 2, "explicit drop keeps newest frame");
	Expect(!queue.DropOldestFrame(), "empty queue cannot drop a frame");
}

void TestUnderrunIsCounted()
{
	RenderOutputQueue queue;
	RenderOutputFrame frame;
	Expect(!queue.TryPop(frame), "empty queue reports underrun");

	RenderOutputQueueMetrics metrics = queue.GetMetrics();
	Expect(metrics.underrunCount == 1, "empty pop increments underrun count");
}

void TestConfigureShrinksDepthToNewCapacity()
{
	VideoPlayoutPolicy policy;
	policy.maxReadyFrames = 4;
	RenderOutputQueue queue(policy);
	queue.Push(MakeFrame(1));
	queue.Push(MakeFrame(2));
	queue.Push(MakeFrame(3));

	VideoPlayoutPolicy smallerPolicy;
	smallerPolicy.targetReadyFrames = 1;
	smallerPolicy.maxReadyFrames = 1;
	queue.Configure(smallerPolicy);

	RenderOutputQueueMetrics metrics = queue.GetMetrics();
	Expect(metrics.depth == 1, "configure trims queue to new capacity");
	Expect(metrics.droppedCount == 2, "configure counts trimmed frames as drops");

	RenderOutputFrame frame;
	Expect(queue.TryPop(frame), "trimmed queue still has newest frame");
	Expect(frame.frameIndex == 3, "configure keeps newest ready frame");
}

void TestConfigureReleasesTrimmedFrames()
{
	gReleasedFrames = 0;
	VideoPlayoutPolicy policy;
	policy.maxReadyFrames = 3;
	RenderOutputQueue queue(policy);
	queue.Push(MakeOwnedFrame(1));
	queue.Push(MakeOwnedFrame(2));
	queue.Push(MakeOwnedFrame(3));

	VideoPlayoutPolicy smallerPolicy;
	smallerPolicy.targetReadyFrames = 1;
	smallerPolicy.maxReadyFrames = 1;
	queue.Configure(smallerPolicy);

	Expect(gReleasedFrames == 2, "configure releases trimmed ready frames");

	RenderOutputFrame frame;
	Expect(queue.TryPop(frame), "trimmed owned queue still has newest frame");
	Expect(frame.frameIndex == 3, "configure keeps newest owned frame after release");
}

void TestClearReleasesQueuedFrames()
{
	gReleasedFrames = 0;
	RenderOutputQueue queue;
	queue.Push(MakeOwnedFrame(1));
	queue.Push(MakeOwnedFrame(2));

	queue.Clear();

	RenderOutputQueueMetrics metrics = queue.GetMetrics();
	Expect(metrics.depth == 0, "clear empties ready queue");
	Expect(gReleasedFrames == 2, "clear releases queued ready frames");
}
}

int main()
{
	TestQueuePreservesOrdering();
	TestBoundedQueueDropsOldestFrame();
	TestOverflowReleasesDroppedFrame();
	TestDropOldestFrameReleasesFrame();
	TestUnderrunIsCounted();
	TestConfigureShrinksDepthToNewCapacity();
	TestConfigureReleasesTrimmedFrames();
	TestClearReleasesQueuedFrames();

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

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