/*
	Copyright 2025 flyinghead

	This file is part of Flycast.

    Flycast is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    Flycast is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Flycast.  If not, see <https://www.gnu.org/licenses/>.
 */
#include "i18n.h"
#include "resources.h"
#include "cfg/option.h"
#include "tinygettext/tinygettext.hpp"
#include "tinygettext/file_system.hpp"
#include "tinygettext/log.hpp"
#include "log/Log.h"
#include "emulator.h"
#ifdef _WIN32
#include <windows.h>
#include <nowide/stackstring.hpp>
#endif
#include <vector>
#include <sstream>
#include <algorithm>
#include <unordered_set>
#include <mutex>

using namespace tinygettext;

namespace i18n
{

static bool inited;

class ResourceFileSystem : public FileSystem
{
	std::vector<std::string> open_directory(const std::string& pathname) override {
		return resource::listDirectory(pathname);
	}
	std::unique_ptr<std::istream> open_file(const std::string& filename) override
	{
		size_t size;
		std::unique_ptr<u8[]> data = resource::load(filename, size);
		if (data == nullptr)
			return nullptr;
		std::string str((const char *)&data[0], (const char *)&data[size]);
		return std::make_unique<std::istringstream>(str);
	}
};

static Dictionary *dictionary;

static Dictionary& getDictionary()
{
	if (dictionary == nullptr)
		reloadLanguage();
	return *dictionary;
}

static std::string getUnixLocaleVariant(const std::string& locale)
{
	size_t at = locale.find('@');
	if (at == locale.npos)
		return {};
	size_t end = locale.find_first_of("_-.@", ++at);
	if (end == locale.npos)
		return locale.substr(at);
	else
		return locale.substr(at, end - at);
}

void parseLocale(const std::string& locale, std::string& language, std::string& country, std::string& variant)
{
	size_t sep = locale.find_first_of("_-.@");
	language = locale.substr(0, sep);
	country.clear();
	variant.clear();
	if (sep == locale.npos)
		return;
	if (locale[sep] == '.') {
		variant = getUnixLocaleVariant(locale);
		return;
	}
	size_t sep2 = locale.find_first_of("_-.@", ++sep);
	if (sep2 == locale.npos) {
		country = locale.substr(sep);
		return;
	}
	country = locale.substr(sep, sep2 - sep);
	if (locale[sep2] == '.') {
		variant = getUnixLocaleVariant(locale);
		return;
	}
	bool lastIsVariant = locale[sep2] == '@';
	sep = ++sep2;
	sep2 = locale.find_first_of("_-.@", sep);
	if (sep2 == locale.npos)
		variant = locale.substr(sep);
	else
		variant = locale.substr(sep, sep2 - sep);
	if (!lastIsVariant)
		std::swap(country, variant);
}

void init()
{
	if (inited)
		return;
	inited = true;
#ifndef LIBRETRO
	try {
		std::locale::global(std::locale(""));
	} catch (const std::runtime_error& e) {
		INFO_LOG(BOOT, "Error setting c++ locale: %s", e.what());
		setlocale(LC_ALL, "");
	}
#endif
	Log::set_log_info_callback([](const std::string& msg)
	{
#if !defined(NDEBUG) || defined(DEBUGFAST)
		static std::mutex mutex;
		static std::unordered_set<std::string> msgFilter;

		if (msg.empty())
			return;
		{
			std::lock_guard<std::mutex> _(mutex);
			if (msgFilter.count(msg) != 0)
				return;
			msgFilter.insert(msg);
		}
		INFO_LOG(COMMON, "%s", msg.substr(0, msg.length() - 1).c_str());
#endif
	});
	Log::set_log_warning_callback([](const std::string& msg) {
		if (!msg.empty())
			WARN_LOG(COMMON, "%s", msg.substr(0, msg.length() - 1).c_str());
	});
	Log::set_log_error_callback([](const std::string& msg) {
		if (!msg.empty())
			ERROR_LOG(COMMON, "%s", msg.substr(0, msg.length() - 1).c_str());
	});
}

void reloadLanguage()
{
	init();
	std::string locale = getCurrentLocale();
	std::string language, country, variant;
	parseLocale(locale, language, country, variant);

	static std::unique_ptr<DictionaryManager> dictMgr;
	if (!dictMgr)
	{
		dictMgr = std::make_unique<DictionaryManager>(std::make_unique<ResourceFileSystem>());
		dictMgr->add_directory("i18n");
	}
	dictMgr->set_language(Language::from_spec(language, country, variant));
	dictionary = &dictMgr->get_dictionary();
	EventManager::event(Event::LocaleChange);
}

static const std::string& translate(const std::string& msg) {
	return getDictionary().translate(msg);
}

std::string Ts(const std::string& msg) {
	return translate(msg);
}

const char *T(const char *msg)
{
	if (msg == nullptr)
		return nullptr;
	std::string smsg(msg);
	const std::string& tr = translate(smsg);
	if (&tr == &smsg)
		return msg;
	else
		return tr.c_str();
}

std::string translateCtx_s(const std::string& context, const std::string& msg) {
	return getDictionary().translate_ctxt(context, msg);
}

const char *translateCtx(const std::string& context, const char *msg)
{
	if (msg == nullptr)
		return nullptr;
	std::string smsg(msg);
	const std::string& tr = getDictionary().translate_ctxt(context, smsg);
	if (&tr == &smsg)
		return msg;
	else
		return tr.c_str();
}

std::string translatePlural_s(const std::string& msg, const std::string& msgPlural, int num) {
	return getDictionary().translate_plural(msg, msgPlural, num);
}

const char *translatePlural(const char *msg, const char *msgPlural, int num) {
	if (msg == nullptr)
		return nullptr;
	std::string smsg(msg);
	std::string smsgPlural(msgPlural);
	const std::string& tr = getDictionary().translate_plural(smsg, smsgPlural, num);
	if (&tr == &smsg)
		return msg;
	else if (&tr == &smsgPlural)
		return msgPlural;
	else
		return tr.c_str();
}

#if !defined(__ANDROID__) && !defined(__SWITCH__) && !defined(LIBRETRO) && !defined(TARGET_MAC)
std::string getSystemLocale()
{
#if defined(_WIN32)
	ULONG numLanguages = 0;
	ULONG bufferSize = 0;
	if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages, nullptr, &bufferSize) || bufferSize == 0)
	{
		ERROR_LOG(COMMON, "GetUserPreferredUILanguages(size) failed: %lx", GetLastError());
		return "en";
	}

	std::vector<wchar_t> buffer(bufferSize);
	if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages, buffer.data(), &bufferSize))
	{
		ERROR_LOG(COMMON, "GetUserPreferredUILanguages(data) failed: %lx", GetLastError());
		return "en";
	}

	nowide::stackstring name;
	if (name.convert(buffer.data()))
		return name.get();

	ERROR_LOG(COMMON, "UTF-16 to UTF-8 conversion failed");
	return "en";
#else
	const char* locale = setlocale(LC_MESSAGES, nullptr);
	return locale ? locale : "en";
#endif
}
#endif

#ifndef LIBRETRO
std::string getCurrentLocale()
{
	std::string locale = config::UILanguage;
	if (!locale.empty())
		return locale;
	return getSystemLocale();
}
#endif

}
