#include "RuntimeStatePersistence.h"

#include "../logging/Logger.h"

#include <fstream>

namespace RenderCadenceCompositor
{
namespace
{
JsonValue ParameterValueToJson(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
{
	switch (definition.type)
	{
	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;
	}
	case ShaderParameterType::Boolean:
		return JsonValue(value.booleanValue);
	case ShaderParameterType::Enum:
		return JsonValue(value.enumValue);
	case ShaderParameterType::Text:
		return JsonValue(value.textValue);
	case ShaderParameterType::Trigger:
		return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
	}
	return JsonValue();
}
}

JsonValue RuntimeStateSnapshotToJson(const RuntimeLayerModelSnapshot& snapshot)
{
	JsonValue root = JsonValue::MakeObject();
	JsonValue layers = JsonValue::MakeArray();

	for (const RuntimeLayerReadModel& layer : snapshot.displayLayers)
	{
		JsonValue layerJson = JsonValue::MakeObject();
		layerJson.set("id", JsonValue(layer.id));
		layerJson.set("shaderId", JsonValue(layer.shaderId));
		layerJson.set("bypass", JsonValue(layer.bypass));

		JsonValue parameterValues = JsonValue::MakeObject();
		for (const ShaderParameterDefinition& definition : layer.parameterDefinitions)
		{
			const auto valueIt = layer.parameterValues.find(definition.id);
			if (valueIt != layer.parameterValues.end())
				parameterValues.set(definition.id, ParameterValueToJson(definition, valueIt->second));
		}
		layerJson.set("parameterValues", parameterValues);
		layers.pushBack(layerJson);
	}

	root.set("layers", layers);
	return root;
}

std::string SerializeRuntimeStateSnapshot(const RuntimeLayerModelSnapshot& snapshot)
{
	return SerializeJson(RuntimeStateSnapshotToJson(snapshot), true);
}

RuntimeStatePersistenceWriter::~RuntimeStatePersistenceWriter()
{
	Stop(true);
}

void RuntimeStatePersistenceWriter::Start(const std::filesystem::path& path, std::chrono::milliseconds debounce)
{
	Stop(true);
	mPath = path;
	mDebounce = debounce;
	{
		std::lock_guard<std::mutex> lock(mMutex);
		mStopping = false;
		mHasPendingState = false;
		mPendingState.clear();
	}
	mThread = std::thread([this]() { ThreadMain(); });
}

void RuntimeStatePersistenceWriter::RequestSave(const RuntimeLayerModelSnapshot& snapshot)
{
	const std::string serializedState = SerializeRuntimeStateSnapshot(snapshot);
	{
		std::lock_guard<std::mutex> lock(mMutex);
		mPendingState = serializedState;
		mHasPendingState = true;
	}
	mCondition.notify_one();
}

void RuntimeStatePersistenceWriter::Stop(bool flushPending)
{
	std::string finalState;
	{
		std::lock_guard<std::mutex> lock(mMutex);
		if (flushPending && mHasPendingState)
			finalState = mPendingState;
		mStopping = true;
	}
	mCondition.notify_one();
	if (mThread.joinable())
		mThread.join();

	if (!finalState.empty())
	{
		std::string error;
		if (!WriteSnapshot(finalState, error))
			LogWarning("runtime-state", error);
	}

	std::lock_guard<std::mutex> lock(mMutex);
	mStopping = false;
	mHasPendingState = false;
	mPendingState.clear();
}

void RuntimeStatePersistenceWriter::ThreadMain()
{
	for (;;)
	{
		std::unique_lock<std::mutex> lock(mMutex);
		mCondition.wait(lock, [this]() {
			return mStopping || mHasPendingState;
		});

		if (mStopping)
			return;

		const std::string pendingAtWake = mPendingState;
		mCondition.wait_for(lock, mDebounce, [this, &pendingAtWake]() {
			return mStopping || mPendingState != pendingAtWake;
		});

		if (mStopping)
			return;
		if (mPendingState != pendingAtWake)
			continue;

		const std::string stateToWrite = mPendingState;
		mHasPendingState = false;
		lock.unlock();

		std::string error;
		if (!WriteSnapshot(stateToWrite, error))
			LogWarning("runtime-state", error);
	}
}

bool RuntimeStatePersistenceWriter::WriteSnapshot(const std::string& serializedState, std::string& error) const
{
	if (mPath.empty())
	{
		error = "Runtime state path is empty.";
		return false;
	}

	std::error_code ec;
	std::filesystem::create_directories(mPath.parent_path(), ec);
	if (ec)
	{
		error = "Could not create runtime state directory: " + ec.message();
		return false;
	}

	const std::filesystem::path tempPath = mPath.string() + ".tmp";
	{
		std::ofstream output(tempPath, std::ios::binary | std::ios::trunc);
		if (!output)
		{
			error = "Could not open runtime state temp file: " + tempPath.string();
			return false;
		}
		output << serializedState << "\n";
		if (!output)
		{
			error = "Could not write runtime state temp file: " + tempPath.string();
			return false;
		}
	}

	std::filesystem::rename(tempPath, mPath, ec);
	if (ec)
	{
		std::filesystem::remove(mPath, ec);
		ec.clear();
		std::filesystem::rename(tempPath, mPath, ec);
		if (ec)
		{
			error = "Could not replace runtime state file: " + ec.message();
			return false;
		}
	}

	error.clear();
	return true;
}
}
