// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2008 Konrad Twardowski

#include "progressbar.h"

#include "commandline.h"
#include "config.h"
#include "mainwindow.h"
#include "password.h"
#include "utils.h"

#include <QColorDialog>
#include <QDebug>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QScreen>
#include <QTimer>

#ifdef Q_OS_WIN32
	#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	#include <QWinTaskbarProgress>
	#endif // QT_VERSION
#endif // Q_OS_WIN32

// public

void ProgressBar::createSettingsMenu(QMenu *menu, const bool fullMode) {
	menu->clear();

	if (fullMode)
		Utils::addTitle(menu, QApplication::windowIcon(), windowTitle());

	bool canConfigure = !Utils::isRestricted("action/options_configure");

	if (fullMode) {
		menu->addAction(i18n("Hide"), [this] {
			if (PasswordDialog::authorizeSettings(this))
				hide();
		});

		menu->addSeparator();
	}

	auto *a = menu->addAction(i18n("Set Color..."), [this] { onSetColor(true); });
	a->setEnabled(canConfigure);
	makeIcon(a, Qt::AlignTop, -1);

	a = menu->addAction(i18n("Set Background Color..."), [this] { onSetColor(false); });
	a->setEnabled(canConfigure);
	makeIcon(a, Qt::AlignTop, 0);

	// position

	auto *positionMenu = new QMenu(i18n("Position"), menu);

	auto *positionGroup = new QActionGroup(this);

	a = positionMenu->addAction(i18n("Top"), [this] { setAlignment(Qt::AlignTop, true, true); });
	makeIcon(a, Qt::AlignTop, height());
	makeRadioButton(a, positionGroup, m_alignment.testFlag(Qt::AlignTop));
		
	a = positionMenu->addAction(i18n("Bottom"), [this] { setAlignment(Qt::AlignBottom, true, true); });
	makeIcon(a, Qt::AlignBottom, height());
	makeRadioButton(a, positionGroup, m_alignment.testFlag(Qt::AlignBottom));

/* TODO: vertical progress bar
	a = positionMenu->addAction(i18n("Left"), [this] { setAlignment(Qt::AlignLeft, true, true); });
	makeIcon(a, Qt::AlignLeft, height());
	makeRadioButton(a, positionGroup, m_alignment.testFlag(Qt::AlignLeft));

	a = positionMenu->addAction(i18n("Right"), [this] { setAlignment(Qt::AlignRight, true, true); });
	makeIcon(a, Qt::AlignRight, height());
	makeRadioButton(a, positionGroup, m_alignment.testFlag(Qt::AlignRight));
*/

	// size

	auto *sizeMenu = new QMenu(i18n("Size"), menu);

	auto *sizeGroup = new QActionGroup(this);
		
	a = sizeMenu->addAction(i18n("Small"), [this] { setSize(Size::Small, true); });
	makeSizeRadioButton(a, sizeGroup, Size::Small);

	a = sizeMenu->addAction(i18n("Normal"), [this] { setSize(Size::Normal, true); });
	makeSizeRadioButton(a, sizeGroup, Size::Normal);

	a = sizeMenu->addAction(i18n("Medium"), [this] { setSize(Size::Medium, true); });
	makeSizeRadioButton(a, sizeGroup, Size::Medium);

	a = sizeMenu->addAction(i18n("Large"), [this] { setSize(Size::Large, true); });
	makeSizeRadioButton(a, sizeGroup, Size::Large);

	menu->addSeparator();
	menu->addMenu(positionMenu);
	menu->addMenu(sizeMenu);

	if (fullMode) {
		menu->addSeparator();
		menu->addAction(MainWindow::self()->cancelAction());
	}
}

void ProgressBar::setAlignment(const Qt::Alignment value, const bool updateConfig, const bool authorize) {
	if (authorize && !PasswordDialog::authorizeSettings(this))
		return;

	if (updateConfig) {
		m_alignmentVar->setInt(value);
		m_alignmentVar->write(true);
	}

	m_alignment = value;
	QSize screenSize = QApplication::primaryScreen()->size();

	int margin = 2_px;

	resize(screenSize.width() - margin * 2, height());
	if (m_alignment.testFlag(Qt::AlignBottom)) {
		move(margin, screenSize.height() - height());
	}
	// Qt::AlignTop
	else {
		move(margin, 0_px);
	}
}


void ProgressBar::setDemo(const bool active) {
	qDebug() << "ProgressBar::setDemo: " << active;

	if (active) {
		m_demoWidth = 0_px;
		m_demoTimer->start(50ms);
	}
	else {
		m_demoTimer->stop();
	}
}

void ProgressBar::setHeight(const int value) {
	resize(width(), qMax(2_px, value));
}

void ProgressBar::updateTaskbar(const double progress, const seconds &seconds) {//!!!review
	#if defined(Q_OS_LINUX) && defined(QT_DBUS_LIB)
	// CREDITS: https://askubuntu.com/questions/65054/unity-launcher-api-for-c/310940
	// DOC: https://wiki.ubuntu.com/Unity/LauncherAPI

	if (!Utils::isUnity() && !Utils::isKDE())
		return;

	QDBusMessage taskbarMessage = QDBusMessage::createSignal(
		"/",
		"com.canonical.Unity.LauncherEntry",
		"Update"
	);

	taskbarMessage << "application://kshutdown.desktop";

	QVariantMap properties;

	bool countVisible = (seconds >= 0s) && (seconds <= 1min);
	bool urgent = seconds <= 1min;
	properties["count"] = qint64(countVisible ? seconds.count() : 0);
	properties["count-visible"] = countVisible;

	bool progressVisible = (progress >= 0);
	properties["progress"] = progressVisible ? progress : 0;
	properties["progress-visible"] = progressVisible;

	properties["urgent"] = urgent;

/* TEST:
	qDebug() << "";
	qDebug() << progress;
	qDebug() << urgent;
	qDebug() << countVisible << progressVisible;
*/

	taskbarMessage << properties;
	QDBusConnection::sessionBus()
		.send(taskbarMessage);
	#elif defined(Q_OS_WIN32)
	Q_UNUSED(seconds)

	#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	QWinTaskbarButton *winTaskbarButton = MainWindow::self()->winTaskbarButton();
	if (winTaskbarButton) {
		QWinTaskbarProgress *taskbarProgress = winTaskbarButton->progress();
		taskbarProgress->setRange(0, 100);
		taskbarProgress->setValue((int)(progress * 100));
		taskbarProgress->setVisible(progress >= 0);
	}
	#else
	Q_UNUSED(progress)
	#endif // QT_VERSION_CHECK

	#else
	Q_UNUSED(progress)
	Q_UNUSED(seconds)
	#endif // defined(Q_OS_LINUX) && defined(QT_DBUS_LIB)
}

void ProgressBar::updateValue(const seconds &secsTo, const seconds &totalSecs) {
	m_secsTo = secsTo;
	m_totalSecs = totalSecs;
	m_completeWidth = 0_px; // reset

	if ((m_totalSecs == 0s) || (m_secsTo == -1s)) {//!!!?
		updateTaskbar(-1, -1s);

		return;
	}

	double progress = 1 - (double)m_secsTo.count() / (double)m_totalSecs.count();
	progress = std::clamp(progress, 0.0, 1.0);
	updateTaskbar(progress, m_secsTo);

	int newCompleteWidth = (int)((float)width() * ((float)(m_totalSecs.count() - m_secsTo.count()) / (float)m_totalSecs.count()));
	if (newCompleteWidth != m_completeWidth) {
		m_completeWidth = newCompleteWidth;

		//qDebug() << progress << m_completeWidth << width();

		repaint();
	}
}

// protected

void ProgressBar::contextMenuEvent(QContextMenuEvent *e) {
	if (
		CLI::isArg("hide-ui") ||
		Utils::isRestricted("kshutdown/progress_bar/menu")
	) {
		return;
	}

	auto *menu = new QMenu(this);
	createSettingsMenu(menu, true);
	menu->popup(e->globalPos());

	e->accept();
}

void ProgressBar::mousePressEvent(QMouseEvent *e) {
	if (!CLI::isArg("hide-ui") && (e->button() == Qt::LeftButton)) {
		auto *mainWindow = MainWindow::self();
		mainWindow->show();
		mainWindow->activateWindow();

		e->accept();

		return;
	}

	QWidget::mousePressEvent(e);
}

void ProgressBar::paintEvent([[maybe_unused]] QPaintEvent *e) {
	QPainter g(this);

	int x = 0_px;
	int y = 0_px;
	int w = width();
	int h = height();

	if (m_demoTimer->isActive()) {
		g.fillRect(x, y, w, h, Qt::black);
		g.fillRect(x, y, qMin(m_demoWidth, w), h, m_demoColor);
	}
	else {
// TODO: reversed option
		QPalette p = palette();
		g.fillRect(x, y, w, h, p.window());

		if ((m_completeWidth <= 0_px) || (m_totalSecs <= 0s))
			return;

		g.fillRect(x, y, m_completeWidth, h, p.windowText());
	}
}

// private

ProgressBar::ProgressBar() // public
	: QWidget(
		nullptr,
		Qt::FramelessWindowHint |
		Qt::NoDropShadowWindowHint |
		Qt::WindowDoesNotAcceptFocus |
		Qt::WindowStaysOnTopHint |

// FIXME: GNOME - no context menu, no tool tip
		Qt::X11BypassWindowManagerHint |

		Qt::Tool
	) {

	m_alignmentVar = new Var("Progress Bar", "Alignment", Qt::AlignTop);
	m_backgroundColorVar = new Var("Progress Bar", "Background Color", QColor(Qt::black));
	m_foregroundColorVar = new Var("Progress Bar", "Foreground Color", QColor(0xF8FFBF/* lime 1 */));
	m_sizeVar = new Var("Progress Bar", "Size", static_cast<int>(Size::Normal));

	m_demoTimer = new QTimer(this);
	m_demoTimer->callOnTimeout([this] { onDemoTimeout(); });

	setAttribute(Qt::WA_AlwaysShowToolTips, true);
	setObjectName("progress-bar");

	setWindowTitle(Utils::makeTitle(i18n("Progress Bar"), QApplication::applicationDisplayName()));
	setToolTip(windowTitle());

	m_demoColor = m_foregroundColorVar->getDefault().value<QColor>();

	updatePalette();

	setHeight(std::clamp(
		m_sizeVar->getInt(),
		static_cast<int>(Size::Small),
		static_cast<int>(Size::Large)
	));

	setAlignment(static_cast<Qt::Alignment>(m_alignmentVar->getInt()), false);

	// update window location on screen size change
	auto *screen = QApplication::primaryScreen();
// TODO: QScreen::primaryScreenChanged
	connect(screen, &QScreen::geometryChanged, [this] { setAlignment(m_alignment, false); });
}

void ProgressBar::makeIcon(QAction *action, const Qt::Alignment alignment, const int _size) {
	int iconSize = Utils::getSmallIconSize()
		.width();

	QPalette p = palette();
	QPixmap pixmap(iconSize, iconSize);

	QPainter painter(&pixmap);
	painter.fillRect(0, 0, iconSize, iconSize, p.window());

	int fgSize = (_size == -1) ? iconSize : _size;

	if (fgSize > 0) {
		switch (alignment) {
			case Qt::AlignTop:
				painter.fillRect(0, 0, iconSize, fgSize, p.windowText());
				break;
			case Qt::AlignBottom:
				painter.fillRect(0, iconSize - fgSize, iconSize, fgSize, p.windowText());
				break;
			case Qt::AlignLeft:
				painter.fillRect(0, 0, fgSize, iconSize, p.windowText());
				break;
			case Qt::AlignRight:
				painter.fillRect(iconSize - fgSize, 0, fgSize, iconSize, p.windowText());
				break;
		}
	}

	action->setIcon(pixmap);
}

void ProgressBar::makeRadioButton(QAction *action, QActionGroup *group, const bool checked) {
	action->setActionGroup(group);
	action->setCheckable(true);
	action->setChecked(checked);
	action->setEnabled(!Utils::isRestricted("action/options_configure"));
}

void ProgressBar::setSize(const Size size, const bool authorize) {
	if (authorize && !PasswordDialog::authorizeSettings(this))
		return;

	int s = static_cast<int>(size);

	setHeight(s);
	setAlignment(m_alignment, false);

	m_sizeVar->setInt(s);
	m_sizeVar->write(true);
}

void ProgressBar::updatePalette() {
	// create copy without alpha
	QColor bg = QColor(m_backgroundColorVar->getColor().rgb());
	QColor fg = QColor(m_foregroundColorVar->getColor().rgb());

	QPalette p;
	p.setColor(QPalette::Window, bg);
	p.setColor(QPalette::WindowText, fg);
	setPalette(p);

	float a = m_backgroundColorVar->getColor().alphaF();
	setWindowOpacity(std::clamp(a, 0.2f, 1.0f));
}

// event handlers:

void ProgressBar::onDemoTimeout() {
	m_demoWidth += 5_px;
	if (m_demoWidth > width() / 3) {
		m_demoWidth = 0_px;
		m_demoTimer->stop();
		repaint();

		return;
	}

	int h;
	int s;
	int v;
	m_demoColor.getHsv(&h, &s, &v);
	h = (h + 5) % 360;
	m_demoColor.setHsv(h, s, v);

	repaint();
}

void ProgressBar::onSetColor(const bool foreground) {
	if (!PasswordDialog::authorizeSettings(this))
		return;

	QColor currentColor = foreground ? m_foregroundColorVar->getColor() : m_backgroundColorVar->getColor();
	auto options = foreground ? QColorDialog::ColorDialogOptions() : QColorDialog::ShowAlphaChannel;
	QColor newColor = QColorDialog::getColor(currentColor, this, QString(), options);

	if (newColor.isValid()) {
		if (foreground) {
			m_foregroundColorVar->setColor(newColor);
			m_foregroundColorVar->write(true);
		}
		else {
			m_backgroundColorVar->setColor(newColor);
			m_backgroundColorVar->write(true);
		}

		updatePalette();
	}
}
