#include "RenderFrameStateResolver.h"

#include <chrono>

namespace
{
constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150);
}

RenderFrameStateResolver::RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider) :
	mRuntimeSnapshotProvider(runtimeSnapshotProvider)
{
}

void RenderFrameStateResolver::StoreCommittedSnapshot(
	const RuntimeRenderStateSnapshot& snapshot,
	const std::vector<RuntimeRenderState>& committedLayerStates)
{
	mCachedLayerRenderStates = committedLayerStates;
	mCachedRenderStateVersion = snapshot.versions.renderStateVersion;
	mCachedParameterStateVersion = snapshot.versions.parameterStateVersion;
	mCachedRenderStateWidth = snapshot.outputWidth;
	mCachedRenderStateHeight = snapshot.outputHeight;
}

bool RenderFrameStateResolver::Resolve(
	const RenderFrameInput& input,
	const std::vector<RuntimeRenderState>& committedLayerStates,
	RuntimeLiveState& liveState,
	std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
	RenderFrameState& frameState)
{
	frameState.hasInputSource = input.hasInputSource;
	frameState.inputFrameWidth = input.inputFrameWidth;
	frameState.inputFrameHeight = input.inputFrameHeight;
	frameState.captureTextureWidth = input.captureTextureWidth;
	frameState.inputPixelFormat = input.inputPixelFormat;
	frameState.historyCap = input.historyCap;
	frameState.layerStates.clear();

	if (input.useCommittedLayerStates)
	{
		frameState.layerStates = ComposeLayerStates(committedLayerStates, liveState, false, input.oscSmoothing, commitRequests);
		mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
		return true;
	}

	const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions();
	const bool renderStateCacheValid =
		!mCachedLayerRenderStates.empty() &&
		mCachedRenderStateVersion == versions.renderStateVersion &&
		mCachedRenderStateWidth == input.renderWidth &&
		mCachedRenderStateHeight == input.renderHeight;

	if (renderStateCacheValid)
	{
		RuntimeRenderStateSnapshot renderSnapshot;
		renderSnapshot.outputWidth = input.renderWidth;
		renderSnapshot.outputHeight = input.renderHeight;
		renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion;
		renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion;
		renderSnapshot.states = mCachedLayerRenderStates;

		renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
		if (mCachedParameterStateVersion != versions.parameterStateVersion &&
			mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot))
		{
			mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
			renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
		}

		mCachedLayerRenderStates = renderSnapshot.states;
		frameState.layerStates = renderSnapshot.states;
		mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
		return true;
	}

	RuntimeRenderStateSnapshot renderSnapshot;
	if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(input.renderWidth, input.renderHeight, renderSnapshot))
	{
		mCachedLayerRenderStates = renderSnapshot.states;
		mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion;
		mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
		mCachedRenderStateWidth = renderSnapshot.outputWidth;
		mCachedRenderStateHeight = renderSnapshot.outputHeight;
		mCachedLayerRenderStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
		frameState.layerStates = mCachedLayerRenderStates;
		return true;
	}

	frameState.layerStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
	mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
	return !frameState.layerStates.empty();
}

std::vector<RuntimeRenderState> RenderFrameStateResolver::ComposeLayerStates(
	const std::vector<RuntimeRenderState>& baseStates,
	RuntimeLiveState& liveState,
	bool allowCommit,
	double smoothing,
	std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const
{
	LayeredRenderStateInput input;
	input.committedLiveLayerStates = &baseStates;
	input.transientAutomationOverlay = &liveState;
	input.allowTransientAutomationCommits = allowCommit;
	input.collectTransientAutomationCommitRequests = commitRequests != nullptr;
	input.transientAutomationSmoothing = smoothing;
	input.transientAutomationCommitDelay = kOscOverlayCommitDelay;
	input.now = std::chrono::steady_clock::now();
	const RenderStateCompositionResult result = mRenderStateComposer.BuildFrameState(input);

	if (commitRequests)
	{
		for (const RuntimeLiveOscCommitRequest& request : result.commitRequests)
			commitRequests->push_back(request);
	}
	return result.layerStates;
}
