#pragma once

#include <chrono>
#include <cstdint>

enum class RenderCadenceAction
{
	Wait,
	Render
};

struct RenderCadencePolicy
{
	bool skipLateTicks = true;
	uint64_t maxSkippedTicksPerDecision = 4;
	double skipThresholdFrames = 2.0;
};

struct RenderCadenceDecision
{
	RenderCadenceAction action = RenderCadenceAction::Wait;
	uint64_t frameIndex = 0;
	uint64_t skippedTicks = 0;
	std::chrono::steady_clock::time_point renderTargetTime;
	std::chrono::steady_clock::time_point nextRenderTime;
	std::chrono::steady_clock::duration waitDuration = std::chrono::steady_clock::duration::zero();
	std::chrono::steady_clock::duration lateness = std::chrono::steady_clock::duration::zero();
	const char* reason = "waiting-for-next-render-tick";
};

struct RenderCadenceMetrics
{
	uint64_t nextFrameIndex = 0;
	uint64_t renderedFrameCount = 0;
	uint64_t skippedTickCount = 0;
	uint64_t lateFrameCount = 0;
	std::chrono::steady_clock::duration lastLateness = std::chrono::steady_clock::duration::zero();
	std::chrono::steady_clock::duration maxLateness = std::chrono::steady_clock::duration::zero();
};

class RenderCadenceController
{
public:
	using Clock = std::chrono::steady_clock;
	using TimePoint = Clock::time_point;
	using Duration = Clock::duration;

	void Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy = RenderCadencePolicy());
	void Reset(TimePoint firstRenderTime);
	RenderCadenceDecision Tick(TimePoint now);

	Duration TargetFrameDuration() const { return mTargetFrameDuration; }
	TimePoint NextRenderTime() const { return mNextRenderTime; }
	uint64_t NextFrameIndex() const { return mNextFrameIndex; }
	const RenderCadenceMetrics& Metrics() const { return mMetrics; }

private:
	uint64_t SkippedTicksForLateness(Duration lateness) const;
	static bool IsPositive(Duration duration);

	Duration mTargetFrameDuration = std::chrono::milliseconds(16);
	TimePoint mNextRenderTime;
	uint64_t mNextFrameIndex = 0;
	RenderCadencePolicy mPolicy;
	RenderCadenceMetrics mMetrics;
};

const char* RenderCadenceActionName(RenderCadenceAction action);
