#include "SupportedShaderCatalog.h"
#include "ShaderPackageRegistry.h"

#include <filesystem>
#include <iostream>
#include <string>

namespace
{
int gFailures = 0;

void Expect(bool condition, const std::string& message)
{
	if (condition)
		return;

	++gFailures;
	std::cerr << "FAIL: " << message << "\n";
}

ShaderPackage MakeSinglePassPackage()
{
	ShaderPackage shaderPackage;
	shaderPackage.id = "supported";
	ShaderPassDefinition pass;
	pass.id = "main";
	pass.entryPoint = "mainImage";
	pass.sourcePath = "shader.slang";
	pass.outputName = "layerOutput";
	shaderPackage.passes.push_back(pass);
	return shaderPackage;
}

std::filesystem::path RepoRoot()
{
	std::filesystem::path path = std::filesystem::current_path();
	while (!path.empty())
	{
		if (std::filesystem::exists(path / "shaders" / "text-overlay" / "shader.json"))
			return path;
		const std::filesystem::path parent = path.parent_path();
		if (parent.empty() || parent == path)
			break;
		path = parent;
	}
	return std::filesystem::current_path();
}

bool CanRunMsdfAtlasGen(std::string& reason)
{
	const std::filesystem::path repoRoot = RepoRoot();
	std::filesystem::path executablePath;
	if (!RenderCadenceCompositor::FontAtlasBuilder::FindMsdfAtlasGenExecutable(repoRoot, executablePath))
	{
		reason = "msdf-atlas-gen executable is not available";
		return false;
	}

	ShaderPackageRegistry registry(4);
	ShaderPackage shaderPackage;
	if (!registry.ParseManifest(repoRoot / "shaders" / "text-overlay" / "shader.json", shaderPackage, reason))
		return false;

	RenderCadenceCompositor::FontAtlasBuildConfig config;
	config.repoRoot = repoRoot;
	config.cacheRoot = repoRoot / "runtime" / "test_font_cache";
	RenderCadenceCompositor::FontAtlasBuilder builder(config);
	std::vector<RenderCadenceCompositor::FontAtlasBuildOutput> outputs;
	if (!builder.BuildPackageFontAtlases(shaderPackage, outputs, reason))
		return false;

	return true;
}

void SupportsSinglePassStatelessPackage()
{
	const ShaderPackage shaderPackage = MakeSinglePassPackage();
	const RenderCadenceCompositor::ShaderSupportResult result =
		RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);

	Expect(result.supported, "single-pass stateless packages should be supported");
	Expect(result.reason.empty(), "supported packages should not report a rejection reason");
}

void SupportsStatelessNamedPassPackage()
{
	ShaderPackage shaderPackage = MakeSinglePassPackage();
	shaderPackage.passes.front().outputName = "generatedMask";
	ShaderPassDefinition secondPass;
	secondPass.id = "second";
	secondPass.entryPoint = "mainImage";
	secondPass.sourcePath = "shader.slang";
	secondPass.inputNames.push_back("generatedMask");
	secondPass.outputName = "layerOutput";
	shaderPackage.passes.push_back(secondPass);

	const RenderCadenceCompositor::ShaderSupportResult result =
		RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);

	Expect(result.supported, "stateless named-pass packages should be supported");
	Expect(result.reason.empty(), "supported named-pass packages should not report a rejection reason");
}

void RejectsUnknownPassInput()
{
	ShaderPackage shaderPackage = MakeSinglePassPackage();
	shaderPackage.passes.front().inputNames.push_back("missingIntermediate");

	const RenderCadenceCompositor::ShaderSupportResult result =
		RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);

	Expect(!result.supported, "packages with unknown pass inputs should be rejected");
	Expect(result.reason.find("unknown input") != std::string::npos, "unknown input rejection should explain the missing named output");
}

void RejectsTemporalPackage()
{
	ShaderPackage shaderPackage = MakeSinglePassPackage();
	shaderPackage.temporal.enabled = true;

	const RenderCadenceCompositor::ShaderSupportResult result =
		RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);

	Expect(!result.supported, "temporal packages should be rejected");
	Expect(result.reason.find("temporal") != std::string::npos, "temporal rejection should mention temporal storage");
}

void RejectsTextureAssets()
{
	ShaderPackage shaderPackage = MakeSinglePassPackage();
	ShaderTextureAsset asset;
	asset.id = "lut";
	shaderPackage.textureAssets.push_back(asset);

	const RenderCadenceCompositor::ShaderSupportResult result =
		RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);

	Expect(!result.supported, "texture-backed packages should be rejected for now");
	Expect(result.reason.find("texture") != std::string::npos, "texture rejection should mention texture assets");
}

void RejectsTextParametersWithoutDeclaredFont()
{
	ShaderPackage shaderPackage = MakeSinglePassPackage();
	ShaderParameterDefinition parameter;
	parameter.id = "caption";
	parameter.type = ShaderParameterType::Text;
	parameter.fontId = "missing";
	shaderPackage.parameters.push_back(parameter);

	const RenderCadenceCompositor::ShaderSupportResult result =
		RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);

	Expect(!result.supported, "text parameters without declared fonts should be rejected");
	Expect(result.reason.find("unknown font") != std::string::npos, "text rejection should mention the missing font");
}

void SupportsTextParametersWithDeclaredFont()
{
	ShaderPackage shaderPackage = MakeSinglePassPackage();
	ShaderFontAsset fontAsset;
	fontAsset.id = "roboto";
	shaderPackage.fontAssets.push_back(fontAsset);
	ShaderParameterDefinition parameter;
	parameter.id = "caption";
	parameter.type = ShaderParameterType::Text;
	parameter.fontId = "roboto";
	shaderPackage.parameters.push_back(parameter);

	const RenderCadenceCompositor::ShaderSupportResult result =
		RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);

	Expect(result.supported, "text parameters with declared fonts should be supported");
	Expect(result.reason.empty(), "supported text parameters should not report a rejection reason");
}

void BuildsDeclaredFontAtlasesDuringCatalogLoad()
{
	std::string msdfReason;
	if (!CanRunMsdfAtlasGen(msdfReason))
	{
		std::cout << "SKIP: msdf-atlas-gen could not run in this environment: " << msdfReason << "\n";
		return;
	}

	RenderCadenceCompositor::SupportedShaderCatalog catalog;
	std::string error;
	Expect(catalog.Load(RepoRoot() / "shaders", 12, error), "shader catalog loads");

	bool textOverlaySupported = false;
	for (const RenderCadenceCompositor::SupportedShaderSummary& shader : catalog.Shaders())
	{
		if (shader.id == "text-overlay")
			textOverlaySupported = true;
	}
	Expect(textOverlaySupported, "text overlay is listed as a supported shader after font atlas preparation");

	const auto& fontAtlases = catalog.FontAtlases();
	const auto textOverlayIt = fontAtlases.find("text-overlay");
	Expect(textOverlayIt != fontAtlases.end(), "text overlay font atlas is prepared during catalog load");
	if (textOverlayIt != fontAtlases.end() && !textOverlayIt->second.empty())
	{
		Expect(std::filesystem::exists(textOverlayIt->second.front().imagePath), "catalog font atlas image exists");
		Expect(std::filesystem::exists(textOverlayIt->second.front().jsonPath), "catalog font atlas json exists");
		Expect(std::filesystem::file_size(textOverlayIt->second.front().imagePath) > 0, "catalog font atlas image is not empty");
		Expect(std::filesystem::file_size(textOverlayIt->second.front().jsonPath) > 0, "catalog font atlas json is not empty");
	}
}
}

int main()
{
	SupportsSinglePassStatelessPackage();
	SupportsStatelessNamedPassPackage();
	RejectsUnknownPassInput();
	RejectsTemporalPackage();
	RejectsTextureAssets();
	RejectsTextParametersWithoutDeclaredFont();
	SupportsTextParametersWithDeclaredFont();
	BuildsDeclaredFontAtlasesDuringCatalogLoad();

	if (gFailures != 0)
	{
		std::cerr << gFailures << " RenderCadenceCompositorSupportedShaderCatalog test failure(s).\n";
		return 1;
	}

	std::cout << "RenderCadenceCompositorSupportedShaderCatalog tests passed.\n";
	return 0;
}
