#include "RuntimeLiveState.h"

#include "RuntimeParameterUtils.h"

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstddef>

namespace
{
constexpr double kOscSmoothingReferenceFps = 60.0;
constexpr double kOscSmoothingMaxStepSeconds = 0.25;

std::string SimplifyOscControlKey(const std::string& text)
{
	std::string simplified;
	for (unsigned char ch : text)
	{
		if (std::isalnum(ch))
			simplified.push_back(static_cast<char>(std::tolower(ch)));
	}
	return simplified;
}

bool MatchesOscControlKey(const std::string& candidate, const std::string& key)
{
	return candidate == key || SimplifyOscControlKey(candidate) == SimplifyOscControlKey(key);
}

double ClampOscAlpha(double value)
{
	return (std::max)(0.0, (std::min)(1.0, value));
}

double ComputeTimeBasedOscAlpha(double smoothing, double deltaSeconds)
{
	const double clampedSmoothing = ClampOscAlpha(smoothing);
	if (clampedSmoothing <= 0.0)
		return 0.0;
	if (clampedSmoothing >= 1.0)
		return 1.0;

	const double clampedDeltaSeconds = (std::max)(0.0, (std::min)(kOscSmoothingMaxStepSeconds, deltaSeconds));
	if (clampedDeltaSeconds <= 0.0)
		return 0.0;

	const double frameScale = clampedDeltaSeconds * kOscSmoothingReferenceFps;
	return ClampOscAlpha(1.0 - std::pow(1.0 - clampedSmoothing, frameScale));
}

JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
{
	switch (definition.type)
	{
	case ShaderParameterType::Boolean:
		return JsonValue(value.booleanValue);
	case ShaderParameterType::Enum:
		return JsonValue(value.enumValue);
	case ShaderParameterType::Text:
		return JsonValue(value.textValue);
	case ShaderParameterType::Trigger:
	case ShaderParameterType::Float:
		return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
	case ShaderParameterType::Vec2:
	case ShaderParameterType::Color:
	{
		JsonValue array = JsonValue::MakeArray();
		for (double number : value.numberValues)
			array.pushBack(JsonValue(number));
		return array;
	}
	}

	return JsonValue();
}

}

void RuntimeLiveState::Clear()
{
	mOscOverlayStates.clear();
}

void RuntimeLiveState::ClearForLayerKey(const std::string& layerKey)
{
	for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
	{
		if (OverlayMatchesLayerKey(it->second, layerKey))
			it = mOscOverlayStates.erase(it);
		else
			++it;
	}
}

bool RuntimeLiveState::OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey)
{
	return MatchesOscControlKey(overlay.layerKey, layerKey);
}

bool RuntimeLiveState::TryResolveOverlayTarget(
	const OscOverlayState& overlay,
	const std::vector<RuntimeRenderState>& states,
	std::vector<RuntimeRenderState>::const_iterator& stateIt,
	std::vector<ShaderParameterDefinition>::const_iterator& definitionIt)
{
	stateIt = std::find_if(states.begin(), states.end(),
		[&overlay](const RuntimeRenderState& state)
		{
			return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
				MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
				MatchesOscControlKey(state.shaderName, overlay.layerKey);
		});
	if (stateIt == states.end())
		return false;

	definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
		[&overlay](const ShaderParameterDefinition& definition)
		{
			return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
				MatchesOscControlKey(definition.label, overlay.parameterKey);
		});
	return definitionIt != stateIt->parameterDefinitions.end();
}

std::size_t RuntimeLiveState::OverlayCount() const
{
	return mOscOverlayStates.size();
}

void RuntimeLiveState::ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates)
{
	const auto now = std::chrono::steady_clock::now();
	for (const RuntimeLiveOscUpdate& update : updates)
	{
		auto overlayIt = mOscOverlayStates.find(update.routeKey);
		if (overlayIt == mOscOverlayStates.end())
		{
			OscOverlayState overlay;
			overlay.layerKey = update.layerKey;
			overlay.parameterKey = update.parameterKey;
			overlay.targetValue = update.targetValue;
			overlay.lastUpdatedTime = now;
			overlay.lastAppliedTime = now;
			overlay.generation = 1;
			mOscOverlayStates[update.routeKey] = std::move(overlay);
		}
		else
		{
			overlayIt->second.targetValue = update.targetValue;
			overlayIt->second.lastUpdatedTime = now;
			overlayIt->second.generation += 1;
			overlayIt->second.commitQueued = false;
		}
	}
}

void RuntimeLiveState::ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits)
{
	for (const RuntimeLiveOscCommitCompletion& completedCommit : completedCommits)
	{
		auto overlayIt = mOscOverlayStates.find(completedCommit.routeKey);
		if (overlayIt == mOscOverlayStates.end())
			continue;

		OscOverlayState& overlay = overlayIt->second;
		if (overlay.commitQueued &&
			overlay.pendingCommitGeneration == completedCommit.generation &&
			overlay.generation == completedCommit.generation)
		{
			mOscOverlayStates.erase(overlayIt);
		}
	}
}

void RuntimeLiveState::PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states)
{
	for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
	{
		std::vector<RuntimeRenderState>::const_iterator stateIt;
		std::vector<ShaderParameterDefinition>::const_iterator definitionIt;
		if (TryResolveOverlayTarget(it->second, states, stateIt, definitionIt))
		{
			ShaderParameterValue targetValue;
			std::string normalizeError;
			if (NormalizeAndValidateParameterValue(*definitionIt, it->second.targetValue, targetValue, normalizeError))
			{
				++it;
				continue;
			}
		}

		it = mOscOverlayStates.erase(it);
	}
}

void RuntimeLiveState::ApplyToLayerStates(
	std::vector<RuntimeRenderState>& states,
	const RuntimeLiveStateApplyOptions& options,
	std::vector<RuntimeLiveOscCommitRequest>* commitRequests)
{
	if (states.empty() || mOscOverlayStates.empty())
		return;

	PruneIncompatibleOverlays(states);
	if (mOscOverlayStates.empty())
		return;

	const auto now = options.now;
	const double clampedSmoothing = ClampOscAlpha(options.smoothing);
	std::vector<std::string> overlayKeysToRemove;

	for (auto& item : mOscOverlayStates)
	{
		const std::string& routeKey = item.first;
		OscOverlayState& overlay = item.second;
		auto stateIt = std::find_if(states.begin(), states.end(),
			[&overlay](const RuntimeRenderState& state)
			{
				return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
					MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
					MatchesOscControlKey(state.shaderName, overlay.layerKey);
			});
		if (stateIt == states.end())
			continue;

		auto definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
			[&overlay](const ShaderParameterDefinition& definition)
			{
				return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
					MatchesOscControlKey(definition.label, overlay.parameterKey);
			});
		if (definitionIt == stateIt->parameterDefinitions.end())
			continue;

		ShaderParameterValue targetValue;
		std::string normalizeError;
		if (!NormalizeAndValidateParameterValue(*definitionIt, overlay.targetValue, targetValue, normalizeError))
			continue;

		if (definitionIt->type == ShaderParameterType::Trigger)
		{
			ShaderParameterValue& value = stateIt->parameterValues[definitionIt->id];
			const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
			const double triggerTime = stateIt->timeSeconds;
			value.numberValues = { previousCount + 1.0, triggerTime };
			overlayKeysToRemove.push_back(routeKey);
			continue;
		}

		const bool smoothable =
			clampedSmoothing > 0.0 &&
			(definitionIt->type == ShaderParameterType::Float ||
				definitionIt->type == ShaderParameterType::Vec2 ||
				definitionIt->type == ShaderParameterType::Color);
		if (!smoothable)
		{
			overlay.currentValue = targetValue;
			overlay.hasCurrentValue = true;
			stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
			if (options.allowCommit &&
				!overlay.commitQueued &&
				now - overlay.lastUpdatedTime >= options.commitDelay &&
				commitRequests)
			{
				commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation });
				overlay.pendingCommitGeneration = overlay.generation;
				overlay.commitQueued = true;
			}
			continue;
		}

		if (!overlay.hasCurrentValue)
		{
			overlay.currentValue = DefaultValueForDefinition(*definitionIt);
			auto currentIt = stateIt->parameterValues.find(definitionIt->id);
			if (currentIt != stateIt->parameterValues.end())
				overlay.currentValue = currentIt->second;
			overlay.hasCurrentValue = true;
		}

		if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size())
			overlay.currentValue.numberValues = targetValue.numberValues;

		double smoothingAlpha = clampedSmoothing;
		if (overlay.lastAppliedTime != std::chrono::steady_clock::time_point())
		{
			const double deltaSeconds =
				std::chrono::duration_cast<std::chrono::duration<double>>(now - overlay.lastAppliedTime).count();
			smoothingAlpha = ComputeTimeBasedOscAlpha(clampedSmoothing, deltaSeconds);
		}
		overlay.lastAppliedTime = now;

		ShaderParameterValue nextValue = targetValue;
		bool converged = true;
		for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index)
		{
			const double currentNumber = overlay.currentValue.numberValues[index];
			const double targetNumber = targetValue.numberValues[index];
			const double delta = targetNumber - currentNumber;
			double nextNumber = currentNumber + delta * smoothingAlpha;
			if (std::fabs(delta) <= 0.0005)
				nextNumber = targetNumber;
			else
				converged = false;
			nextValue.numberValues[index] = nextNumber;
		}

		if (converged)
			nextValue.numberValues = targetValue.numberValues;

		overlay.currentValue = nextValue;
		overlay.hasCurrentValue = true;
		stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
		if (options.allowCommit &&
			converged &&
			!overlay.commitQueued &&
			now - overlay.lastUpdatedTime >= options.commitDelay &&
			commitRequests)
		{
			commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, BuildOscCommitValue(*definitionIt, overlay.currentValue), overlay.generation });
			overlay.pendingCommitGeneration = overlay.generation;
			overlay.commitQueued = true;
		}
	}

	for (const std::string& overlayKey : overlayKeysToRemove)
		mOscOverlayStates.erase(overlayKey);
}
