#include "RuntimeCoordinator.h"

#include "RuntimeEventDispatcher.h"
#include "RuntimeEventPayloads.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeStore.h"

namespace
{
RuntimeEventRenderResetScope ToRuntimeEventRenderResetScope(RuntimeCoordinatorRenderResetScope scope)
{
	switch (scope)
	{
	case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
		return RuntimeEventRenderResetScope::TemporalHistoryOnly;
	case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
		return RuntimeEventRenderResetScope::TemporalHistoryAndFeedback;
	case RuntimeCoordinatorRenderResetScope::None:
	default:
		return RuntimeEventRenderResetScope::None;
	}
}
}

RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher) :
	mRuntimeStore(runtimeStore),
	mRuntimeEventDispatcher(runtimeEventDispatcher)
{
}

RuntimeCoordinatorResult RuntimeCoordinator::AddLayer(const std::string& shaderId)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	if (!ValidateShaderExists(shaderId, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("AddLayer", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true, true);
	PublishCoordinatorResult("AddLayer", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& layerId)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	if (!ValidateLayerExists(layerId, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("RemoveLayer", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true, true);
	if (result.accepted)
	{
		result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
		result.transientOscLayerKey = layerId;
	}
	PublishCoordinatorResult("RemoveLayer", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerId, int direction)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	bool shouldMove = false;
	if (!ResolveLayerMove(layerId, direction, shouldMove, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("MoveLayer", result);
		return result;
	}
	if (!shouldMove)
	{
		RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true, true);
	PublishCoordinatorResult("MoveLayer", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	bool shouldMove = false;
	if (!ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("MoveLayerToIndex", result);
		return result;
	}
	if (!shouldMove)
	{
		RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true, true);
	PublishCoordinatorResult("MoveLayerToIndex", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& layerId, bool bypassed)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	if (!ValidateLayerExists(layerId, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("SetLayerBypass", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false, true);
	PublishCoordinatorResult("SetLayerBypass", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& layerId, const std::string& shaderId)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	if (!ValidateLayerExists(layerId, error) || !ValidateShaderExists(shaderId, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("SetLayerShader", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false, true);
	PublishCoordinatorResult("SetLayerShader", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	ResolvedParameterMutation mutation;
	if (!BuildParameterMutationById(layerId, parameterId, newValue, true, mutation, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("UpdateLayerParameter", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
	PublishCoordinatorResult("UpdateLayerParameter", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	ResolvedParameterMutation mutation;
	if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, true, mutation, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("UpdateLayerParameterByControlKey", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
	PublishCoordinatorResult("UpdateLayerParameterByControlKey", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
{
	std::lock_guard<std::mutex> lock(mMutex);
	constexpr RuntimeCoordinatorOscCommitPersistence kDefaultOscCommitPersistence =
		RuntimeCoordinatorOscCommitPersistence::SessionOnly;
	constexpr bool kPersistSettledOscCommits =
		kDefaultOscCommitPersistence == RuntimeCoordinatorOscCommitPersistence::Persistent;

	std::string error;
	ResolvedParameterMutation mutation;
	if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, kPersistSettledOscCommits, mutation, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("CommitOscParameterByControlKey", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
	PublishCoordinatorResult("CommitOscParameterByControlKey", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::string& layerId)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	if (!ValidateLayerExists(layerId, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("ResetLayerParameters", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.ResetStoredLayerParameterValues(layerId, error), error, false, false, true);
	if (!result.accepted)
	{
		PublishCoordinatorResult("ResetLayerParameters", result);
		return result;
	}

	result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
	result.transientOscLayerKey = layerId;
	result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
	PublishCoordinatorResult("ResetLayerParameters", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::SaveStackPreset(const std::string& presetName)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	if (!ValidatePresetName(presetName, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("SaveStackPreset", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false, true);
	PublishCoordinatorResult("SaveStackPreset", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& presetName)
{
	std::lock_guard<std::mutex> lock(mMutex);
	std::string error;
	if (!ValidatePresetName(presetName, error))
	{
		RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
		PublishCoordinatorResult("LoadStackPreset", result);
		return result;
	}

	RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false, true);
	PublishCoordinatorResult("LoadStackPreset", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::RequestShaderReload(bool preserveFeedbackState)
{
	std::lock_guard<std::mutex> lock(mMutex);
	PublishManualReloadRequested(preserveFeedbackState, "RequestShaderReload");
	RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState);
	PublishCoordinatorFollowUpEvents("RequestShaderReload", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::PollRuntimeStoreChanges(bool& registryChanged)
{
	std::lock_guard<std::mutex> lock(mMutex);

	registryChanged = false;
	bool reloadRequested = false;
	std::string error;
	if (!mRuntimeStore.PollStoredFileChanges(registryChanged, reloadRequested, error))
	{
		RuntimeCoordinatorResult result = HandleRuntimePollFailure(error);
		return result;
	}

	if (reloadRequested)
	{
		PublishFileChangeDetected("PollRuntimeStoreChanges", registryChanged, reloadRequested);
		RuntimeCoordinatorResult result = BuildQueuedReloadResult(false);
		PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result);
		return result;
	}

	if (registryChanged)
	{
		PublishFileChangeDetected("PollRuntimeStoreChanges", registryChanged, reloadRequested);
		RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
		PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result);
		return result;
	}

	RuntimeCoordinatorResult result;
	result.accepted = true;
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std::string& error)
{
	RuntimeCoordinatorResult result;
	result.accepted = true;
	result.runtimeStateBroadcastRequired = true;
	result.compileStatusChanged = true;
	result.compileStatusSucceeded = false;
	result.compileStatusMessage = error;
	PublishCoordinatorFollowUpEvents("HandleRuntimePollFailure", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mPreserveFeedbackOnNextShaderBuild = false;
	mUseCommittedLayerStates = true;

	RuntimeCoordinatorResult result;
	result.accepted = true;
	result.runtimeStateBroadcastRequired = true;
	result.compileStatusChanged = true;
	result.compileStatusSucceeded = false;
	result.compileStatusMessage = error;
	result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
	PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildFailure", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess()
{
	std::lock_guard<std::mutex> lock(mMutex);
	mUseCommittedLayerStates = false;

	RuntimeCoordinatorResult result;
	result.accepted = true;
	result.runtimeStateBroadcastRequired = true;
	result.compileStatusChanged = true;
	result.compileStatusSucceeded = true;
	result.compileStatusMessage = "Shader layers compiled successfully.";
	result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots;
	mPreserveFeedbackOnNextShaderBuild = false;
	PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildSuccess", result);
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest()
{
	std::lock_guard<std::mutex> lock(mMutex);
	PublishManualReloadRequested(false, "HandleRuntimeReloadRequest");
	RuntimeCoordinatorResult result = BuildQueuedReloadResult(false);
	PublishCoordinatorFollowUpEvents("HandleRuntimeReloadRequest", result);
	return result;
}

void RuntimeCoordinator::ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode)
{
	std::lock_guard<std::mutex> lock(mMutex);
	switch (mode)
	{
	case RuntimeCoordinatorCommittedStateMode::UseCommittedStates:
		mUseCommittedLayerStates = true;
		break;
	case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots:
		mUseCommittedLayerStates = false;
		break;
	case RuntimeCoordinatorCommittedStateMode::Unchanged:
	default:
		break;
	}
}

bool RuntimeCoordinator::UseCommittedLayerStates() const
{
	return mUseCommittedLayerStates.load();
}

bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	return mPreserveFeedbackOnNextShaderBuild;
}

bool RuntimeCoordinator::BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
	bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
	RuntimeStore::StoredParameterSnapshot snapshot;
	if (!mRuntimeStore.TryGetStoredParameterById(layerId, parameterId, snapshot, error))
		return false;

	return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue,
		newValue, persistState, mutation, error);
}

bool RuntimeCoordinator::BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
	bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
	RuntimeStore::StoredParameterSnapshot snapshot;
	if (!mRuntimeStore.TryGetStoredParameterByControlKey(layerKey, parameterKey, snapshot, error))
		return false;

	return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue,
		newValue, persistState, mutation, error);
}

bool RuntimeCoordinator::BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition,
	const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue,
	bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
	mutation.layerId = layerId;
	mutation.parameterId = definition.id;
	mutation.persistState = persistState;

	if (definition.type == ShaderParameterType::Trigger)
	{
		const double previousCount = !hasCurrentValue || currentValue.numberValues.empty()
			? 0.0
			: currentValue.numberValues[0];
		const double triggerTime = mRuntimeStore.GetRuntimeElapsedSeconds();
		mutation.value.numberValues = { previousCount + 1.0, triggerTime };
		mutation.persistState = false;
		return true;
	}

	return NormalizeAndValidateParameterValue(definition, newValue, mutation.value, error);
}

bool RuntimeCoordinator::ValidateLayerExists(const std::string& layerId, std::string& error) const
{
	if (mRuntimeStore.HasStoredLayer(layerId))
		return true;

	error = "Unknown layer id: " + layerId;
	return false;
}

bool RuntimeCoordinator::ValidateShaderExists(const std::string& shaderId, std::string& error) const
{
	if (mRuntimeStore.HasStoredShader(shaderId))
		return true;

	error = "Unknown shader id: " + shaderId;
	return false;
}

bool RuntimeCoordinator::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
	return mRuntimeStore.ResolveStoredLayerMove(layerId, direction, shouldMove, error);
}

bool RuntimeCoordinator::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
	return mRuntimeStore.ResolveStoredLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
}

bool RuntimeCoordinator::ValidatePresetName(const std::string& presetName, std::string& error) const
{
	if (mRuntimeStore.IsValidStackPresetName(presetName))
		return true;

	error = "Preset name must include at least one letter or number.";
	return false;
}

RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState, bool persistenceRequested)
{
	if (!succeeded)
	{
		RuntimeCoordinatorResult result;
		result.accepted = false;
		result.errorMessage = errorMessage;
		return result;
	}

	if (reloadRequired)
	{
		RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState);
		result.persistenceRequested = persistenceRequested;
		return result;
	}

	RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
	result.persistenceRequested = persistenceRequested;
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState)
{
	mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState;
	mUseCommittedLayerStates = true;

	RuntimeCoordinatorResult result;
	result.accepted = true;
	result.runtimeStateBroadcastRequired = true;
	result.shaderBuildRequested = true;
	result.compileStatusChanged = true;
	result.compileStatusSucceeded = true;
	result.compileStatusMessage = "Shader rebuild queued.";
	result.clearReloadRequest = true;
	result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
	return result;
}

RuntimeCoordinatorResult RuntimeCoordinator::BuildAcceptedNoReloadResult() const
{
	RuntimeCoordinatorResult result;
	result.accepted = true;
	result.runtimeStateBroadcastRequired = true;
	return result;
}

void RuntimeCoordinator::PublishFileChangeDetected(const std::string& reason, bool registryChanged, bool reloadRequested) const
{
	try
	{
		FileChangeDetectedEvent event;
		event.path = reason;
		event.shaderPackageCandidate = registryChanged || reloadRequested;
		event.runtimeConfigCandidate = false;
		event.presetCandidate = false;
		mRuntimeEventDispatcher.PublishPayload(event, "RuntimeCoordinator");
	}
	catch (...)
	{
	}
}

void RuntimeCoordinator::PublishManualReloadRequested(bool preserveFeedbackState, const std::string& reason) const
{
	try
	{
		ManualReloadRequestedEvent event;
		event.preserveFeedbackState = preserveFeedbackState;
		event.reason = reason;
		mRuntimeEventDispatcher.PublishPayload(event, "RuntimeCoordinator");
	}
	catch (...)
	{
	}
}

void RuntimeCoordinator::PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const
{
	try
	{
		RuntimeMutationEvent mutation;
		mutation.action = action;
		mutation.accepted = result.accepted;
		mutation.runtimeStateChanged = result.accepted && result.runtimeStateBroadcastRequired;
		mutation.runtimeStateBroadcastRequired = result.runtimeStateBroadcastRequired;
		mutation.shaderBuildRequested = result.shaderBuildRequested;
		mutation.persistenceRequested = result.persistenceRequested;
		mutation.clearTransientOscState = result.transientOscInvalidation != RuntimeCoordinatorTransientOscInvalidation::None;
		mutation.renderResetScope = ToRuntimeEventRenderResetScope(result.renderResetScope);
		mutation.errorMessage = result.errorMessage;
		mRuntimeEventDispatcher.PublishPayload(mutation, "RuntimeCoordinator");

		PublishCoordinatorFollowUpEvents(action, result);
	}
	catch (...)
	{
	}
}

void RuntimeCoordinator::PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const
{
	try
	{
		if (!result.accepted)
			return;

		if (result.runtimeStateBroadcastRequired)
		{
			RuntimeStateChangedEvent stateChanged;
			stateChanged.reason = action;
			stateChanged.renderVisible = result.renderResetScope != RuntimeCoordinatorRenderResetScope::None;
			stateChanged.persistenceRequested = result.persistenceRequested;
			mRuntimeEventDispatcher.PublishPayload(stateChanged, "RuntimeCoordinator");
		}

		if (result.persistenceRequested)
		{
			RuntimePersistenceRequestedEvent persistenceRequested;
			persistenceRequested.request = PersistenceRequest::RuntimeStateRequest(action);
			mRuntimeEventDispatcher.PublishPayload(persistenceRequested, "RuntimeCoordinator");
		}

		if (result.shaderBuildRequested)
		{
			RuntimeReloadRequestedEvent reloadRequested;
			reloadRequested.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild;
			reloadRequested.reason = action;
			mRuntimeEventDispatcher.PublishPayload(reloadRequested, "RuntimeCoordinator");

			ShaderBuildEvent shaderBuild;
			shaderBuild.phase = RuntimeEventShaderBuildPhase::Requested;
			shaderBuild.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild;
			shaderBuild.succeeded = true;
			shaderBuild.message = result.compileStatusMessage;
			mRuntimeEventDispatcher.PublishPayload(shaderBuild, "RuntimeCoordinator");
		}

		if (result.compileStatusChanged)
		{
			CompileStatusChangedEvent compileStatus;
			compileStatus.succeeded = result.compileStatusSucceeded;
			compileStatus.message = result.compileStatusMessage;
			mRuntimeEventDispatcher.PublishPayload(compileStatus, "RuntimeCoordinator");
		}
	}
	catch (...)
	{
	}
}
