#include "RuntimeStatePresenter.h"

#include "RuntimeStateJson.h"
#include "RuntimeStore.h"

std::string RuntimeStatePresenter::BuildRuntimeStateJson(const RuntimeStore& runtimeStore)
{
	return SerializeJson(BuildRuntimeStateValue(runtimeStore), true);
}

JsonValue RuntimeStatePresenter::BuildRuntimeStateValue(const RuntimeStore& runtimeStore)
{
	const RuntimeStatePresentationReadModel model = runtimeStore.BuildRuntimeStatePresentationReadModel();
	const HealthTelemetry::Snapshot& telemetrySnapshot = model.telemetry;

	JsonValue root = JsonValue::MakeObject();

	JsonValue app = JsonValue::MakeObject();
	app.set("serverPort", JsonValue(static_cast<double>(model.serverPort)));
	app.set("oscPort", JsonValue(static_cast<double>(model.config.oscPort)));
	app.set("oscBindAddress", JsonValue(model.config.oscBindAddress));
	app.set("oscSmoothing", JsonValue(model.config.oscSmoothing));
	app.set("autoReload", JsonValue(model.autoReloadEnabled));
	app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(model.config.maxTemporalHistoryFrames)));
	app.set("previewFps", JsonValue(static_cast<double>(model.config.previewFps)));
	app.set("enableExternalKeying", JsonValue(model.config.enableExternalKeying));
	app.set("inputVideoFormat", JsonValue(model.config.inputVideoFormat));
	app.set("inputFrameRate", JsonValue(model.config.inputFrameRate));
	app.set("outputVideoFormat", JsonValue(model.config.outputVideoFormat));
	app.set("outputFrameRate", JsonValue(model.config.outputFrameRate));
	root.set("app", app);

	JsonValue runtime = JsonValue::MakeObject();
	runtime.set("layerCount", JsonValue(static_cast<double>(model.layerStack.LayerCount())));
	runtime.set("compileSucceeded", JsonValue(model.compileSucceeded));
	runtime.set("compileMessage", JsonValue(model.compileMessage));
	root.set("runtime", runtime);

	JsonValue video = JsonValue::MakeObject();
	video.set("hasSignal", JsonValue(telemetrySnapshot.signal.hasSignal));
	video.set("width", JsonValue(static_cast<double>(telemetrySnapshot.signal.width)));
	video.set("height", JsonValue(static_cast<double>(telemetrySnapshot.signal.height)));
	video.set("modeName", JsonValue(telemetrySnapshot.signal.modeName));
	root.set("video", video);

	JsonValue deckLink = JsonValue::MakeObject();
	deckLink.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
	deckLink.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
	deckLink.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
	deckLink.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
	deckLink.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
	deckLink.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
	deckLink.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
	root.set("decklink", deckLink);

	JsonValue videoIO = JsonValue::MakeObject();
	videoIO.set("backend", JsonValue(telemetrySnapshot.videoIO.backendName));
	videoIO.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
	videoIO.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
	videoIO.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
	videoIO.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
	videoIO.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
	videoIO.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
	videoIO.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
	root.set("videoIO", videoIO);

	JsonValue performance = JsonValue::MakeObject();
	performance.set("frameBudgetMs", JsonValue(telemetrySnapshot.performance.frameBudgetMilliseconds));
	performance.set("renderMs", JsonValue(telemetrySnapshot.performance.renderMilliseconds));
	performance.set("smoothedRenderMs", JsonValue(telemetrySnapshot.performance.smoothedRenderMilliseconds));
	performance.set("budgetUsedPercent", JsonValue(
		telemetrySnapshot.performance.frameBudgetMilliseconds > 0.0
			? (telemetrySnapshot.performance.smoothedRenderMilliseconds / telemetrySnapshot.performance.frameBudgetMilliseconds) * 100.0
			: 0.0));
	performance.set("completionIntervalMs", JsonValue(telemetrySnapshot.performance.completionIntervalMilliseconds));
	performance.set("smoothedCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.smoothedCompletionIntervalMilliseconds));
	performance.set("maxCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.maxCompletionIntervalMilliseconds));
	performance.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.lateFrameCount)));
	performance.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.droppedFrameCount)));
	performance.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.flushedFrameCount)));
	root.set("performance", performance);

	JsonValue eventQueue = JsonValue::MakeObject();
	eventQueue.set("name", JsonValue(telemetrySnapshot.runtimeEvents.queue.queueName));
	eventQueue.set("depth", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.depth)));
	eventQueue.set("capacity", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.capacity)));
	eventQueue.set("droppedCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.droppedCount)));
	eventQueue.set("oldestEventAgeMs", JsonValue(telemetrySnapshot.runtimeEvents.queue.oldestEventAgeMilliseconds));

	JsonValue eventDispatch = JsonValue::MakeObject();
	eventDispatch.set("dispatchCallCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.dispatchCallCount)));
	eventDispatch.set("dispatchedEventCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.dispatchedEventCount)));
	eventDispatch.set("handlerInvocationCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.handlerInvocationCount)));
	eventDispatch.set("handlerFailureCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.handlerFailureCount)));
	eventDispatch.set("lastDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.lastDispatchDurationMilliseconds));
	eventDispatch.set("maxDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.maxDispatchDurationMilliseconds));

	JsonValue runtimeEvents = JsonValue::MakeObject();
	runtimeEvents.set("queue", eventQueue);
	runtimeEvents.set("dispatch", eventDispatch);
	root.set("runtimeEvents", runtimeEvents);

	JsonValue shaderLibrary = JsonValue::MakeArray();
	for (const ShaderPackageStatus& status : model.packageStatuses)
	{
		JsonValue shader = JsonValue::MakeObject();
		shader.set("id", JsonValue(status.id));
		shader.set("name", JsonValue(status.displayName));
		shader.set("description", JsonValue(status.description));
		shader.set("category", JsonValue(status.category));
		shader.set("available", JsonValue(status.available));
		if (!status.available)
			shader.set("error", JsonValue(status.error));

		auto shaderIt = model.shaderCatalog.packagesById.find(status.id);
		if (status.available && shaderIt != model.shaderCatalog.packagesById.end() && shaderIt->second.temporal.enabled)
		{
			const ShaderPackage& shaderPackage = shaderIt->second;
			JsonValue temporal = JsonValue::MakeObject();
			temporal.set("enabled", JsonValue(true));
			temporal.set("historySource", JsonValue(RuntimeStateJson::TemporalHistorySourceToString(shaderPackage.temporal.historySource)));
			temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.requestedHistoryLength)));
			temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.effectiveHistoryLength)));
			shader.set("temporal", temporal);
		}
		if (status.available && shaderIt != model.shaderCatalog.packagesById.end() && shaderIt->second.feedback.enabled)
		{
			const ShaderPackage& shaderPackage = shaderIt->second;
			JsonValue feedback = JsonValue::MakeObject();
			feedback.set("enabled", JsonValue(true));
			feedback.set("writePass", JsonValue(shaderPackage.feedback.writePassId));
			shader.set("feedback", feedback);
		}
		shaderLibrary.pushBack(shader);
	}
	root.set("shaders", shaderLibrary);

	JsonValue stackPresets = JsonValue::MakeArray();
	for (const std::string& presetName : model.stackPresetNames)
		stackPresets.pushBack(JsonValue(presetName));
	root.set("stackPresets", stackPresets);

	root.set("layers", RuntimeStateJson::SerializeLayerStack(model.layerStack.Layers(), model.shaderCatalog.packagesById));
	return root;
}
