#include "RuntimeLayerController.h"

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

#include <chrono>

namespace RenderCadenceCompositor
{
RuntimeLayerController::RuntimeLayerController(RenderLayerPublisher publisher) :
	mPublisher(std::move(publisher))
{
}

RuntimeLayerController::~RuntimeLayerController()
{
	Stop();
}

void RuntimeLayerController::SetPublisher(RenderLayerPublisher publisher)
{
	mPublisher = std::move(publisher);
}

void RuntimeLayerController::Initialize(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames, std::string& runtimeShaderId)
{
	mShaderLibrary = shaderLibrary;
	mMaxTemporalHistoryFrames = maxTemporalHistoryFrames;
	mRuntimeStatePath = ResolveRuntimeStatePath();
	mPersistenceWriter.Start(mRuntimeStatePath, std::chrono::milliseconds(250));
	LoadSupportedShaderCatalog(shaderLibrary, maxTemporalHistoryFrames);
	InitializeLayerModel(runtimeShaderId);
}

void RuntimeLayerController::StartStartupBuild(const std::string& runtimeShaderId)
{
	std::vector<std::pair<std::string, std::string>> buildsToStart;
	{
		std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
		buildsToStart = mRuntimeLayerModel.PendingLayerBuilds();
	}

	if (buildsToStart.empty() && runtimeShaderId.empty())
	{
		Log("runtime-shader", "Runtime shader build disabled.");
		return;
	}

	if (buildsToStart.empty())
	{
		const std::string layerId = FirstRuntimeLayerId();
		if (!layerId.empty())
			buildsToStart.push_back({ layerId, runtimeShaderId });
	}

	for (const auto& build : buildsToStart)
	{
		Log("runtime-shader", "Starting background Slang build for layer '" + build.first + "' shader '" + build.second + "'.");
		StartLayerShaderBuild(build.first, build.second);
	}
}

void RuntimeLayerController::Stop()
{
	mPersistenceWriter.Stop(true);
	StopAllRuntimeShaderBuilds();
}

RuntimeLayerModelSnapshot RuntimeLayerController::Snapshot(const CadenceTelemetrySnapshot& telemetry) const
{
	std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
	RuntimeLayerModelSnapshot snapshot = mRuntimeLayerModel.Snapshot();
	if (telemetry.shaderBuildFailures > 0)
	{
		snapshot.compileSucceeded = false;
		snapshot.compileMessage = "Runtime shader GL commit failed; see logs for details.";
	}
	return snapshot;
}

void RuntimeLayerController::PublishRuntimeRenderLayers()
{
	if (!mPublisher)
		return;

	std::vector<RuntimeRenderLayerModel> renderLayers;
	{
		std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
		renderLayers = mRuntimeLayerModel.Snapshot().renderLayers;
	}
	mPublisher(renderLayers);
}

bool RuntimeLayerController::MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact)
{
	std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
	std::string error;
	if (!mRuntimeLayerModel.MarkBuildReady(artifact, error))
	{
		LogWarning("runtime-shader", error);
		return false;
	}
	return true;
}

void RuntimeLayerController::MarkRuntimeBuildFailedForLayer(const std::string& layerId, const std::string& message)
{
	std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
	std::string error;
	if (!mRuntimeLayerModel.MarkBuildFailed(layerId, message, error))
		LogWarning("runtime-shader", error);
}

}
