#pragma once

#include "../app/AppConfig.h"
#include "../app/AppConfigProvider.h"
#include "../json/JsonWriter.h"
#include "RuntimeLayerModel.h"
#include "SupportedShaderCatalog.h"
#include "../telemetry/CadenceTelemetryJson.h"

#include <cstdint>
#include <string>
#include <vector>

namespace RenderCadenceCompositor
{
struct RuntimeStateJsonInput
{
	const AppConfig& config;
	const CadenceTelemetrySnapshot& telemetry;
	unsigned short serverPort = 0;
	bool videoOutputEnabled = false;
	std::string videoOutputStatus;
	const SupportedShaderCatalog& shaderCatalog;
	const RuntimeLayerModelSnapshot& runtimeLayers;
};

inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
{
	writer.BeginObject();
	writer.KeyString("backend", "decklink");
	writer.KeyNull("modelName");
	writer.KeyBool("supportsInternalKeying", false);
	writer.KeyBool("supportsExternalKeying", false);
	writer.KeyBool("keyerInterfaceAvailable", false);
	writer.KeyBool("externalKeyingRequested", input.config.deckLink.externalKeyingEnabled);
	writer.KeyBool("externalKeyingActive", input.videoOutputEnabled && input.config.deckLink.externalKeyingEnabled);
	writer.KeyString("statusMessage", input.videoOutputStatus);
	writer.EndObject();
}

inline void OutputDimensions(const RuntimeStateJsonInput& input, unsigned& width, unsigned& height)
{
	VideoFormatDimensions(input.config.outputVideoFormat, width, height);
}

inline const char* ShaderParameterTypeName(ShaderParameterType type)
{
	switch (type)
	{
	case ShaderParameterType::Float: return "float";
	case ShaderParameterType::Vec2: return "vec2";
	case ShaderParameterType::Color: return "color";
	case ShaderParameterType::Boolean: return "bool";
	case ShaderParameterType::Enum: return "enum";
	case ShaderParameterType::Text: return "text";
	case ShaderParameterType::Trigger: return "trigger";
	}
	return "unknown";
}

inline void WriteNumberArray(JsonWriter& writer, const std::vector<double>& values)
{
	writer.BeginArray();
	for (double value : values)
		writer.Double(value);
	writer.EndArray();
}

inline void WriteDefaultParameterValue(JsonWriter& writer, const ShaderParameterDefinition& parameter)
{
	switch (parameter.type)
	{
	case ShaderParameterType::Boolean:
		writer.Bool(parameter.defaultBoolean);
		return;
	case ShaderParameterType::Enum:
		writer.String(parameter.defaultEnumValue);
		return;
	case ShaderParameterType::Text:
		writer.String(parameter.defaultTextValue);
		return;
	case ShaderParameterType::Trigger:
		writer.Double(0.0);
		return;
	case ShaderParameterType::Float:
		writer.Double(parameter.defaultNumbers.empty() ? 0.0 : parameter.defaultNumbers.front());
		return;
	case ShaderParameterType::Vec2:
	case ShaderParameterType::Color:
		WriteNumberArray(writer, parameter.defaultNumbers);
		return;
	}
	writer.Null();
}

inline void WriteParameterValue(JsonWriter& writer, const ShaderParameterDefinition& parameter, const ShaderParameterValue& value)
{
	switch (parameter.type)
	{
	case ShaderParameterType::Boolean:
		writer.Bool(value.booleanValue);
		return;
	case ShaderParameterType::Enum:
		writer.String(value.enumValue);
		return;
	case ShaderParameterType::Text:
		writer.String(value.textValue);
		return;
	case ShaderParameterType::Trigger:
	case ShaderParameterType::Float:
		writer.Double(value.numberValues.empty() ? 0.0 : value.numberValues.front());
		return;
	case ShaderParameterType::Vec2:
	case ShaderParameterType::Color:
		WriteNumberArray(writer, value.numberValues);
		return;
	}
	writer.Null();
}

inline void WriteTemporalJson(JsonWriter& writer, const TemporalSettings& temporal)
{
	writer.BeginObject();
	writer.KeyBool("enabled", temporal.enabled);
	writer.KeyString("historySource", "none");
	writer.KeyUInt("requestedHistoryLength", temporal.requestedHistoryLength);
	writer.KeyUInt("effectiveHistoryLength", temporal.effectiveHistoryLength);
	writer.EndObject();
}

inline void WriteFeedbackJson(JsonWriter& writer, const FeedbackSettings& feedback)
{
	writer.BeginObject();
	writer.KeyBool("enabled", feedback.enabled);
	writer.KeyString("writePass", feedback.writePassId);
	writer.EndObject();
}

inline const char* RuntimeLayerBuildStateName(RuntimeLayerBuildState state)
{
	switch (state)
	{
	case RuntimeLayerBuildState::Pending: return "pending";
	case RuntimeLayerBuildState::Ready: return "ready";
	case RuntimeLayerBuildState::Failed: return "failed";
	}
	return "unknown";
}

inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter, const ShaderParameterValue* value)
{
	writer.BeginObject();
	writer.KeyString("id", parameter.id);
	writer.KeyString("label", parameter.label.empty() ? parameter.id : parameter.label);
	writer.KeyString("description", parameter.description);
	writer.KeyString("type", ShaderParameterTypeName(parameter.type));
	writer.Key("defaultValue");
	WriteDefaultParameterValue(writer, parameter);
	writer.Key("value");
	if (value)
		WriteParameterValue(writer, parameter, *value);
	else
		WriteDefaultParameterValue(writer, parameter);

	if (!parameter.minNumbers.empty())
	{
		writer.Key("min");
		WriteNumberArray(writer, parameter.minNumbers);
	}
	if (!parameter.maxNumbers.empty())
	{
		writer.Key("max");
		WriteNumberArray(writer, parameter.maxNumbers);
	}
	if (!parameter.stepNumbers.empty())
	{
		writer.Key("step");
		WriteNumberArray(writer, parameter.stepNumbers);
	}
	if (parameter.type == ShaderParameterType::Enum)
	{
		writer.Key("options");
		writer.BeginArray();
		for (const ShaderParameterOption& option : parameter.enumOptions)
		{
			writer.BeginObject();
			writer.KeyString("value", option.value);
			writer.KeyString("label", option.label.empty() ? option.value : option.label);
			writer.EndObject();
		}
		writer.EndArray();
	}
	if (parameter.type == ShaderParameterType::Text)
	{
		writer.KeyUInt("maxLength", parameter.maxLength);
		if (!parameter.fontId.empty())
			writer.KeyString("font", parameter.fontId);
	}
	writer.EndObject();
}

inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
{
	writer.BeginArray();
	for (const RuntimeLayerReadModel& layer : input.runtimeLayers.displayLayers)
	{
		const ShaderPackage* shaderPackage = input.shaderCatalog.FindPackage(layer.shaderId);
		writer.BeginObject();
		writer.KeyString("id", layer.id);
		writer.KeyString("shaderId", layer.shaderId);
		writer.KeyString("shaderName", layer.shaderName);
		writer.KeyBool("bypass", layer.bypass);
		writer.KeyString("buildState", RuntimeLayerBuildStateName(layer.buildState));
		writer.KeyBool("renderReady", layer.renderReady);
		writer.KeyString("message", layer.message);
		writer.Key("temporal");
		if (shaderPackage)
			WriteTemporalJson(writer, shaderPackage->temporal);
		else
			WriteTemporalJson(writer, TemporalSettings());
		writer.Key("feedback");
		if (shaderPackage)
			WriteFeedbackJson(writer, shaderPackage->feedback);
		else
			WriteFeedbackJson(writer, FeedbackSettings());
		writer.Key("parameters");
		writer.BeginArray();
		for (const ShaderParameterDefinition& parameter : layer.parameterDefinitions)
		{
			const auto valueIt = layer.parameterValues.find(parameter.id);
			WriteParameterDefinitionJson(writer, parameter, valueIt == layer.parameterValues.end() ? nullptr : &valueIt->second);
		}
		writer.EndArray();
		writer.EndObject();
	}
	writer.EndArray();
}

inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
{
	JsonWriter writer;
	writer.BeginObject();

	writer.Key("app");
	writer.BeginObject();
	writer.KeyUInt("serverPort", input.serverPort);
	writer.KeyUInt("oscPort", input.config.oscPort);
	writer.KeyString("oscBindAddress", input.config.oscBindAddress);
	writer.KeyDouble("oscSmoothing", input.config.oscSmoothing);
	writer.KeyBool("autoReload", input.config.autoReload);
	writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames));
	writer.KeyDouble("previewFps", input.config.previewFps);
	writer.KeyBool("enableExternalKeying", input.config.deckLink.externalKeyingEnabled);
	writer.KeyString("inputVideoFormat", input.config.inputVideoFormat);
	writer.KeyString("inputFrameRate", input.config.inputFrameRate);
	writer.KeyString("outputVideoFormat", input.config.outputVideoFormat);
	writer.KeyString("outputFrameRate", input.config.outputFrameRate);
	writer.EndObject();

	writer.Key("runtime");
	writer.BeginObject();
	writer.KeyUInt("layerCount", static_cast<uint64_t>(input.runtimeLayers.displayLayers.size()));
	writer.KeyBool("compileSucceeded", input.runtimeLayers.compileSucceeded);
	writer.KeyString("compileMessage", input.runtimeLayers.compileMessage);
	writer.EndObject();

	writer.Key("video");
	writer.BeginObject();
	unsigned outputWidth = 0;
	unsigned outputHeight = 0;
	OutputDimensions(input, outputWidth, outputHeight);
	writer.KeyBool("hasSignal", input.videoOutputEnabled);
	writer.KeyUInt("width", outputWidth);
	writer.KeyUInt("height", outputHeight);
	writer.KeyString("modeName", input.config.outputVideoFormat + " output-only");
	writer.EndObject();

	writer.Key("decklink");
	WriteVideoIoStatusJson(writer, input);
	writer.Key("videoIO");
	WriteVideoIoStatusJson(writer, input);

	writer.Key("performance");
	writer.BeginObject();
	writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(input.config.outputFrameRate));
	writer.KeyDouble("renderMs", input.telemetry.renderFrameMilliseconds);
	writer.KeyNull("smoothedRenderMs");
	writer.KeyDouble("budgetUsedPercent", input.telemetry.renderFrameBudgetUsedPercent);
	writer.KeyNull("completionIntervalMs");
	writer.KeyNull("smoothedCompletionIntervalMs");
	writer.KeyNull("maxCompletionIntervalMs");
	writer.KeyUInt("lateFrameCount", input.telemetry.displayedLate);
	writer.KeyUInt("droppedFrameCount", input.telemetry.dropped);
	writer.KeyNull("flushedFrameCount");
	writer.Key("cadence");
	WriteCadenceTelemetryJson(writer, input.telemetry);
	writer.EndObject();

	writer.KeyNull("backendPlayout");
	writer.KeyNull("runtimeEvents");
	writer.Key("shaders");
	writer.BeginArray();
	for (const SupportedShaderSummary& shader : input.shaderCatalog.Shaders())
	{
		writer.BeginObject();
		writer.KeyString("id", shader.id);
		writer.KeyString("name", shader.name);
		writer.KeyString("description", shader.description);
		writer.KeyString("category", shader.category);
		writer.KeyBool("available", true);
		writer.KeyNull("error");
		writer.EndObject();
	}
	writer.EndArray();
	writer.Key("stackPresets");
	writer.BeginArray();
	writer.EndArray();
	writer.Key("layers");
	WriteLayersJson(writer, input);

	writer.EndObject();
	return writer.StringValue();
}
}
