#include "RenderCadenceClock.h"

#include <chrono>
#include <iostream>

namespace
{
int gFailures = 0;

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

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

void TestEarlyPollWaitsWithoutAdvancing()
{
	using Clock = RenderCadenceClock::Clock;
	RenderCadenceClock cadence(16.0);
	const auto start = Clock::now();
	cadence.Reset(start);

	const auto tick = cadence.Poll(start - std::chrono::milliseconds(1));
	Expect(!tick.due, "early poll is not due");
	Expect(tick.sleepFor > RenderCadenceClock::Duration::zero(), "early poll returns a sleep duration");
	Expect(cadence.OverrunCount() == 0, "early poll does not count overrun");
	Expect(cadence.SkippedFrameCount() == 0, "early poll does not skip frames");
}

void TestDuePollRendersWithoutSkipping()
{
	using Clock = RenderCadenceClock::Clock;
	RenderCadenceClock cadence(16.0);
	const auto start = Clock::now();
	cadence.Reset(start);

	const auto tick = cadence.Poll(start);
	Expect(tick.due, "exact cadence time is due");
	Expect(tick.skippedFrames == 0, "exact cadence time skips no frames");
	Expect(cadence.OverrunCount() == 0, "exact cadence time is not an overrun");

	cadence.MarkRendered(start);
	Expect(cadence.NextRenderTime() > start, "mark rendered advances next render time");
}

void TestLatePollRecordsSkippedFrames()
{
	using Clock = RenderCadenceClock::Clock;
	RenderCadenceClock cadence(10.0);
	const auto start = Clock::now();
	cadence.Reset(start);

	const auto tick = cadence.Poll(start + std::chrono::milliseconds(35));
	Expect(tick.due, "late poll is due");
	Expect(tick.skippedFrames == 3, "late poll records skipped frame intervals");
	Expect(cadence.OverrunCount() == 1, "late poll records overrun");
	Expect(cadence.SkippedFrameCount() == 3, "late poll accumulates skipped frames");
}

void TestLatePollSkipsMissedIntervalsInsteadOfCatchingUp()
{
	using Clock = RenderCadenceClock::Clock;
	RenderCadenceClock cadence(10.0);
	const auto start = Clock::now();
	cadence.Reset(start);

	const auto late = start + std::chrono::milliseconds(35);
	const auto tick = cadence.Poll(late);
	Expect(tick.due, "late skipped-interval poll is due");
	Expect(tick.skippedFrames == 3, "late skipped-interval poll counts missed frames");

	cadence.MarkRendered(late);
	Expect(cadence.NextRenderTime() > late, "late render schedules the next tick in the future");
	Expect(cadence.NextRenderTime() - late <= std::chrono::milliseconds(6), "late render does not leave catch-up frames due immediately");

	const auto immediateFollowup = cadence.Poll(late);
	Expect(!immediateFollowup.due, "cadence does not allow an immediate catch-up render after a late frame");
	Expect(immediateFollowup.sleepFor > RenderCadenceClock::Duration::zero(), "cadence reports wait time after skipping missed intervals");
}

void TestMarkRenderedRebasesAfterLargeStall()
{
	using Clock = RenderCadenceClock::Clock;
	RenderCadenceClock cadence(10.0);
	const auto start = Clock::now();
	cadence.Reset(start);

	const auto stalled = start + std::chrono::milliseconds(100);
	cadence.MarkRendered(stalled);

	const auto next = cadence.NextRenderTime();
	Expect(next > stalled, "large stall rebases next render time after now");
	Expect(next - stalled <= std::chrono::milliseconds(11), "large stall rebases to roughly one frame ahead");
}
}

int main()
{
	TestEarlyPollWaitsWithoutAdvancing();
	TestDuePollRendersWithoutSkipping();
	TestLatePollRecordsSkippedFrames();
	TestLatePollSkipsMissedIntervalsInsteadOfCatchingUp();
	TestMarkRenderedRebasesAfterLargeStall();

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

	std::cout << "RenderCadenceCompositor cadence clock tests passed.\n";
	return 0;
}
