#include "FontAtlasBuilder.h"

#include <algorithm>
#include <cctype>
#include <sstream>
#include <system_error>

namespace RenderCadenceCompositor
{
namespace
{
std::string QuotePath(const std::filesystem::path& path)
{
	return "\"" + path.string() + "\"";
}

std::string NumberText(double value)
{
	std::ostringstream stream;
	stream << value;
	return stream.str();
}
}

FontAtlasBuilder::FontAtlasBuilder(FontAtlasBuildConfig config) :
	mConfig(std::move(config))
{
	if (mConfig.cacheRoot.empty())
		mConfig.cacheRoot = mConfig.repoRoot / "runtime" / "font_cache";
}

bool FontAtlasBuilder::BuildPackageFontAtlases(
	const ShaderPackage& shaderPackage,
	std::vector<FontAtlasBuildOutput>& outputs,
	std::string& error) const
{
	outputs.clear();
	for (const ShaderFontAsset& fontAsset : shaderPackage.fontAssets)
	{
		FontAtlasBuildOutput output;
		if (!BuildFontAtlas(shaderPackage, fontAsset, output, error))
			return false;
		outputs.push_back(std::move(output));
	}

	error.clear();
	return true;
}

bool FontAtlasBuilder::BuildFontAtlas(
	const ShaderPackage& shaderPackage,
	const ShaderFontAsset& fontAsset,
	FontAtlasBuildOutput& output,
	std::string& error) const
{
	output = FontAtlasBuildOutput();
	if (fontAsset.id.empty())
	{
		error = "Font asset id is empty for shader package '" + shaderPackage.id + "'.";
		return false;
	}
	if (fontAsset.path.empty() || !std::filesystem::exists(fontAsset.path))
	{
		error = "Font asset '" + fontAsset.id + "' does not exist: " + fontAsset.path.string();
		return false;
	}

	std::filesystem::path executablePath;
	if (!FindMsdfAtlasGenExecutable(mConfig.repoRoot, executablePath))
	{
		error = "Could not find msdf-atlas-gen.exe under 3rdParty/msdf-atlas-gen.";
		return false;
	}

	const std::filesystem::path packageCache = PackageCacheDirectory(shaderPackage);
	std::error_code fsError;
	std::filesystem::create_directories(packageCache, fsError);
	if (fsError)
	{
		error = "Could not create font atlas cache directory: " + packageCache.string();
		return false;
	}

	const std::string outputStem = SanitizePathToken(shaderPackage.id + "-" + fontAsset.id);
	output.fontId = fontAsset.id;
	output.imagePath = packageCache / (outputStem + ".png");
	output.jsonPath = packageCache / (outputStem + ".json");

	const std::string commandLine =
		QuotePath(executablePath) +
		" -font " + QuotePath(fontAsset.path) +
		" -fontname " + fontAsset.id +
		" -type " + mConfig.atlasType +
		" -format png" +
		" -size " + NumberText(mConfig.sizePixelsPerEm) +
		" -pxrange " + NumberText(mConfig.pixelRange) +
		" -yorigin top" +
		" -imageout " + QuotePath(output.imagePath) +
		" -json " + QuotePath(output.jsonPath);

	if (!RunProcess(commandLine, mConfig.repoRoot, error))
		return false;

	if (!std::filesystem::exists(output.imagePath) || !std::filesystem::exists(output.jsonPath) ||
		std::filesystem::file_size(output.imagePath) == 0 || std::filesystem::file_size(output.jsonPath) == 0)
	{
		error = "msdf-atlas-gen did not produce expected atlas outputs for font '" + fontAsset.id + "'.";
		return false;
	}
	if (!LoadAtlasJson(output, error))
		return false;
	if (!LoadAtlasImage(output, error))
		return false;
	if (output.width == 0 || output.height == 0 || output.rgbaPixels.empty())
	{
		error = "Prepared font atlas image is empty for font '" + fontAsset.id + "'.";
		return false;
	}

	error.clear();
	return true;
}

bool FontAtlasBuilder::FindMsdfAtlasGenExecutable(const std::filesystem::path& repoRoot, std::filesystem::path& executablePath)
{
	const std::filesystem::path expectedPath = repoRoot / "3rdParty" / "msdf-atlas-gen" / "msdf-atlas-gen.exe";
	if (std::filesystem::exists(expectedPath))
	{
		executablePath = expectedPath;
		return true;
	}

	return false;
}

std::filesystem::path FontAtlasBuilder::PackageCacheDirectory(const ShaderPackage& shaderPackage) const
{
	return mConfig.cacheRoot / SanitizePathToken(shaderPackage.id);
}

std::string FontAtlasBuilder::SanitizePathToken(const std::string& value)
{
	std::string sanitized;
	sanitized.reserve(value.size());
	for (unsigned char character : value)
	{
		if (std::isalnum(character) || character == '-' || character == '_')
			sanitized.push_back(static_cast<char>(character));
		else
			sanitized.push_back('_');
	}
	return sanitized.empty() ? "font" : sanitized;
}
}
