/* LanguagePreferences.cpp */
/*
 * Copyright (C) 2011-2024 Michael Lugmair
 *
 * This file is part of sayonara player
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.

 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "LanguagePreferences.h"
#include "Utils/Logger/Logger.h"

#include "Utils/Algorithm.h"
#include "Utils/FileUtils.h"
#include "Utils/Language/Language.h"
#include "Utils/Language/LanguageUtils.h"
#include "Utils/Settings/Settings.h"
#include "Utils/Utils.h"
#include "Utils/WebAccess/WebClient.h"
#include "Utils/WebAccess/WebClientFactory.h"

#include <QRegExp>
#include <algorithm>

namespace
{
	constexpr const auto ClassName = "LanguagePreferences";

	bool checkLineForUpdate(const QString& currentLanguageCode, const QString& currentChecksum, const QString& line)
	{
		const auto splitted = line.split(QRegExp("\\s+"));
		if(splitted.size() != 2)
		{
			return false;
		}

		const auto languageCode = Util::Language::extractLanguageCode(splitted[1]);
		if(languageCode == currentLanguageCode)
		{
			const auto& checksum = splitted[0];
			return (currentChecksum != checksum);
		}

		return false;
	}

	bool checkDataForUpdate(const QString& currentLanguageCode, const QString& data)
	{
		const auto currentChecksum = Util::Language::getChecksum(currentLanguageCode);
		const auto isUpdateAvailable = Util::Algorithm::contains(data.split("\n"), [&](const auto& row) {
			return checkLineForUpdate(currentLanguageCode, currentChecksum, row);
		});

		if(!isUpdateAvailable)
		{
			spLog(Log::Info, ClassName) << "No need to update language " << currentLanguageCode;
			return false;
		}

		spLog(Log::Info, ClassName) << "Language update available for " << currentLanguageCode;

		return true;
	}

	bool isCurrentLanguage(const QString& languageCode)
	{
		return languageCode.toLower() == GetSetting(Set::Player_Language).toLower();
	}

	bool replaceLanguageFile(const QString& languageCode, const QByteArray& data)
	{
		const auto filepath = Util::Language::getHomeTargetPath(languageCode);
		const auto written = Util::File::writeFile(data, filepath);
		if(written)
		{
			spLog(Log::Info, ClassName) << "Language file written to " << filepath;

			Util::Language::updateLanguageVersion(languageCode);

			if(isCurrentLanguage(languageCode))
			{
				spLog(Log::Debug, ClassName) << "Notify player about language update for " << languageCode;
				Settings::instance()->shout<Set::Player_Language>();
			}
		}

		else
		{
			spLog(Log::Warning, ClassName) << "Could not write language file to " << filepath;
		}

		return written;
	}

	LanguagePreferences::LanguageData createSystemLanguageData()
	{
		const auto languageCode = Util::Language::determineSystemLanguage();
		const auto systemLocale = QLocale(languageCode);
		const auto systemLanguageName = systemLocale.nativeLanguageName().toCaseFolded();
		const auto iconPath = Util::Language::getIconPath(languageCode);

		return {
			Util::Language::systemLanguageCode(),
			Lang::get(Lang::Automatic) + " " + QString("(%1)").arg(systemLanguageName),
			iconPath
		};
	}
} // namespace

struct LanguagePreferences::Private
{
	WebClientFactory* webClientFactory;

	explicit Private(WebClientFactory* webClientFactory) :
		webClientFactory {webClientFactory} {}
};

LanguagePreferences::LanguagePreferences(WebClientFactory* webClientFactory, QObject* parent) :
	QObject {parent},
	m {Pimpl::make<Private>(webClientFactory)} {}

LanguagePreferences::~LanguagePreferences() = default;

auto LanguagePreferences::getAllLanguages() -> std::pair<QList<LanguagePreferences::LanguageData>, int>
{
	auto languageData = QList<LanguagePreferences::LanguageData> {createSystemLanguageData()};

	const auto playerLanguage = GetSetting(Set::Player_Language);
	const auto locales = Util::Language::availableLanguages();

	auto currentIndex = 0;
	auto i = 1;
	for(auto it = locales.begin(); it != locales.end(); it++, i++)
	{
		const auto& languageCode = it.key();
		const auto& locale = it.value();
		const auto iconPath = Util::Language::getIconPath(languageCode);

		auto languageName = Util::stringToVeryFirstUpper(locale.nativeLanguageName().toCaseFolded());
		languageData << LanguageData {
			languageCode,
			languageName,
			iconPath
		};

		if(languageCode.toLower() == playerLanguage.toLower())
		{
			currentIndex = i;
		}
	}

	return std::make_pair(languageData, currentIndex);
}

void LanguagePreferences::checkForUpdate(const QString& languageCode)
{
	if(Util::Language::isSystemLanguage(languageCode))
	{
		return;
	}

	auto* webClient = m->webClientFactory->createClient(this);
	const auto url = Util::Language::getChecksumHttpPath();

	connect(webClient, &WebClient::sigFinished, this, [this, webClient, languageCode]() {
		updateCheckFinished(webClient, languageCode);
	});
	webClient->run(url);
}

void LanguagePreferences::updateCheckFinished(WebClient* webClient, const QString& languageCode)
{
	const auto data = QString::fromUtf8(webClient->data());
	const auto hasError = webClient->hasError();

	webClient->deleteLater();

	if(hasError || data.isEmpty())
	{
		emit sigWarning(tr("Cannot check for language update"));
		spLog(Log::Warning, this) << "Cannot download checksums " << webClient->url();
		return;
	}

	const auto canUpdate = checkDataForUpdate(languageCode, data);
	if(canUpdate)
	{
		downloadUpdate(languageCode);
	}

	else
	{
		emit sigInfo(tr("Language is up to date"));
	}
}

void LanguagePreferences::downloadUpdate(const QString& languageCode)
{
	if(const auto url = Util::Language::getHttpPath(languageCode); !url.isEmpty())
	{
		auto* webClient = m->webClientFactory->createClient(this);
		connect(webClient, &WebClient::sigFinished, this, [this, webClient, languageCode]() {
			downloadFinished(webClient, languageCode);
		});

		webClient->run(url);
	}
}

void LanguagePreferences::downloadFinished(WebClient* webClient, const QString& languageCode)
{
	const auto data = webClient->data();
	const auto hasError = webClient->hasError();

	webClient->deleteLater();

	if(hasError || data.isEmpty())
	{
		spLog(Log::Warning, this) << "Cannot download file from " << webClient->url();
		emit sigWarning(tr("Cannot fetch language update"));
		return;
	}

	const auto success = replaceLanguageFile(languageCode, data);
	if(success)
	{
		emit sigInfo(tr("Language was updated successfully") + ".");
	}
	else
	{
		emit sigWarning(QObject::tr("Cannot fetch language update"));
	}
}

QString LanguagePreferences::importLanguage(const QString& filename)
{
	if(filename.isEmpty())
	{
		return {};
	}

	const auto success = Util::Language::importLanguageFile(filename);
	if(!success)
	{
		emit sigWarning(tr("The language file could not be imported"));
		return {};
	}

	auto newLanguageCode = Util::Language::extractLanguageCode(filename);
	emit sigInfo(tr("The language file was imported successfully"));

	return newLanguageCode;
}
