#include "InputFrameMailbox.h"

#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>

namespace
{
int gFailures = 0;

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

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

InputFrameMailboxConfig MakeConfig(std::size_t capacity = 2)
{
	InputFrameMailboxConfig config;
	config.width = 2;
	config.height = 2;
	config.pixelFormat = VideoIOPixelFormat::Bgra8;
	config.rowBytes = VideoIORowBytes(config.pixelFormat, config.width);
	config.capacity = capacity;
	return config;
}

InputFrameMailboxConfig MakeBufferedConfig(std::size_t capacity = 4, std::size_t maxReadyFrames = 2)
{
	InputFrameMailboxConfig config = MakeConfig(capacity);
	config.maxReadyFrames = maxReadyFrames;
	return config;
}

std::vector<unsigned char> MakeFrame(unsigned char value)
{
	return std::vector<unsigned char>(16, value);
}

void TestSubmitDropsOldestWhenFull()
{
	InputFrameMailbox mailbox(MakeConfig(2));
	const std::vector<unsigned char> frame1 = MakeFrame(1);
	const std::vector<unsigned char> frame2 = MakeFrame(2);
	const std::vector<unsigned char> frame3 = MakeFrame(3);

	Expect(mailbox.SubmitFrame(frame1.data(), 8, 1), "first frame submits into full test");
	Expect(mailbox.SubmitFrame(frame2.data(), 8, 2), "second frame submits into full test");
	Expect(mailbox.SubmitFrame(frame3.data(), 8, 3), "third frame submits by dropping oldest ready frame");

	InputFrame oldest;
	Expect(mailbox.TryAcquireOldest(oldest), "oldest frame available after drop");
	Expect(oldest.frameIndex == 2, "bounded mailbox keeps FIFO order after trimming oldest overflow");
	Expect(mailbox.Release(oldest), "oldest frame releases");

	const InputFrameMailboxMetrics metrics = mailbox.Metrics();
	Expect(metrics.droppedReadyFrames >= 1, "full mailbox records ready drop");
	Expect(metrics.submitMisses == 0, "full mailbox did not block producer when ready slots were disposable");
}

void TestReadingFrameIsProtected()
{
	InputFrameMailbox mailbox(MakeConfig(1));
	const std::vector<unsigned char> frame1 = MakeFrame(1);
	const std::vector<unsigned char> frame2 = MakeFrame(2);

	Expect(mailbox.SubmitFrame(frame1.data(), 8, 1), "protected frame submits");
	InputFrame acquired;
	Expect(mailbox.TryAcquireOldest(acquired), "protected frame acquired");
	Expect(!mailbox.SubmitFrame(frame2.data(), 8, 2), "producer cannot overwrite frame currently being read");
	Expect(mailbox.Release(acquired), "protected frame releases");
	Expect(mailbox.SubmitFrame(frame2.data(), 8, 2), "producer can submit after release");
}

void TestAcquireOldestConsumesFifoWithoutDroppingReadyFrames()
{
	InputFrameMailbox mailbox(MakeConfig(3));
	const std::vector<unsigned char> frame1 = MakeFrame(1);
	const std::vector<unsigned char> frame2 = MakeFrame(2);

	Expect(mailbox.SubmitFrame(frame1.data(), 8, 1), "fifo first frame submits");
	Expect(mailbox.SubmitFrame(frame2.data(), 8, 2), "fifo second frame submits");

	InputFrame acquired;
	Expect(mailbox.TryAcquireOldest(acquired), "fifo oldest frame acquired");
	Expect(acquired.frameIndex == 1, "fifo acquire returns oldest frame");
	Expect(mailbox.Release(acquired), "fifo acquired frame releases");

	const InputFrameMailboxMetrics metrics = mailbox.Metrics();
	Expect(metrics.readyCount == 1, "fifo acquire leaves newer frame ready");
	Expect(metrics.droppedReadyFrames == 0, "fifo acquire does not drop newer ready frame");
}

void TestMaxReadyFramesKeepsConfiguredInputBuffer()
{
	InputFrameMailbox mailbox(MakeBufferedConfig(4, 3));
	const std::vector<unsigned char> frame1 = MakeFrame(1);
	const std::vector<unsigned char> frame2 = MakeFrame(2);
	const std::vector<unsigned char> frame3 = MakeFrame(3);
	const std::vector<unsigned char> frame4 = MakeFrame(4);

	Expect(mailbox.SubmitFrame(frame1.data(), 8, 1), "bounded first frame submits");
	Expect(mailbox.SubmitFrame(frame2.data(), 8, 2), "bounded second frame submits");
	Expect(mailbox.SubmitFrame(frame3.data(), 8, 3), "bounded third frame submits");
	Expect(mailbox.SubmitFrame(frame4.data(), 8, 4), "bounded fourth frame submits");

	InputFrame acquired;
	Expect(mailbox.TryAcquireOldest(acquired), "bounded oldest available frame acquired");
	Expect(acquired.frameIndex == 2, "bounded buffer trims oldest beyond configured ready frame limit");
	Expect(mailbox.Release(acquired), "bounded acquired frame releases");

	const InputFrameMailboxMetrics metrics = mailbox.Metrics();
	Expect(metrics.readyCount == 2, "bounded acquire leaves remaining configured ready frames");
	Expect(metrics.droppedReadyFrames == 1, "bounded buffer records trimmed frame");
}
}

int main()
{
	TestSubmitDropsOldestWhenFull();
	TestReadingFrameIsProtected();
	TestAcquireOldestConsumesFifoWithoutDroppingReadyFrames();
	TestMaxReadyFramesKeepsConfiguredInputBuffer();

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

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