/* SPDX-FileCopyrightText: 2026 Project Tick
 * SPDX-FileContributor: Project Tick
 * SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-MeshMC-MMCO-Module-Exception-1.0
 *
 *   MeshMC - A Custom Launcher for Minecraft
 *   Copyright (C) 2026 Project Tick
 *
 *   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, with the additional permission
 *   described in the MeshMC MMCO Module Exception 1.0.
 *
 *   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 <https://www.gnu.org/licenses/>.
 *
 *   You should have received a copy of the MeshMC MMCO Module Exception 1.0
 *   along with this program.  If not, see <https://projecttick.org/licenses/>.
 */

#include "Library.h"
#include "MinecraftInstance.h"

#include <net/Download.h>
#include <net/ChecksumValidator.h>
#include <FileSystem.h>
#include <BuildConfig.h>

void Library::getApplicableFiles(OpSys system, QStringList& jar,
								 QStringList& native, QStringList& native32,
								 QStringList& native64,
								 const QString& overridePath) const
{
	bool local = isLocal();
	auto actualPath = [&](QString relPath) {
		QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
		if (local && !overridePath.isEmpty()) {
			QString fileName = out.fileName();
			return QFileInfo(FS::PathCombine(overridePath, fileName))
				.absoluteFilePath();
		}
		return out.absoluteFilePath();
	};
	QString raw_storage = storageSuffix(system);
	if (isNative()) {
		if (raw_storage.contains("${arch}")) {
			auto nat32Storage = raw_storage;
			nat32Storage.replace("${arch}", "32");
			auto nat64Storage = raw_storage;
			nat64Storage.replace("${arch}", "64");
			native32 += actualPath(nat32Storage);
			native64 += actualPath(nat64Storage);
		} else {
			native += actualPath(raw_storage);
		}
	} else {
		jar += actualPath(raw_storage);
	}
}

QList<NetAction::Ptr> Library::getDownloads(OpSys system,
											class HttpMetaCache* cache,
											QStringList& failedLocalFiles,
											const QString& overridePath) const
{
	QList<NetAction::Ptr> out;
	bool stale = isAlwaysStale();
	bool local = isLocal();

	auto check_local_file = [&](QString storage) {
		QFileInfo fileinfo(storage);
		QString fileName = fileinfo.fileName();
		auto fullPath = FS::PathCombine(overridePath, fileName);
		QFileInfo localFileInfo(fullPath);
		if (!localFileInfo.exists()) {
			failedLocalFiles.append(localFileInfo.filePath());
			return false;
		}
		return true;
	};

	auto add_download = [&](QString storage, QString url, QString sha1) {
		if (local) {
			return check_local_file(storage);
		}
		auto entry = cache->resolveEntry("libraries", storage);
		if (stale) {
			entry->setStale(true);
		}
		if (!entry->isStale())
			return true;
		Net::Download::Options options;
		if (stale) {
			options |= Net::Download::Option::AcceptLocalFiles;
		}

		if (sha1.size()) {
			auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
			auto dl = Net::Download::makeCached(url, entry, options);
			dl->addValidator(
				new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
			qDebug() << "Checksummed Download for:" << rawName().serialize()
					 << "storage:" << storage << "url:" << url;
			out.append(dl);
		} else {
			out.append(Net::Download::makeCached(url, entry, options));
			qDebug() << "Download for:" << rawName().serialize()
					 << "storage:" << storage << "url:" << url;
		}
		return true;
	};

	QString raw_storage = storageSuffix(system);
	if (m_mojangDownloads) {
		if (isNative()) {
			if (m_nativeClassifiers.contains(system)) {
				auto nativeClassifier = m_nativeClassifiers[system];
				if (nativeClassifier.contains("${arch}")) {
					auto nat32Classifier = nativeClassifier;
					nat32Classifier.replace("${arch}", "32");
					auto nat64Classifier = nativeClassifier;
					nat64Classifier.replace("${arch}", "64");
					auto nat32info =
						m_mojangDownloads->getDownloadInfo(nat32Classifier);
					if (nat32info) {
						auto cooked_storage = raw_storage;
						cooked_storage.replace("${arch}", "32");
						add_download(cooked_storage, nat32info->url,
									 nat32info->sha1);
					}
					auto nat64info =
						m_mojangDownloads->getDownloadInfo(nat64Classifier);
					if (nat64info) {
						auto cooked_storage = raw_storage;
						cooked_storage.replace("${arch}", "64");
						add_download(cooked_storage, nat64info->url,
									 nat64info->sha1);
					}
				} else {
					auto info =
						m_mojangDownloads->getDownloadInfo(nativeClassifier);
					if (info) {
						add_download(raw_storage, info->url, info->sha1);
					}
				}
			} else {
				qDebug() << "Ignoring native library" << m_name.serialize()
						 << "because it has no classifier for current OS";
			}
		} else {
			if (m_mojangDownloads->artifact) {
				auto artifact = m_mojangDownloads->artifact;
				add_download(raw_storage, artifact->url, artifact->sha1);
			} else {
				qDebug() << "Ignoring java library" << m_name.serialize()
						 << "because it has no artifact";
			}
		}
	} else {
		auto raw_dl = [&]() {
			if (!m_absoluteURL.isEmpty()) {
				return m_absoluteURL;
			}

			if (m_repositoryURL.isEmpty()) {
				return BuildConfig.LIBRARY_BASE + raw_storage;
			}

			if (m_repositoryURL.endsWith('/')) {
				return m_repositoryURL + raw_storage;
			} else {
				return m_repositoryURL + QChar('/') + raw_storage;
			}
		}();
		if (raw_storage.contains("${arch}")) {
			QString cooked_storage = raw_storage;
			QString cooked_dl = raw_dl;
			add_download(cooked_storage.replace("${arch}", "32"),
						 cooked_dl.replace("${arch}", "32"), QString());
			cooked_storage = raw_storage;
			cooked_dl = raw_dl;
			add_download(cooked_storage.replace("${arch}", "64"),
						 cooked_dl.replace("${arch}", "64"), QString());
		} else {
			add_download(raw_storage, raw_dl, QString());
		}
	}
	return out;
}

bool Library::isActive() const
{
	bool result = true;
	if (m_rules.empty()) {
		result = true;
	} else {
		RuleAction ruleResult = Disallow;
		for (auto rule : m_rules) {
			RuleAction temp = rule->apply(this);
			if (temp != Defer)
				ruleResult = temp;
		}
		result = result && (ruleResult == Allow);
	}
	if (isNative()) {
		result = result && m_nativeClassifiers.contains(currentSystem);
	}
	return result;
}

bool Library::isLocal() const
{
	return m_hint == "local";
}

bool Library::isAlwaysStale() const
{
	return m_hint == "always-stale";
}

void Library::setStoragePrefix(QString prefix)
{
	m_storagePrefix = prefix;
}

QString Library::defaultStoragePrefix()
{
	return "libraries/";
}

QString Library::storagePrefix() const
{
	if (m_storagePrefix.isEmpty()) {
		return defaultStoragePrefix();
	}
	return m_storagePrefix;
}

QString Library::filename(OpSys system) const
{
	if (!m_filename.isEmpty()) {
		return m_filename;
	}
	// non-native? use only the gradle specifier
	if (!isNative()) {
		return m_name.getFileName();
	}

	// otherwise native, override classifiers. Mojang HACK!
	GradleSpecifier nativeSpec = m_name;
	if (m_nativeClassifiers.contains(system)) {
		nativeSpec.setClassifier(m_nativeClassifiers[system]);
	} else {
		nativeSpec.setClassifier("INVALID");
	}
	return nativeSpec.getFileName();
}

QString Library::displayName(OpSys system) const
{
	if (!m_displayname.isEmpty())
		return m_displayname;
	return filename(system);
}

QString Library::storageSuffix(OpSys system) const
{
	// non-native? use only the gradle specifier
	if (!isNative()) {
		return m_name.toPath(m_filename);
	}

	// otherwise native, override classifiers. Mojang HACK!
	GradleSpecifier nativeSpec = m_name;
	if (m_nativeClassifiers.contains(system)) {
		nativeSpec.setClassifier(m_nativeClassifiers[system]);
	} else {
		nativeSpec.setClassifier("INVALID");
	}
	return nativeSpec.toPath(m_filename);
}
