#include <cstring>

#include "Common/Data/Text/I18n.h"
#include "Common/Data/Format/IniFile.h"
#include "Common/File/VFS/VFS.h"
#include "Common/Log.h"

#include "Common/StringUtils.h"

// Don't forget to update the constants in the header file if you change this.
static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = {
	"Audio",
	"Controls",
	"CwCheats",
	"DesktopUI",
	"Developer",
	"Dialog",
	"Error",
	"Game",
	"Graphics",
	"InstallZip",
	"KeyMapping",
	"MainMenu",
	"MainSettings",
	"MappableControls",
	"Networking",
	"Pause",
	"PostShaders",
	"PSPCredits",
	"MemStick",
	"RemoteISO",
	"Reporting",
	"Savedata",
	"Screen",
	"Search",
	"Store",
	"SysInfo",
	"System",
	"TextureShaders",
	"Themes",
	"UI Elements",
	"VR",
	"Achievements",
	"PSPSettings",
};

I18NRepo g_i18nrepo;

std::string I18NRepo::LanguageID() {
	return languageID_;
}

I18NRepo::I18NRepo() {
	Clear();
}

void I18NRepo::Clear() {
	std::lock_guard<std::mutex> guard(catsLock_);
	for (auto &iter : cats_) {
		// Initialize with empty categories, so that early lookups don't crash.
		iter = std::make_shared<I18NCategory>();
	}
}

I18NCategory::I18NCategory(const Section &section) {
	std::map<std::string, std::string> sectionMap = section.ToMap();
	SetMap(sectionMap);
	name_ = section.name().c_str();
}

void I18NCategory::Clear() {
	map_.clear();
	missedKeyLog_.clear();
}

std::string_view I18NCategory::T(std::string_view key, std::string_view def) {
	auto iter = map_.find(key);
	if (iter != map_.end()) {
		return iter->second.text.c_str();
	} else {
		if (map_.empty()) {
			// Too early. This is probably in desktop-ui translation.
			return !def.empty() ? def : key;
		}
		if (key != "Font") {
			// Font is allowed to be missing.
			INFO_LOG(Log::UI, "Missing translation [%s] %.*s (%.*s)", name_.c_str(), STR_VIEW(key), STR_VIEW(def));
			std::lock_guard<std::mutex> guard(missedKeyLock_);
			std::string missedKey(key);
			if (!def.empty())
				missedKeyLog_[missedKey] = def;
			else
				missedKeyLog_[missedKey] = missedKey;
		}
		return !def.empty() ? def : key;
	}
}

const char *I18NCategory::T_cstr(const char *key, const char *def) {
	auto iter = map_.find(key);
	if (iter != map_.end()) {
		return iter->second.text.c_str();
	} else {
		if (map_.empty()) {
			// Too early. This is probably in desktop-ui translation.
			return def ? def : key;
		}
		std::string missedKey(key);
		if (missedKey != "Font") {
			INFO_LOG(Log::UI, "Missing translation %s (%s)", key, def);

			std::lock_guard<std::mutex> guard(missedKeyLock_);
			if (def)
				missedKeyLog_[missedKey] = def;
			else
				missedKeyLog_[missedKey] = std::string(key);
		}
		return def ? def : key;
	}
}

void I18NCategory::SetMap(const std::map<std::string, std::string> &m) {
	for (const auto &[key, value] : m) {
		if (map_.find(key) == map_.end()) {
			std::string text = ReplaceAll(value, "\\n", "\n");
			_dbg_assert_(key.find('\n') == std::string::npos);
			map_[key] = I18NEntry(text);
		}
	}
}

std::map<std::string, std::string, std::less<>> I18NCategory::Missed() const {
	std::lock_guard<std::mutex> guard(missedKeyLock_);
	return missedKeyLog_;
}

std::shared_ptr<I18NCategory> I18NRepo::GetCategory(I18NCat category) {
	std::lock_guard<std::mutex> guard(catsLock_);
	if (category != I18NCat::NONE)
		return cats_[(size_t)category];
	else
		return nullptr;
}

Path I18NRepo::GetIniPath(const std::string &languageID) const {
	return Path("lang") / (languageID + ".ini");
}

bool I18NRepo::IniExists(const std::string &languageID) const {
	File::FileInfo info;
	if (!g_VFS.Exists(GetIniPath(languageID).ToString().c_str()))
		return false;
	return true;
}

bool I18NRepo::LoadIni(const std::string &languageID, const Path &overridePath) {
	IniFile ini;
	Path iniPath;

//	INFO_LOG(Log::UI, "Loading lang ini %s", iniPath.c_str());
	if (!overridePath.empty()) {
		iniPath = overridePath / (languageID + ".ini");
	} else {
		iniPath = GetIniPath(languageID);
	}

	if (!ini.LoadFromVFS(g_VFS, iniPath.ToString()))
		return false;

	Clear();

	const std::vector<std::unique_ptr<Section>> &sections = ini.Sections();

	std::lock_guard<std::mutex> guard(catsLock_);
	for (auto &section : sections) {
		for (size_t i = 0; i < (size_t)I18NCat::CATEGORY_COUNT; i++) {
			if (!strcmp(section->name().c_str(), g_categoryNames[i])) {
				cats_[i].reset(new I18NCategory(*section.get()));
			}
		}
	}

	languageID_ = languageID;
	return true;
}

void I18NRepo::LogMissingKeys() const {
	std::lock_guard<std::mutex> guard(catsLock_);
	for (size_t i = 0; i < (size_t)I18NCat::CATEGORY_COUNT; i++) {
		auto &cat = cats_[i];
		for (auto &key : cat->Missed()) {
			INFO_LOG(Log::UI, "Missing translation [%s]: %s (%s)", g_categoryNames[i], key.first.c_str(), key.second.c_str());
		}
	}
}

std::shared_ptr<I18NCategory> GetI18NCategory(I18NCat category) {
	if (category == I18NCat::NONE) {
		return std::shared_ptr<I18NCategory>();
	}
	std::shared_ptr<I18NCategory> cat = g_i18nrepo.GetCategory(category);
	_dbg_assert_(cat);
	return cat;
}
