#include "LayerStackStore.h"

#include "RuntimeParameterUtils.h"
#include "RuntimeStateJson.h"

#include <algorithm>
#include <cctype>
#include <cmath>
#include <set>
#include <sstream>
#include <utility>

namespace
{
std::string TrimCopy(const std::string& text)
{
	std::size_t start = 0;
	while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
		++start;

	std::size_t end = text.size();
	while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
		--end;

	return text.substr(start, end - start);
}

std::string SimplifyControlKey(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 MatchesControlKey(const std::string& candidate, const std::string& key)
{
	return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
}

bool TryParseLayerIdNumber(const std::string& layerId, uint64_t& number)
{
	const std::string prefix = "layer-";
	if (layerId.rfind(prefix, 0) != 0 || layerId.size() == prefix.size())
		return false;

	uint64_t parsed = 0;
	for (std::size_t index = prefix.size(); index < layerId.size(); ++index)
	{
		const unsigned char ch = static_cast<unsigned char>(layerId[index]);
		if (!std::isdigit(ch))
			return false;
		parsed = parsed * 10 + static_cast<uint64_t>(ch - '0');
	}

	number = parsed;
	return true;
}

}

bool LayerStackStore::LoadPersistentStateValue(const JsonValue& root)
{
	if (const JsonValue* layersValue = root.find("layers"))
	{
		for (const JsonValue& layerValue : layersValue->asArray())
		{
			if (!layerValue.isObject())
				continue;
			LayerPersistentState layer;
			if (const JsonValue* idValue = layerValue.find("id"))
				layer.id = idValue->asString();
			if (const JsonValue* shaderIdValue = layerValue.find("shaderId"))
				layer.shaderId = shaderIdValue->asString();
			if (const JsonValue* bypassValue = layerValue.find("bypass"))
				layer.bypass = bypassValue->asBoolean(false);
			else if (const JsonValue* enabledValue = layerValue.find("enabled"))
				layer.bypass = !enabledValue->asBoolean(true);

			if (const JsonValue* parameterValues = layerValue.find("parameterValues"))
			{
				for (const auto& parameterItem : parameterValues->asObject())
				{
					ShaderParameterValue value;
					const JsonValue& jsonValue = parameterItem.second;
					if (jsonValue.isBoolean())
						value.booleanValue = jsonValue.asBoolean();
					else if (jsonValue.isString())
						value.enumValue = jsonValue.asString();
					else if (jsonValue.isNumber())
						value.numberValues.push_back(jsonValue.asNumber());
					else if (jsonValue.isArray())
						value.numberValues = JsonArrayToNumbers(jsonValue);
					layer.parameterValues[parameterItem.first] = value;
				}
			}

			if (!layer.shaderId.empty())
				mLayers.push_back(layer);
		}
	}
	else
	{
		std::string activeShaderId;
		if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
			activeShaderId = activeShaderValue->asString();

		if (!activeShaderId.empty())
		{
			LayerPersistentState layer;
			layer.id = GenerateLayerId(mLayers, mNextLayerId);
			layer.shaderId = activeShaderId;
			layer.bypass = false;

			if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
			{
				const JsonValue* shaderValues = valuesByShader->find(activeShaderId);
				if (shaderValues)
				{
					for (const auto& parameterItem : shaderValues->asObject())
					{
						ShaderParameterValue value;
						const JsonValue& jsonValue = parameterItem.second;
						if (jsonValue.isBoolean())
							value.booleanValue = jsonValue.asBoolean();
						else if (jsonValue.isString())
							value.enumValue = jsonValue.asString();
						else if (jsonValue.isNumber())
							value.numberValues.push_back(jsonValue.asNumber());
						else if (jsonValue.isArray())
							value.numberValues = JsonArrayToNumbers(jsonValue);
						layer.parameterValues[parameterItem.first] = value;
					}
				}
			}

			mLayers.push_back(layer);
		}
	}

	return true;
}

JsonValue LayerStackStore::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const
{
	JsonValue root = JsonValue::MakeObject();
	JsonValue layers = JsonValue::MakeArray();
	for (const LayerPersistentState& layer : mLayers)
	{
		JsonValue layerValue = JsonValue::MakeObject();
		layerValue.set("id", JsonValue(layer.id));
		layerValue.set("shaderId", JsonValue(layer.shaderId));
		layerValue.set("bypass", JsonValue(layer.bypass));

		JsonValue parameterValues = JsonValue::MakeObject();
		const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
		for (const auto& parameterItem : layer.parameterValues)
		{
			const ShaderParameterDefinition* definition = nullptr;
			if (shaderPackage)
			{
				for (const ShaderParameterDefinition& candidate : shaderPackage->parameters)
				{
					if (candidate.id == parameterItem.first)
					{
						definition = &candidate;
						break;
					}
				}
			}

			if (definition)
				parameterValues.set(parameterItem.first, RuntimeStateJson::SerializeParameterValue(*definition, parameterItem.second));
		}

		layerValue.set("parameterValues", parameterValues);
		layers.pushBack(layerValue);
	}
	root.set("layers", layers);
	return root;
}

void LayerStackStore::NormalizeLayerIds()
{
	std::set<std::string> usedIds;
	uint64_t maxLayerNumber = mNextLayerId;

	for (LayerPersistentState& layer : mLayers)
	{
		uint64_t layerNumber = 0;
		const bool hasReusableId = !layer.id.empty() &&
			usedIds.find(layer.id) == usedIds.end() &&
			TryParseLayerIdNumber(layer.id, layerNumber);

		if (hasReusableId)
		{
			usedIds.insert(layer.id);
			maxLayerNumber = (std::max)(maxLayerNumber, layerNumber);
			continue;
		}

		do
		{
			++maxLayerNumber;
			layer.id = "layer-" + std::to_string(maxLayerNumber);
		}
		while (usedIds.find(layer.id) != usedIds.end());

		usedIds.insert(layer.id);
	}

	mNextLayerId = maxLayerNumber;
}

void LayerStackStore::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog)
{
	for (LayerPersistentState& layer : mLayers)
	{
		const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
		if (shaderPackage)
			EnsureLayerDefaults(layer, *shaderPackage);
	}
}

void LayerStackStore::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog)
{
	if (!mLayers.empty() || shaderCatalog.PackageOrder().empty())
		return;

	LayerPersistentState layer;
	layer.id = GenerateLayerId(mLayers, mNextLayerId);
	layer.shaderId = shaderCatalog.PackageOrder().front();
	layer.bypass = false;
	if (const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId))
		EnsureLayerDefaults(layer, *shaderPackage);
	mLayers.push_back(layer);
}

void LayerStackStore::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog)
{
	for (auto it = mLayers.begin(); it != mLayers.end();)
	{
		if (!shaderCatalog.HasPackage(it->shaderId))
			it = mLayers.erase(it);
		else
			++it;
	}
}

bool LayerStackStore::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
{
	const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
	if (!shaderPackage)
	{
		error = "Unknown shader id: " + shaderId;
		return false;
	}

	LayerPersistentState layer;
	layer.id = GenerateLayerId(mLayers, mNextLayerId);
	layer.shaderId = shaderId;
	layer.bypass = false;
	EnsureLayerDefaults(layer, *shaderPackage);
	mLayers.push_back(layer);
	return true;
}

bool LayerStackStore::DeleteLayer(const std::string& layerId, std::string& error)
{
	auto it = std::find_if(mLayers.begin(), mLayers.end(),
		[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
	if (it == mLayers.end())
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	mLayers.erase(it);
	return true;
}

bool LayerStackStore::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
	auto it = std::find_if(mLayers.begin(), mLayers.end(),
		[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
	if (it == mLayers.end())
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
	const std::ptrdiff_t newIndex = index + direction;
	if (newIndex < 0 || newIndex >= static_cast<std::ptrdiff_t>(mLayers.size()))
		return true;

	std::swap(mLayers[index], mLayers[newIndex]);
	return true;
}

bool LayerStackStore::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
	auto it = std::find_if(mLayers.begin(), mLayers.end(),
		[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
	if (it == mLayers.end())
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	if (mLayers.empty())
		return true;

	if (targetIndex >= mLayers.size())
		targetIndex = mLayers.size() - 1;

	const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
	if (sourceIndex == targetIndex)
		return true;

	LayerPersistentState movedLayer = *it;
	mLayers.erase(mLayers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
	mLayers.insert(mLayers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
	return true;
}

bool LayerStackStore::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
{
	LayerPersistentState* layer = FindLayerById(layerId);
	if (!layer)
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	layer->bypass = bypassed;
	return true;
}

bool LayerStackStore::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
{
	LayerPersistentState* layer = FindLayerById(layerId);
	if (!layer)
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
	if (!shaderPackage)
	{
		error = "Unknown shader id: " + shaderId;
		return false;
	}

	layer->shaderId = shaderId;
	layer->parameterValues.clear();
	EnsureLayerDefaults(*layer, *shaderPackage);
	return true;
}

bool LayerStackStore::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error)
{
	LayerPersistentState* layer = FindLayerById(layerId);
	if (!layer)
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	layer->parameterValues[parameterId] = value;
	return true;
}

bool LayerStackStore::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error)
{
	LayerPersistentState* layer = FindLayerById(layerId);
	if (!layer)
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
	if (!shaderPackage)
	{
		error = "Unknown shader id: " + layer->shaderId;
		return false;
	}

	layer->parameterValues.clear();
	EnsureLayerDefaults(*layer, *shaderPackage);
	return true;
}

bool LayerStackStore::HasLayer(const std::string& layerId) const
{
	return FindLayerById(layerId) != nullptr;
}

bool LayerStackStore::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
{
	const LayerPersistentState* layer = FindLayerById(layerId);
	if (!layer)
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
	if (!shaderPackage)
	{
		error = "Unknown shader id: " + layer->shaderId;
		return false;
	}

	auto parameterIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
		[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
	if (parameterIt == shaderPackage->parameters.end())
	{
		error = "Unknown parameter id: " + parameterId;
		return false;
	}

	snapshot = StoredParameterSnapshot();
	snapshot.layerId = layer->id;
	snapshot.definition = *parameterIt;
	auto valueIt = layer->parameterValues.find(parameterIt->id);
	if (valueIt != layer->parameterValues.end())
	{
		snapshot.currentValue = valueIt->second;
		snapshot.hasCurrentValue = true;
	}
	return true;
}

bool LayerStackStore::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
{
	const LayerPersistentState* matchedLayer = nullptr;
	const ShaderPackage* matchedPackage = nullptr;

	for (const LayerPersistentState& layer : mLayers)
	{
		const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
		if (!shaderPackage)
			continue;

		if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderPackage->id, layerKey) ||
			MatchesControlKey(shaderPackage->displayName, layerKey))
		{
			matchedLayer = &layer;
			matchedPackage = shaderPackage;
			break;
		}
	}

	if (!matchedLayer || !matchedPackage)
	{
		error = "Unknown OSC layer key: " + layerKey;
		return false;
	}

	auto parameterIt = std::find_if(matchedPackage->parameters.begin(), matchedPackage->parameters.end(),
		[&parameterKey](const ShaderParameterDefinition& definition)
		{
			return MatchesControlKey(definition.id, parameterKey) || MatchesControlKey(definition.label, parameterKey);
		});
	if (parameterIt == matchedPackage->parameters.end())
	{
		error = "Unknown OSC parameter key: " + parameterKey;
		return false;
	}

	snapshot = StoredParameterSnapshot();
	snapshot.layerId = matchedLayer->id;
	snapshot.definition = *parameterIt;
	auto valueIt = matchedLayer->parameterValues.find(parameterIt->id);
	if (valueIt != matchedLayer->parameterValues.end())
	{
		snapshot.currentValue = valueIt->second;
		snapshot.hasCurrentValue = true;
	}
	return true;
}

bool LayerStackStore::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
	auto it = std::find_if(mLayers.begin(), mLayers.end(),
		[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
	if (it == mLayers.end())
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
	const std::ptrdiff_t newIndex = index + direction;
	shouldMove = newIndex >= 0 && newIndex < static_cast<std::ptrdiff_t>(mLayers.size()) && newIndex != index;
	return true;
}

bool LayerStackStore::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
	auto it = std::find_if(mLayers.begin(), mLayers.end(),
		[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
	if (it == mLayers.end())
	{
		error = "Unknown layer id: " + layerId;
		return false;
	}

	if (mLayers.empty())
	{
		shouldMove = false;
		return true;
	}

	const std::size_t clampedTargetIndex = (std::min)(targetIndex, mLayers.size() - 1);
	const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
	shouldMove = sourceIndex != clampedTargetIndex;
	return true;
}

JsonValue LayerStackStore::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const
{
	JsonValue root = JsonValue::MakeObject();
	root.set("version", JsonValue(1.0));
	root.set("name", JsonValue(TrimCopy(presetName)));
	root.set("layers", RuntimeStateJson::SerializeLayerStack(*this, shaderCatalog));
	return root;
}

bool LayerStackStore::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error)
{
	const JsonValue* layersValue = root.find("layers");
	if (!layersValue || !layersValue->isArray())
	{
		error = "Preset file is missing a valid 'layers' array.";
		return false;
	}

	std::vector<LayerPersistentState> nextLayers;
	uint64_t nextLayerId = mNextLayerId;
	if (!DeserializeLayerStack(shaderCatalog, *layersValue, nextLayers, nextLayerId, error))
		return false;

	if (nextLayers.empty())
	{
		error = "Preset does not contain any valid layers.";
		return false;
	}

	mLayers = std::move(nextLayers);
	mNextLayerId = nextLayerId;
	return true;
}

std::string LayerStackStore::MakeSafePresetFileStem(const std::string& presetName)
{
	return ::MakeSafePresetFileStem(presetName);
}

const std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers() const
{
	return mLayers;
}

std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers()
{
	return mLayers;
}

std::size_t LayerStackStore::LayerCount() const
{
	return mLayers.size();
}

const LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId) const
{
	auto it = std::find_if(mLayers.begin(), mLayers.end(),
		[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
	return it == mLayers.end() ? nullptr : &*it;
}

LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId)
{
	auto it = std::find_if(mLayers.begin(), mLayers.end(),
		[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
	return it == mLayers.end() ? nullptr : &*it;
}

ShaderParameterValue LayerStackStore::DefaultValueForDefinition(const ShaderParameterDefinition& definition)
{
	return ::DefaultValueForDefinition(definition);
}

void LayerStackStore::EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage)
{
	for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
	{
		auto valueIt = layerState.parameterValues.find(definition.id);
		if (valueIt == layerState.parameterValues.end())
		{
			layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
			continue;
		}

		JsonValue valueJson;
		bool shouldNormalize = true;
		switch (definition.type)
		{
		case ShaderParameterType::Float:
			if (valueIt->second.numberValues.empty())
				shouldNormalize = false;
			else
				valueJson = JsonValue(valueIt->second.numberValues.front());
			break;
		case ShaderParameterType::Vec2:
		case ShaderParameterType::Color:
			valueJson = JsonValue::MakeArray();
			for (double number : valueIt->second.numberValues)
				valueJson.pushBack(JsonValue(number));
			break;
		case ShaderParameterType::Boolean:
			valueJson = JsonValue(valueIt->second.booleanValue);
			break;
		case ShaderParameterType::Enum:
			valueJson = JsonValue(valueIt->second.enumValue);
			break;
		case ShaderParameterType::Text:
		{
			const std::string textValue = !valueIt->second.textValue.empty()
				? valueIt->second.textValue
				: valueIt->second.enumValue;
			if (textValue.empty())
			{
				valueIt->second = DefaultValueForDefinition(definition);
				shouldNormalize = false;
			}
			else
			{
				valueJson = JsonValue(textValue);
			}
			break;
		}
		case ShaderParameterType::Trigger:
			if (valueIt->second.numberValues.empty())
				valueJson = JsonValue(0.0);
			else
				valueJson = JsonValue((std::max)(0.0, std::floor(valueIt->second.numberValues.front())));
			break;
		}

		if (!shouldNormalize)
			continue;

		ShaderParameterValue normalizedValue;
		std::string normalizeError;
		if (NormalizeAndValidateParameterValue(definition, valueJson, normalizedValue, normalizeError))
			valueIt->second = normalizedValue;
		else
			valueIt->second = DefaultValueForDefinition(definition);
	}
}

bool LayerStackStore::DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error)
{
	for (const JsonValue& layerValue : layersValue.asArray())
	{
		if (!layerValue.isObject())
			continue;

		const JsonValue* shaderIdValue = layerValue.find("shaderId");
		if (!shaderIdValue)
			continue;

		const std::string shaderId = shaderIdValue->asString();
		const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
		if (!shaderPackage)
		{
			error = "Preset references unknown shader id: " + shaderId;
			return false;
		}

		LayerPersistentState layer;
		layer.id = GenerateLayerId(layers, nextLayerId);
		layer.shaderId = shaderId;
		if (const JsonValue* bypassValue = layerValue.find("bypass"))
			layer.bypass = bypassValue->asBoolean(false);

		if (const JsonValue* parametersValue = layerValue.find("parameters"))
		{
			for (const JsonValue& parameterValue : parametersValue->asArray())
			{
				if (!parameterValue.isObject())
					continue;

				const JsonValue* parameterIdValue = parameterValue.find("id");
				const JsonValue* valueValue = parameterValue.find("value");
				if (!parameterIdValue || !valueValue)
					continue;

				const std::string parameterId = parameterIdValue->asString();
				auto definitionIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
					[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
				if (definitionIt == shaderPackage->parameters.end())
					continue;

				ShaderParameterValue normalizedValue;
				if (!NormalizeAndValidateParameterValue(*definitionIt, *valueValue, normalizedValue, error))
					return false;

				layer.parameterValues[parameterId] = normalizedValue;
			}
		}

		EnsureLayerDefaults(layer, *shaderPackage);
		layers.push_back(layer);
	}

	return true;
}

std::string LayerStackStore::GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId)
{
	while (true)
	{
		++nextLayerId;
		const std::string candidate = "layer-" + std::to_string(nextLayerId);
		auto it = std::find_if(layers.begin(), layers.end(),
			[&candidate](const LayerPersistentState& layer) { return layer.id == candidate; });
		if (it == layers.end())
			return candidate;
	}
}
