#include "MetaInfo.h"
#include "Cafe/Filesystem/fsc.h"

#include "pugixml.hpp"
#include "Cafe/Filesystem/FST/FST.h"

MetaInfo::MetaInfo()
{
	m_type = GameType::FSC;
	
	uint32 meta_size;
	const auto meta_data = ReadFSCFile("vol/meta/meta.xml", meta_size);
	if (meta_size == 0 || !meta_data)
		throw std::runtime_error("meta.xml missing");

	pugi::xml_document meta_doc;
	ParseMetaFile(meta_doc, meta_doc.load_buffer_inplace(meta_data.get(), meta_size));
}

MetaInfo::MetaInfo(const fs::path& filename)
{
	if (!fs::exists(filename))
		throw std::invalid_argument("filename doesn't exist");

	if (fs::is_directory(filename))
	{
		MetaInfo::ParseDirectory(filename);
		m_type = GameType::Directory;
		m_type_path = filename;
	}
	else
		MetaInfo::ParseFile(filename);
}

std::string MetaInfo::GetName(CafeConsoleLanguage language) const
{
	std::string long_name{ GetLongName(language) };
	const auto nl = long_name.find(L'\n');
	if (nl != std::string::npos)
		long_name.replace(nl, 1, " - ");

	return long_name;
}

const std::string& MetaInfo::GetLongName(CafeConsoleLanguage language) const
{
	return m_long_name[(int)language].empty() ? m_long_name[(int)CafeConsoleLanguage::EN] : m_long_name[(int)language];
}

const std::string& MetaInfo::GetShortName(CafeConsoleLanguage language) const
{
	return m_short_name[(int)language].empty() ? m_short_name[(int)CafeConsoleLanguage::EN] : m_short_name[(int)language];
}

const std::string& MetaInfo::GetPublisher(CafeConsoleLanguage language) const
{
	return m_publisher[(int)language].empty() ? m_publisher[(int)CafeConsoleLanguage::EN] : m_publisher[(int)language];
}

void MetaInfo::ParseDirectory(const fs::path& filename)
{
	const auto meta_dir = fs::path(filename).append(L"meta");
	if (!fs::exists(meta_dir) || !fs::is_directory(meta_dir))
		throw std::invalid_argument("meta directory missing");

	const auto meta_file = meta_dir / L"meta.xml";
	if (!fs::exists(meta_file) || !fs::is_regular_file(meta_file))
		throw std::invalid_argument("meta.xml missing");

	ParseMetaFile(meta_file.wstring());
}

bool MetaInfo::ParseFile(const fs::path& filename)
{
	const auto extension = filename.extension();
	if (filename.filename() != "meta.xml")
		return false;
	
	const auto base_dir = filename.parent_path().parent_path();

	ParseMetaFile(filename);
	m_type = GameType::Directory;
	m_type_path = base_dir;
	return true;
}

void MetaInfo::ParseMetaFile(const fs::path& meta_file)
{
	pugi::xml_document doc;
	const auto result = doc.load_file(meta_file.wstring().c_str());
	ParseMetaFile(doc, result);
}

void MetaInfo::ParseMetaFile(const pugi::xml_document& doc, const pugi::xml_parse_result& result)
{
	if (!result)
		throw std::invalid_argument(fmt::format("error when parsing the meta.xml: {}", result.description()));

	const auto root = doc.child("menu");
	if (!root)
		throw std::invalid_argument("meta.xml invalid");

	for (const auto& child : root.children())
	{
		std::string_view name = child.name();
		if (name == "title_version")
			m_version = child.text().as_uint();
		else if (name == "product_code")
			m_product_code = child.text().as_string();
		else if (name == "company_code")
			m_company_code = child.text().as_string();
		else if (name == "content_platform")
			m_content_platform = child.text().as_string();
		else if (name == "title_id")
			m_title_id = std::stoull(child.text().as_string(), nullptr, 16);
		else if (name == "region")
			m_region = (CafeConsoleRegion)child.text().as_uint();
		else if (boost::starts_with(name, "longname_"))
		{
			const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1));
			if (index != -1)
				m_long_name[index] = child.text().as_string();
		}
		else if (boost::starts_with(name, L"shortname_"))
		{
			const sint32 index = GetLanguageIndex(name.substr(std::size("shortname_") - 1));
			if (index != -1)
				m_short_name[index] = child.text().as_string();
		}
		else if (boost::starts_with(name, L"publisher_"))
		{
			const sint32 index = GetLanguageIndex(name.substr(std::size("publisher_") - 1));
			if (index != -1)
				m_publisher[index] = child.text().as_string();
		}
	}
}

std::unique_ptr<uint8[]> MetaInfo::GetIcon(uint32& size) const
{
	size = 0;
	switch (m_type)
	{
	case GameType::FSC:
		return ReadFSCFile("vol/meta/iconTex.tga", size);
	case GameType::Directory:
	{
		cemu_assert_debug(!m_type_path.empty());
		const auto icon = fs::path(m_type_path).append(L"meta").append(L"iconTex.tga");
		std::ifstream file(icon, std::ios::binary | std::ios::ate);
		if (file.is_open())
		{
			size = file.tellg();
			if (size > 0)
			{
				file.seekg(0, std::ios::beg);
				auto result = std::make_unique<uint8[]>(size);
				file.read((char*)result.get(), size);
				return result;
			}
		}
		return nullptr;
	}
	case GameType::Image:
	{
		cemu_assert_debug(!m_type_path.empty());
		FSTVolume* volume = FSTVolume::OpenFromDiscImage(m_type_path);
		if (volume)
		{
			auto result = ReadVirtualFile(volume, "meta/iconTex.tga", size);
			delete volume;
			return result;
		}
		return nullptr;
	}
	default:
		__assume(false);
	}
	return nullptr;
}
