/* 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 "JavaSettingsWidget.h"

#include <QVBoxLayout>
#include <QGroupBox>
#include <QSpinBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QToolButton>
#include <QFileDialog>

#include <sys.h>

#include "java/JavaInstall.h"
#include "java/JavaUtils.h"
#include "FileSystem.h"

#include "ui/dialogs/CustomMessageBox.h"
#include "ui/widgets/VersionSelectWidget.h"

#include "Application.h"
#include "BuildConfig.h"

JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent)
{
	m_availableMemory = Sys::getSystemRam() / Sys::mebibyte;

	goodIcon = APPLICATION->getThemedIcon("status-good");
	yellowIcon = APPLICATION->getThemedIcon("status-yellow");
	badIcon = APPLICATION->getThemedIcon("status-bad");
	setupUi();

	connect(m_minMemSpinBox, &QSpinBox::valueChanged, this,
			&JavaSettingsWidget::memoryValueChanged);
	connect(m_maxMemSpinBox, &QSpinBox::valueChanged, this,
			&JavaSettingsWidget::memoryValueChanged);
	connect(m_permGenSpinBox, &QSpinBox::valueChanged, this,
			&JavaSettingsWidget::memoryValueChanged);
	connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this,
			&JavaSettingsWidget::javaVersionSelected);
	connect(m_javaBrowseBtn, &QPushButton::clicked, this,
			&JavaSettingsWidget::on_javaBrowseBtn_clicked);
	connect(m_javaPathTextBox, &QLineEdit::textEdited, this,
			&JavaSettingsWidget::javaPathEdited);
	connect(m_javaStatusBtn, &QToolButton::clicked, this,
			&JavaSettingsWidget::on_javaStatusBtn_clicked);
}

void JavaSettingsWidget::setupUi()
{
	setObjectName(QStringLiteral("javaSettingsWidget"));
	m_verticalLayout = new QVBoxLayout(this);
	m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));

	m_versionWidget = new VersionSelectWidget(this);
	m_verticalLayout->addWidget(m_versionWidget);

	m_horizontalLayout = new QHBoxLayout();
	m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
	m_javaPathTextBox = new QLineEdit(this);
	m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox"));

	m_horizontalLayout->addWidget(m_javaPathTextBox);

	m_javaBrowseBtn = new QPushButton(this);
	m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn"));

	m_horizontalLayout->addWidget(m_javaBrowseBtn);

	m_javaStatusBtn = new QToolButton(this);
	m_javaStatusBtn->setIcon(yellowIcon);
	m_horizontalLayout->addWidget(m_javaStatusBtn);

	m_verticalLayout->addLayout(m_horizontalLayout);

	m_memoryGroupBox = new QGroupBox(this);
	m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox"));
	m_gridLayout_2 = new QGridLayout(m_memoryGroupBox);
	m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2"));

	m_labelMinMem = new QLabel(m_memoryGroupBox);
	m_labelMinMem->setObjectName(QStringLiteral("labelMinMem"));
	m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1);

	m_minMemSpinBox = new QSpinBox(m_memoryGroupBox);
	m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox"));
	m_minMemSpinBox->setSuffix(QStringLiteral(" MiB"));
	m_minMemSpinBox->setMinimum(128);
	m_minMemSpinBox->setMaximum(m_availableMemory);
	m_minMemSpinBox->setSingleStep(128);
	m_labelMinMem->setBuddy(m_minMemSpinBox);
	m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1);

	m_labelMaxMem = new QLabel(m_memoryGroupBox);
	m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem"));
	m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1);

	m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox);
	m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox"));
	m_maxMemSpinBox->setSuffix(QStringLiteral(" MiB"));
	m_maxMemSpinBox->setMinimum(128);
	m_maxMemSpinBox->setMaximum(m_availableMemory);
	m_maxMemSpinBox->setSingleStep(128);
	m_labelMaxMem->setBuddy(m_maxMemSpinBox);
	m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1);

	m_labelPermGen = new QLabel(m_memoryGroupBox);
	m_labelPermGen->setObjectName(QStringLiteral("labelPermGen"));
	m_labelPermGen->setText(QStringLiteral("PermGen:"));
	m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1);
	m_labelPermGen->setVisible(false);

	m_permGenSpinBox = new QSpinBox(m_memoryGroupBox);
	m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox"));
	m_permGenSpinBox->setSuffix(QStringLiteral(" MiB"));
	m_permGenSpinBox->setMinimum(64);
	m_permGenSpinBox->setMaximum(m_availableMemory);
	m_permGenSpinBox->setSingleStep(8);
	m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1);
	m_permGenSpinBox->setVisible(false);

	m_verticalLayout->addWidget(m_memoryGroupBox);

	retranslate();
}

void JavaSettingsWidget::initialize()
{
	m_versionWidget->initialize(APPLICATION->javalist().get());
	m_versionWidget->setResizeOn(2);
	auto s = APPLICATION->settings();
	// Memory
	observedMinMemory = s->get("MinMemAlloc").toInt();
	observedMaxMemory = s->get("MaxMemAlloc").toInt();
	observedPermGenMemory = s->get("PermGen").toInt();
	m_minMemSpinBox->setValue(observedMinMemory);
	m_maxMemSpinBox->setValue(observedMaxMemory);
	m_permGenSpinBox->setValue(observedPermGenMemory);
}

void JavaSettingsWidget::refresh()
{
	m_versionWidget->loadList();
}

JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate()
{
	switch (javaStatus) {
		default:
		case JavaStatus::NotSet:
		case JavaStatus::DoesNotExist:
		case JavaStatus::DoesNotStart:
		case JavaStatus::ReturnedInvalidData: {
			int button =
				CustomMessageBox::selectable(
					this, tr("No Java version selected"),
					tr("You didn't select a Java version or selected something "
					   "that doesn't work.\n"
					   "%1 will not be able to start Minecraft.\n"
					   "Do you wish to proceed without any Java?"
					   "\n\n"
					   "You can change the Java version in the settings "
					   "later.\n")
						.arg(BuildConfig.MESHMC_NAME),
					QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No,
					QMessageBox::NoButton)
					->exec();
			if (button == QMessageBox::No) {
				return ValidationStatus::Bad;
			}
			return ValidationStatus::JavaBad;
		} break;
		case JavaStatus::Pending: {
			return ValidationStatus::Bad;
		}
		case JavaStatus::Good: {
			return ValidationStatus::AllOK;
		}
	}
}

QString JavaSettingsWidget::javaPath() const
{
	return m_javaPathTextBox->text();
}

int JavaSettingsWidget::maxHeapSize() const
{
	return m_maxMemSpinBox->value();
}

int JavaSettingsWidget::minHeapSize() const
{
	return m_minMemSpinBox->value();
}

bool JavaSettingsWidget::permGenEnabled() const
{
	return m_permGenSpinBox->isVisible();
}

int JavaSettingsWidget::permGenSize() const
{
	return m_permGenSpinBox->value();
}

void JavaSettingsWidget::memoryValueChanged(int)
{
	bool actuallyChanged = false;
	int min = m_minMemSpinBox->value();
	int max = m_maxMemSpinBox->value();
	int permgen = m_permGenSpinBox->value();
	QObject* obj = sender();
	if (obj == m_minMemSpinBox && min != observedMinMemory) {
		observedMinMemory = min;
		actuallyChanged = true;
		if (min > max) {
			observedMaxMemory = min;
			m_maxMemSpinBox->setValue(min);
		}
	} else if (obj == m_maxMemSpinBox && max != observedMaxMemory) {
		observedMaxMemory = max;
		actuallyChanged = true;
		if (min > max) {
			observedMinMemory = max;
			m_minMemSpinBox->setValue(max);
		}
	} else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) {
		observedPermGenMemory = permgen;
		actuallyChanged = true;
	}
	if (actuallyChanged) {
		checkJavaPathOnEdit(m_javaPathTextBox->text());
	}
}

void JavaSettingsWidget::javaVersionSelected(BaseVersionPtr version)
{
	auto java = std::dynamic_pointer_cast<JavaInstall>(version);
	if (!java) {
		return;
	}
	auto visible = java->id.requiresPermGen();
	m_labelPermGen->setVisible(visible);
	m_permGenSpinBox->setVisible(visible);
	m_javaPathTextBox->setText(java->path);
	checkJavaPath(java->path);
}

void JavaSettingsWidget::on_javaBrowseBtn_clicked()
{
	QString filter;
#if defined Q_OS_WIN32
	filter = "Java (javaw.exe)";
#else
	filter = "Java (java)";
#endif
	QString raw_path = QFileDialog::getOpenFileName(
		this, tr("Find Java executable"), QString(), filter);
	if (raw_path.isEmpty()) {
		return;
	}
	QString cooked_path = FS::NormalizePath(raw_path);
	m_javaPathTextBox->setText(cooked_path);
	checkJavaPath(cooked_path);
}

void JavaSettingsWidget::on_javaStatusBtn_clicked()
{
	QString text;
	bool failed = false;
	switch (javaStatus) {
		case JavaStatus::NotSet:
			checkJavaPath(m_javaPathTextBox->text());
			return;
		case JavaStatus::DoesNotExist:
			text += QObject::tr("The specified file either doesn't exist or is "
								"not a proper executable.");
			failed = true;
			break;
		case JavaStatus::DoesNotStart: {
			text += QObject::tr(
				"The specified java binary didn't start properly.<br />");
			auto htmlError = m_result.errorLog;
			if (!htmlError.isEmpty()) {
				htmlError.replace('\n', "<br />");
				text += QString("<font color=\"red\">%1</font>").arg(htmlError);
			}
			failed = true;
			break;
		}
		case JavaStatus::ReturnedInvalidData: {
			text += QObject::tr(
				"The specified java binary returned unexpected results:<br />");
			auto htmlOut = m_result.outLog;
			if (!htmlOut.isEmpty()) {
				htmlOut.replace('\n', "<br />");
				text += QString("<font color=\"red\">%1</font>").arg(htmlOut);
			}
			failed = true;
			break;
		}
		case JavaStatus::Good:
			text += QObject::tr("Java test succeeded!<br />Platform reported: "
								"%1<br />Java version "
								"reported: %2<br />")
						.arg(m_result.realPlatform,
							 m_result.javaVersion.toString());
			break;
		case JavaStatus::Pending:
			// TODO: abort here?
			return;
	}
	CustomMessageBox::selectable(
		this,
		failed ? QObject::tr("Java test failure")
			   : QObject::tr("Java test success"),
		text, failed ? QMessageBox::Critical : QMessageBox::Information)
		->show();
}

void JavaSettingsWidget::setJavaStatus(JavaSettingsWidget::JavaStatus status)
{
	javaStatus = status;
	switch (javaStatus) {
		case JavaStatus::Good:
			m_javaStatusBtn->setIcon(goodIcon);
			break;
		case JavaStatus::NotSet:
		case JavaStatus::Pending:
			m_javaStatusBtn->setIcon(yellowIcon);
			break;
		default:
			m_javaStatusBtn->setIcon(badIcon);
			break;
	}
}

void JavaSettingsWidget::javaPathEdited(const QString& path)
{
	checkJavaPathOnEdit(path);
}

void JavaSettingsWidget::checkJavaPathOnEdit(const QString& path)
{
	auto realPath = FS::ResolveExecutable(path);
	QFileInfo pathInfo(realPath);
	if (pathInfo.baseName().toLower().contains("java")) {
		checkJavaPath(path);
	} else {
		if (!m_checker) {
			setJavaStatus(JavaStatus::NotSet);
		}
	}
}

void JavaSettingsWidget::checkJavaPath(const QString& path)
{
	if (m_checker) {
		queuedCheck = path;
		return;
	}
	auto realPath = FS::ResolveExecutable(path);
	if (realPath.isNull()) {
		setJavaStatus(JavaStatus::DoesNotExist);
		return;
	}
	setJavaStatus(JavaStatus::Pending);
	m_checker.reset(new JavaChecker());
	m_checker->m_path = path;
	m_checker->m_minMem = m_minMemSpinBox->value();
	m_checker->m_maxMem = m_maxMemSpinBox->value();
	if (m_permGenSpinBox->isVisible()) {
		m_checker->m_permGen = m_permGenSpinBox->value();
	}
	connect(m_checker.get(), &JavaChecker::checkFinished, this,
			&JavaSettingsWidget::checkFinished);
	m_checker->performCheck();
}

void JavaSettingsWidget::checkFinished(JavaCheckResult result)
{
	m_result = result;
	switch (result.validity) {
		case JavaCheckResult::Validity::Valid: {
			setJavaStatus(JavaStatus::Good);
			break;
		}
		case JavaCheckResult::Validity::ReturnedInvalidData: {
			setJavaStatus(JavaStatus::ReturnedInvalidData);
			break;
		}
		case JavaCheckResult::Validity::Errored: {
			setJavaStatus(JavaStatus::DoesNotStart);
			break;
		}
	}
	m_checker.reset();
	if (!queuedCheck.isNull()) {
		checkJavaPath(queuedCheck);
		queuedCheck.clear();
	}
}

void JavaSettingsWidget::retranslate()
{
	m_memoryGroupBox->setTitle(tr("Memory"));
	m_maxMemSpinBox->setToolTip(
		tr("The maximum amount of memory Minecraft is allowed to use."));
	m_labelMinMem->setText(tr("Minimum memory allocation:"));
	m_labelMaxMem->setText(tr("Maximum memory allocation:"));
	m_minMemSpinBox->setToolTip(
		tr("The amount of memory Minecraft is started with."));
	m_permGenSpinBox->setToolTip(
		tr("The amount of memory available to store loaded Java classes."));
	m_javaBrowseBtn->setText(tr("Browse"));
}
