#include "RuntimeSnapshotProvider.h"

#include "RuntimeEventDispatcher.h"

#include <windows.h>

#include <utility>

RuntimeSnapshotProvider::RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder, RuntimeEventDispatcher& runtimeEventDispatcher) :
	mRenderSnapshotBuilder(renderSnapshotBuilder),
	mRuntimeEventDispatcher(runtimeEventDispatcher)
{
}

bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
{
	try
	{
		return mRenderSnapshotBuilder.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
	}
	catch (const std::exception& exception)
	{
		error = std::string("RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
		return false;
	}
	catch (...)
	{
		error = "RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
		return false;
	}
}

unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
{
	return mRenderSnapshotBuilder.GetMaxTemporalHistoryFrames();
}

RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
{
	return mRenderSnapshotBuilder.GetVersions();
}

void RuntimeSnapshotProvider::AdvanceFrame()
{
	mRenderSnapshotBuilder.AdvanceFrame();
}

RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
{
	PublishRenderSnapshotPublishRequested(outputWidth, outputHeight, "publish-render-state-snapshot");

	for (;;)
	{
		const RuntimeSnapshotVersions versionsBefore = GetVersions();
		RuntimeRenderStateSnapshot publishedSnapshot;
		if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, publishedSnapshot))
		{
			PublishRenderSnapshotPublished(publishedSnapshot);
			return publishedSnapshot;
		}

		RuntimeRenderStateSnapshot snapshot;
		snapshot.outputWidth = outputWidth;
		snapshot.outputHeight = outputHeight;
		mRenderSnapshotBuilder.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);

		const RuntimeSnapshotVersions versionsAfter = GetVersions();
		if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
			versionsBefore.parameterStateVersion == versionsAfter.parameterStateVersion)
		{
			snapshot.versions = versionsAfter;
			StorePublishedRenderStateSnapshot(snapshot);
			PublishRenderSnapshotPublished(snapshot);
			return snapshot;
		}
	}
}

bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const
{
	PublishRenderSnapshotPublishRequested(outputWidth, outputHeight, "try-publish-render-state-snapshot");

	const RuntimeSnapshotVersions versionsBefore = GetVersions();
	if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, snapshot))
	{
		PublishRenderSnapshotPublished(snapshot);
		return true;
	}

	std::vector<RuntimeRenderState> states;
	if (!mRenderSnapshotBuilder.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
		return false;

	const RuntimeSnapshotVersions versionsAfter = GetVersions();
	if (versionsBefore.renderStateVersion != versionsAfter.renderStateVersion ||
		versionsBefore.parameterStateVersion != versionsAfter.parameterStateVersion)
	{
		return false;
	}

	snapshot.outputWidth = outputWidth;
	snapshot.outputHeight = outputHeight;
	snapshot.versions = versionsAfter;
	snapshot.states = std::move(states);
	StorePublishedRenderStateSnapshot(snapshot);
	PublishRenderSnapshotPublished(snapshot);
	return true;
}

bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
{
	const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
	if (!mRenderSnapshotBuilder.TryRefreshLayerParameters(snapshot.states))
		return false;

	const RuntimeSnapshotVersions versions = GetVersions();
	if (versions.renderStateVersion != expectedRenderStateVersion)
		return false;

	snapshot.versions = versions;
	StorePublishedRenderStateSnapshot(snapshot);
	PublishRenderSnapshotPublished(snapshot);
	return true;
}

void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
{
	mRenderSnapshotBuilder.RefreshDynamicRenderStateFields(states);
}

bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
	const RuntimeSnapshotVersions& versions, RuntimeRenderStateSnapshot& snapshot) const
{
	std::lock_guard<std::mutex> lock(mPublishedSnapshotMutex);
	if (!mHasPublishedRenderStateSnapshot ||
		!SnapshotMatches(mPublishedRenderStateSnapshot, outputWidth, outputHeight, versions))
	{
		return false;
	}

	snapshot = mPublishedRenderStateSnapshot;
	return true;
}

void RuntimeSnapshotProvider::StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const
{
	std::lock_guard<std::mutex> lock(mPublishedSnapshotMutex);
	mPublishedRenderStateSnapshot = snapshot;
	mHasPublishedRenderStateSnapshot = true;
}

bool RuntimeSnapshotProvider::SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
	const RuntimeSnapshotVersions& versions)
{
	return snapshot.outputWidth == outputWidth &&
		snapshot.outputHeight == outputHeight &&
		snapshot.versions.renderStateVersion == versions.renderStateVersion &&
		snapshot.versions.parameterStateVersion == versions.parameterStateVersion;
}

void RuntimeSnapshotProvider::PublishRenderSnapshotPublishRequested(unsigned outputWidth, unsigned outputHeight, const std::string& reason) const
{
	try
	{
		RenderSnapshotPublishRequestedEvent event;
		event.outputWidth = outputWidth;
		event.outputHeight = outputHeight;
		event.reason = reason;
		if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeSnapshotProvider"))
			OutputDebugStringA("RenderSnapshotPublishRequested event publish failed.\n");
	}
	catch (...)
	{
		OutputDebugStringA("RenderSnapshotPublishRequested event publish threw.\n");
	}
}

void RuntimeSnapshotProvider::PublishRenderSnapshotPublished(const RuntimeRenderStateSnapshot& snapshot) const
{
	try
	{
		RenderSnapshotPublishedEvent event;
		event.snapshotVersion = snapshot.versions.renderStateVersion;
		event.structureVersion = snapshot.versions.renderStateVersion;
		event.parameterVersion = snapshot.versions.parameterStateVersion;
		event.outputWidth = snapshot.outputWidth;
		event.outputHeight = snapshot.outputHeight;
		event.layerCount = snapshot.states.size();
		if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeSnapshotProvider"))
			OutputDebugStringA("RenderSnapshotPublished event publish failed.\n");
	}
	catch (...)
	{
		OutputDebugStringA("RenderSnapshotPublished event publish threw.\n");
	}
}
