#include "RenderCommandQueue.h"

#include <iostream>

namespace
{
int gFailures = 0;

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

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

void TestPreviewRequestUsesLatestValue()
{
	RenderCommandQueue queue;
	queue.RequestPreviewPresent({ 1920, 1080 });
	queue.RequestPreviewPresent({ 1280, 720 });

	const RenderCommandQueueMetrics metrics = queue.GetMetrics();
	Expect(metrics.depth == 1, "preview requests coalesce to one pending command");
	Expect(metrics.enqueuedCount == 1, "first preview request is counted as enqueued");
	Expect(metrics.coalescedCount == 1, "second preview request is counted as coalesced");

	RenderPreviewPresentRequest request;
	Expect(queue.TryTakePreviewPresent(request), "preview request can be consumed");
	Expect(request.outputFrameWidth == 1280 && request.outputFrameHeight == 720, "latest preview request wins");
	Expect(!queue.TryTakePreviewPresent(request), "preview request is removed after consume");
	Expect(queue.GetMetrics().depth == 0, "preview consume empties queue depth");
}

void TestScreenshotRequestUsesLatestValue()
{
	RenderCommandQueue queue;
	queue.RequestScreenshotCapture({ 640, 360 });
	queue.RequestScreenshotCapture({ 3840, 2160 });

	RenderScreenshotCaptureRequest request;
	Expect(queue.TryTakeScreenshotCapture(request), "screenshot request can be consumed");
	Expect(request.width == 3840 && request.height == 2160, "latest screenshot request wins");
	Expect(!queue.TryTakeScreenshotCapture(request), "screenshot request is removed after consume");
}

void TestRenderResetScopesCoalesceToStrongestRequest()
{
	RenderCommandQueue queue;
	queue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
	queue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly);

	RenderCommandResetScope scope = RenderCommandResetScope::None;
	Expect(queue.TryTakeRenderReset(scope), "render reset request can be consumed");
	Expect(scope == RenderCommandResetScope::TemporalHistoryAndFeedback, "temporal and feedback reset requests merge");
	Expect(!queue.TryTakeRenderReset(scope), "render reset request is removed after consume");

	queue.RequestRenderReset(RenderCommandResetScope::None);
	Expect(queue.GetMetrics().depth == 0, "none reset request is ignored");
}

void TestInputUploadRequestUsesLatestValue()
{
	int firstPixel = 1;
	int secondPixel = 2;
	RenderCommandQueue queue;

	RenderInputUploadRequest firstRequest;
	firstRequest.inputFrame.bytes = &firstPixel;
	firstRequest.inputFrame.width = 1920;
	firstRequest.videoState.captureTextureWidth = 1920;
	firstRequest.ownedBytes = { 1, 2, 3, 4 };
	queue.RequestInputUpload(firstRequest);

	RenderInputUploadRequest secondRequest;
	secondRequest.inputFrame.bytes = &secondPixel;
	secondRequest.inputFrame.width = 1280;
	secondRequest.videoState.captureTextureWidth = 1280;
	secondRequest.ownedBytes = { 5, 6 };
	queue.RequestInputUpload(secondRequest);

	const RenderCommandQueueMetrics metrics = queue.GetMetrics();
	Expect(metrics.depth == 1, "input upload requests coalesce to one pending command");
	Expect(metrics.enqueuedCount == 1, "first input upload request is counted as enqueued");
	Expect(metrics.coalescedCount == 1, "second input upload request is counted as coalesced");

	RenderInputUploadRequest request;
	Expect(queue.TryTakeInputUpload(request), "input upload request can be consumed");
	Expect(request.inputFrame.bytes == &secondPixel, "latest input upload bytes pointer wins");
	Expect(request.inputFrame.width == 1280, "latest input upload frame wins");
	Expect(request.videoState.captureTextureWidth == 1280, "latest input upload state wins");
	Expect(request.ownedBytes.size() == 2 && request.ownedBytes[0] == 5 && request.ownedBytes[1] == 6, "latest input upload owned bytes win");
	Expect(!queue.TryTakeInputUpload(request), "input upload request is removed after consume");
}

void TestOutputFrameRequestsAreFifo()
{
	RenderCommandQueue queue;
	RenderOutputFrameRequest firstRequest;
	firstRequest.videoState.outputFrameSize.width = 1920;
	firstRequest.completion.result = VideoIOCompletionResult::Completed;
	queue.RequestOutputFrame(firstRequest);

	RenderOutputFrameRequest secondRequest;
	secondRequest.videoState.outputFrameSize.width = 1280;
	secondRequest.completion.result = VideoIOCompletionResult::Dropped;
	queue.RequestOutputFrame(secondRequest);

	Expect(queue.GetMetrics().depth == 2, "output frame requests are queued independently");

	RenderOutputFrameRequest request;
	Expect(queue.TryTakeOutputFrame(request), "first output request can be consumed");
	Expect(request.videoState.outputFrameSize.width == 1920, "first output request is consumed first");
	Expect(request.completion.result == VideoIOCompletionResult::Completed, "first output completion is preserved");
	Expect(queue.TryTakeOutputFrame(request), "second output request can be consumed");
	Expect(request.videoState.outputFrameSize.width == 1280, "second output request is consumed second");
	Expect(request.completion.result == VideoIOCompletionResult::Dropped, "second output completion is preserved");
	Expect(!queue.TryTakeOutputFrame(request), "output queue is empty after consuming all requests");
}

void TestIndependentCommandKindsShareDepth()
{
	RenderCommandQueue queue;
	queue.RequestPreviewPresent({ 1, 2 });
	queue.RequestScreenshotCapture({ 3, 4 });
	queue.RequestInputUpload({});
	queue.RequestOutputFrame({});
	queue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);

	Expect(queue.GetMetrics().depth == 5, "independent command kinds each contribute to depth");
}
}

int main()
{
	TestPreviewRequestUsesLatestValue();
	TestScreenshotRequestUsesLatestValue();
	TestRenderResetScopesCoalesceToStrongestRequest();
	TestInputUploadRequestUsesLatestValue();
	TestOutputFrameRequestsAreFifo();
	TestIndependentCommandKindsShareDepth();

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

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