#pragma once

#include <chrono>
#include <cstddef>
#include <cstdint>
#include <string>

namespace RenderCadenceCompositor
{
struct CadenceTelemetrySnapshot
{
	double sampleSeconds = 0.0;
	double renderFps = 0.0;
	double scheduleFps = 0.0;
	std::size_t freeFrames = 0;
	std::size_t completedFrames = 0;
	std::size_t scheduledFrames = 0;
	uint64_t renderedTotal = 0;
	uint64_t scheduledTotal = 0;
	uint64_t completedPollMisses = 0;
	uint64_t scheduleFailures = 0;
	uint64_t completedDrops = 0;
	uint64_t acquireMisses = 0;
	uint64_t completions = 0;
	uint64_t displayedLate = 0;
	uint64_t dropped = 0;
	uint64_t clockOverruns = 0;
	uint64_t clockSkippedFrames = 0;
	uint64_t shaderBuildsCommitted = 0;
	uint64_t shaderBuildFailures = 0;
	double renderFrameMilliseconds = 0.0;
	double renderFrameBudgetUsedPercent = 0.0;
	double renderFrameMaxMilliseconds = 0.0;
	double readbackQueueMilliseconds = 0.0;
	double completedReadbackCopyMilliseconds = 0.0;
	uint64_t inputFramesReceived = 0;
	uint64_t inputFramesDropped = 0;
	uint64_t inputConsumeMisses = 0;
	uint64_t inputUploadMisses = 0;
	std::size_t inputReadyFrames = 0;
	std::size_t inputReadingFrames = 0;
	double inputLatestAgeMilliseconds = 0.0;
	double inputUploadMilliseconds = 0.0;
	bool inputFormatSupported = true;
	bool inputSignalPresent = false;
	double inputCaptureFps = 0.0;
	double inputConvertMilliseconds = 0.0;
	double inputSubmitMilliseconds = 0.0;
	uint64_t inputNoSignalFrames = 0;
	uint64_t inputUnsupportedFrames = 0;
	uint64_t inputSubmitMisses = 0;
	std::string inputCaptureFormat = "none";
	bool deckLinkBufferedAvailable = false;
	uint64_t deckLinkBuffered = 0;
	double deckLinkScheduleCallMilliseconds = 0.0;
	bool deckLinkScheduleLeadAvailable = false;
	int64_t deckLinkPlaybackStreamTime = 0;
	uint64_t deckLinkPlaybackFrameIndex = 0;
	uint64_t deckLinkNextScheduleFrameIndex = 0;
	int64_t deckLinkScheduleLeadFrames = 0;
	uint64_t deckLinkScheduleRealignments = 0;
};

class CadenceTelemetry
{
public:
	template <typename SystemFrameExchange, typename Output, typename OutputThread>
	CadenceTelemetrySnapshot Sample(
		const SystemFrameExchange& exchange,
		const Output& output,
		const OutputThread& outputThread)
	{
		const auto now = Clock::now();
		const double seconds = mHasLastSample
			? std::chrono::duration_cast<std::chrono::duration<double>>(now - mLastSampleTime).count()
			: 0.0;

		const auto exchangeMetrics = exchange.Metrics();
		const auto outputMetrics = output.Metrics();
		const auto threadMetrics = outputThread.Metrics();

		CadenceTelemetrySnapshot snapshot;
		snapshot.sampleSeconds = seconds;
		snapshot.renderedTotal = exchangeMetrics.completedFrames;
		snapshot.scheduledTotal = exchangeMetrics.scheduledFrames;
		snapshot.freeFrames = exchangeMetrics.freeCount;
		snapshot.completedFrames = exchangeMetrics.completedCount;
		snapshot.scheduledFrames = exchangeMetrics.scheduledCount;
		snapshot.completedPollMisses = threadMetrics.completedPollMisses;
		snapshot.scheduleFailures = outputMetrics.scheduleFailures > threadMetrics.scheduleFailures
			? outputMetrics.scheduleFailures
			: threadMetrics.scheduleFailures;
		snapshot.completedDrops = exchangeMetrics.completedDrops;
		snapshot.acquireMisses = exchangeMetrics.acquireMisses;
		snapshot.completions = outputMetrics.completions;
		snapshot.displayedLate = outputMetrics.displayedLate;
		snapshot.dropped = outputMetrics.dropped;
		snapshot.deckLinkBufferedAvailable = outputMetrics.actualBufferedFramesAvailable;
		snapshot.deckLinkBuffered = outputMetrics.actualBufferedFrames;
		snapshot.deckLinkScheduleCallMilliseconds = outputMetrics.scheduleCallMilliseconds;
		snapshot.deckLinkScheduleLeadAvailable = outputMetrics.scheduleLeadAvailable;
		snapshot.deckLinkPlaybackStreamTime = outputMetrics.playbackStreamTime;
		snapshot.deckLinkPlaybackFrameIndex = outputMetrics.playbackFrameIndex;
		snapshot.deckLinkNextScheduleFrameIndex = outputMetrics.nextScheduleFrameIndex;
		snapshot.deckLinkScheduleLeadFrames = outputMetrics.scheduleLeadFrames;
		snapshot.deckLinkScheduleRealignments = outputMetrics.scheduleRealignmentCount;

		if (mHasLastSample && seconds > 0.0)
		{
			snapshot.renderFps = static_cast<double>(snapshot.renderedTotal - mLastRenderedFrames) / seconds;
			snapshot.scheduleFps = static_cast<double>(snapshot.scheduledTotal - mLastScheduledFrames) / seconds;
		}

		mLastSampleTime = now;
		mLastRenderedFrames = snapshot.renderedTotal;
		mLastScheduledFrames = snapshot.scheduledTotal;
		mHasLastSample = true;
		return snapshot;
	}

	template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread>
	CadenceTelemetrySnapshot Sample(
		const SystemFrameExchange& exchange,
		const Output& output,
		const OutputThread& outputThread,
		const RenderThread& renderThread)
	{
		CadenceTelemetrySnapshot snapshot = Sample(exchange, output, outputThread);
		const auto renderMetrics = renderThread.GetMetrics();
		snapshot.clockOverruns = renderMetrics.clockOverruns;
		snapshot.clockSkippedFrames = renderMetrics.skippedFrames;
		snapshot.shaderBuildsCommitted = renderMetrics.shaderBuildsCommitted;
		snapshot.shaderBuildFailures = renderMetrics.shaderBuildFailures;
		snapshot.renderFrameMilliseconds = renderMetrics.renderFrameMilliseconds;
		snapshot.renderFrameBudgetUsedPercent = renderMetrics.renderFrameBudgetUsedPercent;
		snapshot.renderFrameMaxMilliseconds = renderMetrics.renderFrameMaxMilliseconds;
		snapshot.readbackQueueMilliseconds = renderMetrics.readbackQueueMilliseconds;
		snapshot.completedReadbackCopyMilliseconds = renderMetrics.completedReadbackCopyMilliseconds;
		snapshot.inputFramesReceived = renderMetrics.inputFramesReceived;
		snapshot.inputFramesDropped = renderMetrics.inputFramesDropped;
		snapshot.inputConsumeMisses = renderMetrics.inputConsumeMisses;
		snapshot.inputUploadMisses = renderMetrics.inputUploadMisses;
		snapshot.inputReadyFrames = renderMetrics.inputReadyFrames;
		snapshot.inputReadingFrames = renderMetrics.inputReadingFrames;
		snapshot.inputLatestAgeMilliseconds = renderMetrics.inputLatestAgeMilliseconds;
		snapshot.inputUploadMilliseconds = renderMetrics.inputUploadMilliseconds;
		snapshot.inputFormatSupported = renderMetrics.inputFormatSupported;
		snapshot.inputSignalPresent = renderMetrics.inputSignalPresent;
		return snapshot;
	}

	template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread, typename InputEdge>
	CadenceTelemetrySnapshot Sample(
		const SystemFrameExchange& exchange,
		const Output& output,
		const OutputThread& outputThread,
		const RenderThread& renderThread,
		const InputEdge& inputEdge)
	{
		CadenceTelemetrySnapshot snapshot = Sample(exchange, output, outputThread, renderThread);
		const auto inputMetrics = inputEdge.Metrics();
		snapshot.inputConvertMilliseconds = inputMetrics.convertMilliseconds;
		snapshot.inputSubmitMilliseconds = inputMetrics.submitMilliseconds;
		snapshot.inputNoSignalFrames = inputMetrics.noInputSourceFrames;
		snapshot.inputUnsupportedFrames = inputMetrics.unsupportedFrames;
		snapshot.inputSubmitMisses = inputMetrics.submitMisses;
		snapshot.inputCaptureFormat = inputMetrics.captureFormat ? inputMetrics.captureFormat : "none";
		if (snapshot.sampleSeconds > 0.0)
			snapshot.inputCaptureFps = static_cast<double>(inputMetrics.capturedFrames - mLastInputCapturedFrames) / snapshot.sampleSeconds;
		mLastInputCapturedFrames = inputMetrics.capturedFrames;
		return snapshot;
	}

private:
	using Clock = std::chrono::steady_clock;

	Clock::time_point mLastSampleTime = Clock::now();
	uint64_t mLastRenderedFrames = 0;
	uint64_t mLastScheduledFrames = 0;
	uint64_t mLastInputCapturedFrames = 0;
	bool mHasLastSample = false;
};
}
