#include "SupportedShaderCatalog.h"

#include "ShaderPackageRegistry.h"

#include <map>
#include <sstream>
#include <utility>

namespace RenderCadenceCompositor
{
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
{
	if (shaderPackage.passes.empty())
		return { false, "Shader package has no render passes." };

	if (shaderPackage.temporal.enabled)
		return { false, "RenderCadenceCompositor currently supports only stateless shaders; temporal history is not enabled in this app." };

	if (shaderPackage.feedback.enabled)
		return { false, "RenderCadenceCompositor currently supports only stateless shaders; feedback storage is not enabled in this app." };

	if (!shaderPackage.textureAssets.empty())
		return { false, "RenderCadenceCompositor does not load shader texture assets yet; texture-backed shaders need a CPU-prepared asset handoff first." };

	for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
	{
		if (parameter.type != ShaderParameterType::Text)
			continue;

		if (parameter.fontId.empty())
			return { false, "Text parameter '" + parameter.id + "' must reference a declared font asset." };

		bool hasFontAsset = false;
		for (const ShaderFontAsset& fontAsset : shaderPackage.fontAssets)
		{
			if (fontAsset.id == parameter.fontId)
			{
				hasFontAsset = true;
				break;
			}
		}
		if (!hasFontAsset)
			return { false, "Text parameter '" + parameter.id + "' references unknown font asset '" + parameter.fontId + "'." };
	}

	bool writesLayerOutput = false;
	for (const ShaderPassDefinition& pass : shaderPackage.passes)
	{
		if (pass.sourcePath.empty())
		{
			return { false, "Shader pass '" + pass.id + "' has no source." };
		}
		if (pass.outputName == "layerOutput")
			writesLayerOutput = true;
		for (const std::string& inputName : pass.inputNames)
		{
			if (inputName == "videoInput" || inputName == "layerInput")
				continue;
			bool matchesNamedOutput = false;
			for (const ShaderPassDefinition& outputPass : shaderPackage.passes)
			{
				if (outputPass.outputName == inputName)
				{
					matchesNamedOutput = true;
					break;
				}
			}
			if (!matchesNamedOutput)
				return { false, "Shader pass '" + pass.id + "' references unknown input '" + inputName + "'." };
		}
	}
	if (!writesLayerOutput)
		return { false, "Shader package must write a pass output named 'layerOutput'." };

	return { true, std::string() };
}

std::string ShaderPackageFingerprint(const ShaderPackage& shaderPackage)
{
	const auto fileTimeText = [](std::filesystem::file_time_type value) {
		return std::to_string(value.time_since_epoch().count());
	};

	std::ostringstream source;
	source << shaderPackage.id << "\n"
		<< shaderPackage.displayName << "\n"
		<< shaderPackage.description << "\n"
		<< shaderPackage.category << "\n"
		<< shaderPackage.entryPoint << "\n"
		<< fileTimeText(shaderPackage.manifestWriteTime) << "\n"
		<< fileTimeText(shaderPackage.shaderWriteTime) << "\n";
	for (const ShaderPassDefinition& pass : shaderPackage.passes)
	{
		source << "pass:" << pass.id << ":" << pass.entryPoint << ":" << pass.outputName << ":"
			<< fileTimeText(pass.sourceWriteTime) << "\n";
		for (const std::string& inputName : pass.inputNames)
			source << "input:" << inputName << "\n";
	}
	for (const ShaderFontAsset& font : shaderPackage.fontAssets)
		source << "font:" << font.id << ":" << font.path.string() << ":" << fileTimeText(font.writeTime) << "\n";
	for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
	{
		source << "param:" << parameter.id << ":" << static_cast<int>(parameter.type) << ":"
			<< parameter.label << ":" << parameter.description << ":" << parameter.fontId << ":"
			<< parameter.defaultTextValue << ":" << parameter.defaultBoolean << ":"
			<< parameter.defaultEnumValue << ":" << parameter.maxLength << "\n";
		for (double value : parameter.defaultNumbers)
			source << "default:" << value << "\n";
		for (double value : parameter.minNumbers)
			source << "min:" << value << "\n";
		for (double value : parameter.maxNumbers)
			source << "max:" << value << "\n";
		for (const ShaderParameterOption& option : parameter.enumOptions)
			source << "option:" << option.value << ":" << option.label << "\n";
	}

	const std::string text = source.str();
	return std::to_string(text.size()) + ":" + std::to_string(std::hash<std::string>{}(text));
}

bool SupportedShaderCatalog::Load(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error)
{
	mShaders.clear();
	mPackagesById.clear();
	mFontAtlasesByShaderId.clear();

	if (shaderRoot.empty())
	{
		error = "Shader library path is empty.";
		return false;
	}

	ShaderPackageRegistry registry(maxTemporalHistoryFrames);
	std::map<std::string, ShaderPackage> packagesById;
	std::vector<std::string> packageOrder;
	std::vector<ShaderPackageStatus> packageStatuses;
	if (!registry.Scan(shaderRoot, packagesById, packageOrder, packageStatuses, error))
		return false;

	FontAtlasBuildConfig fontConfig;
	fontConfig.repoRoot = shaderRoot.parent_path();
	FontAtlasBuilder fontAtlasBuilder(fontConfig);

	for (const std::string& packageId : packageOrder)
	{
		const auto packageIt = packagesById.find(packageId);
		if (packageIt == packagesById.end())
			continue;

		const ShaderPackage& shaderPackage = packageIt->second;
		if (!shaderPackage.fontAssets.empty())
		{
			std::vector<FontAtlasBuildOutput> fontAtlasOutputs;
			std::string fontAtlasError;
			if (fontAtlasBuilder.BuildPackageFontAtlases(shaderPackage, fontAtlasOutputs, fontAtlasError))
				mFontAtlasesByShaderId[shaderPackage.id] = std::move(fontAtlasOutputs);
		}

		const ShaderSupportResult support = CheckStatelessSinglePassShaderSupport(shaderPackage);
		if (!support.supported)
			continue;

		SupportedShaderSummary summary;
		summary.id = shaderPackage.id;
		summary.name = shaderPackage.displayName.empty() ? shaderPackage.id : shaderPackage.displayName;
		summary.description = shaderPackage.description;
		summary.category = shaderPackage.category;
		mShaders.push_back(std::move(summary));
		mPackagesById[shaderPackage.id] = shaderPackage;
	}

	error.clear();
	return true;
}

const ShaderPackage* SupportedShaderCatalog::FindPackage(const std::string& shaderId) const
{
	const auto packageIt = mPackagesById.find(shaderId);
	return packageIt == mPackagesById.end() ? nullptr : &packageIt->second;
}
}
