#include "stdafx.h"
#include "ShaderPackageRegistry.h"

#include "RuntimeJson.h"
#include "ShaderManifestParser.h"

#include <algorithm>
#include <fstream>
#include <sstream>

namespace
{
std::string ReadTextFile(const std::filesystem::path& path, std::string& error)
{
	std::ifstream input(path, std::ios::binary);
	if (!input)
	{
		error = "Could not open file: " + path.string();
		return std::string();
	}

	std::ostringstream buffer;
	buffer << input.rdbuf();
	return buffer.str();
}

std::string UniqueUnavailableShaderId(const std::filesystem::path& manifestPath, const std::string& parsedId)
{
	const std::string fallbackId = manifestPath.parent_path().filename().string();
	const std::string baseId = parsedId.empty() ? fallbackId : parsedId;
	return baseId + "@invalid:" + fallbackId;
}

ShaderPackageStatus BuildUnavailableStatus(const std::filesystem::path& manifestPath, const ShaderPackage& partialPackage, const std::string& packageError)
{
	ShaderPackageStatus status;
	status.id = UniqueUnavailableShaderId(manifestPath, partialPackage.id);
	status.displayName = !partialPackage.displayName.empty() ? partialPackage.displayName : manifestPath.parent_path().filename().string();
	status.description = partialPackage.description;
	status.category = !partialPackage.category.empty() ? partialPackage.category : "Unavailable";
	status.available = false;
	status.error = packageError;
	return status;
}

ShaderPackageStatus BuildAvailableStatus(const ShaderPackage& shaderPackage)
{
	ShaderPackageStatus status;
	status.id = shaderPackage.id;
	status.displayName = shaderPackage.displayName;
	status.description = shaderPackage.description;
	status.category = shaderPackage.category;
	status.available = true;
	return status;
}
}

ShaderPackageRegistry::ShaderPackageRegistry(unsigned maxTemporalHistoryFrames)
	: mMaxTemporalHistoryFrames(maxTemporalHistoryFrames)
{
}

bool ShaderPackageRegistry::Scan(
	const std::filesystem::path& shaderRoot,
	std::map<std::string, ShaderPackage>& packagesById,
	std::vector<std::string>& packageOrder,
	std::vector<ShaderPackageStatus>& packageStatuses,
	std::string& error) const
{
	packagesById.clear();
	packageOrder.clear();
	packageStatuses.clear();

	if (!std::filesystem::exists(shaderRoot))
	{
		error = "Shader library directory does not exist: " + shaderRoot.string();
		return false;
	}

	for (const auto& entry : std::filesystem::directory_iterator(shaderRoot))
	{
		if (!entry.is_directory())
			continue;

		std::filesystem::path manifestPath = entry.path() / "shader.json";
		if (!std::filesystem::exists(manifestPath))
			continue;

		ShaderPackage shaderPackage;
		if (!ParseManifest(manifestPath, shaderPackage, error))
		{
			packageStatuses.push_back(BuildUnavailableStatus(manifestPath, shaderPackage, error));
			error.clear();
			continue;
		}

		if (packagesById.find(shaderPackage.id) != packagesById.end())
		{
			packageStatuses.push_back(BuildUnavailableStatus(manifestPath, shaderPackage, "Duplicate shader id found: " + shaderPackage.id));
			continue;
		}

		packageOrder.push_back(shaderPackage.id);
		packageStatuses.push_back(BuildAvailableStatus(shaderPackage));
		packagesById[shaderPackage.id] = shaderPackage;
	}

	std::sort(packageOrder.begin(), packageOrder.end());
	std::sort(packageStatuses.begin(), packageStatuses.end(), [](const ShaderPackageStatus& left, const ShaderPackageStatus& right) {
		return left.displayName < right.displayName;
	});
	return true;
}

bool ShaderPackageRegistry::ParseManifest(const std::filesystem::path& manifestPath, ShaderPackage& shaderPackage, std::string& error) const
{
	const std::string manifestText = ReadTextFile(manifestPath, error);
	if (manifestText.empty())
		return false;

	JsonValue manifestJson;
	if (!ParseJson(manifestText, manifestJson, error))
		return false;
	if (!manifestJson.isObject())
	{
		error = "Shader manifest root must be an object: " + manifestPath.string();
		return false;
	}

	using namespace ShaderManifestParsing;
	if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error))
		return false;

	if (!ParsePassDefinitions(manifestJson, shaderPackage, manifestPath, error))
		return false;

	shaderPackage.shaderWriteTime = shaderPackage.passes.front().sourceWriteTime;
	for (const ShaderPassDefinition& pass : shaderPackage.passes)
	{
		if (pass.sourceWriteTime > shaderPackage.shaderWriteTime)
			shaderPackage.shaderWriteTime = pass.sourceWriteTime;
	}
	shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);

	return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) &&
		ParseFontAssets(manifestJson, shaderPackage, manifestPath, error) &&
		ParseTemporalSettings(manifestJson, shaderPackage, mMaxTemporalHistoryFrames, manifestPath, error) &&
		ParseFeedbackSettings(manifestJson, shaderPackage, manifestPath, error) &&
		ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error);
}
