/*
 * Copyright (C) 2014-2026 CZ.NIC
 *
 * 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/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <QtMath> /* qCeil */
#include <QApplication>
#include <QCalendarWidget>
#include <QDate>
#include <QPalette>
#include <QPushButton>
#include <QRegularExpression>
#include <QTextCharFormat>

#include "src/datovka_shared/log/log.h"
#include "src/global.h"
#include "src/gui/styles.h"
#include "src/settings/prefs_specific.h"

bool GuiStyles::isDarkTheme = false;

GuiStyles::AppUiTheme GuiStyles::intToAppUiTheme(int index)
{
	switch (index) {
	case NO_THEME:
		return NO_THEME;
		break;
	case LIGHT_THEME:
		return LIGHT_THEME;
		break;
	case DARK_THEME:
		return DARK_THEME;
		break;
	default:
		return NO_THEME;
		break;
	}
}

QString GuiStyles::styleSheetPath(int index)
{
	switch (index) {
	case NO_THEME:
		isDarkTheme = detectDarkSystemTheme();
		return QString();
		break;
	case LIGHT_THEME:
		isDarkTheme = false;
#if defined(Q_OS_MACOS)
		isDarkTheme = detectDarkSystemTheme();
		return ":/stylesheet/DatovkaStylesheetMacOS.qss";
#elif defined(Q_OS_WIN)
		return ":/stylesheet/DatovkaStylesheetLightWindows.qss";
#else /* default */
		return ":/stylesheet/DatovkaStylesheetLightLinux.qss";
#endif
		break;
	case DARK_THEME:
		isDarkTheme = true;
#if defined(Q_OS_WIN)
		return ":/stylesheet/DatovkaStylesheetDarkWindows.qss";
#else /* default */
		return ":/stylesheet/DatovkaStylesheetDarkLinux.qss";
#endif
		break;
	default:
		/* Default to system theme on any unsupported value. */
		isDarkTheme = detectDarkSystemTheme();
		return QString();
		break;
	}
}

/*!
 * @brief Collect all occurrences of @FFFfrtopx regular expression matches to a map.
 *
 * @param[in] str Text to be processed.
 * @param[in] re Regular expression matching @FFFfrtopx.
 * @param[in,out] ratiosMapping Maps matched string to respective FFF floating-point values.
 */
static
void collectRatios(const QString &str, const QRegularExpression &re, QMap<QString, double> &ratiosMapping)
{
	QRegularExpressionMatchIterator it = re.globalMatch(str);
	while (it.hasNext()) {
		const QRegularExpressionMatch match = it.next();
		if (match.hasMatch()) {
			QString expression = match.captured(0).toUtf8().constData();
			{
				bool iOk = false;
				double value = match.captured(1).toDouble(&iOk);
				if (Q_UNLIKELY(!iOk)) {
					logErrorNL("Cannot obtain ratio number from '%s'.",
					     expression.toUtf8().constData());
				}
				ratiosMapping[expression] = value;
			}
		}
	}
}

/*!
 * @brief Replaces all occurrences of '@FFFfrtopx' and '@(FFF)frtopx' with the
 *     expression 'IIIpx' where III = FFF * fontHeightPx.
 *
 * @note Eg.: If \a fontHeightPx is 14 then '@2.0frtopx' is replaced by '28px'.
 *
 * @param[in] styleSheet Style sheet text to be modified.
 * @param[in] fontHeightPx Font height in pixels.
 * @return Modified style sheet text.
 */
static
QString replaceStyleFontSizeRatios(QString styleSheet, const int fontHeightPx)
{
	static const QRegularExpression re1(
	    "@(((\\+|-)?([0-9]+)(\\.[0-9]+)?)|((\\+|-)?\\.?[0-9]+))frtopx");
	static const QRegularExpression re2(
	    "@\\((((\\+|-)?([0-9]+)(\\.[0-9]+)?)|((\\+|-)?\\.?[0-9]+))\\)frtopx");

	static const QString pxStr("%1px");

	QMap<QString, double> ratiosMapping;

	/* Collect ratios. */
	collectRatios(styleSheet, re1, ratiosMapping);
	collectRatios(styleSheet, re2, ratiosMapping);

	/* Compute size in pixels. */
	QMap<QString, int> valueMapping;
	for (const QString &key : ratiosMapping.keys()) {
		double ratio = ratiosMapping.value(key);
		int pixels = qCeil(ratio * fontHeightPx);
		valueMapping[key] = pixels;
	}

	for (const QString &key : valueMapping.keys()) {
		styleSheet.replace(key, pxStr.arg(valueMapping.value(key)));
	}

	return styleSheet;
}

QString GuiStyles::replaceStyleSheetVariables(QString styleSheet,
    qint64 fontSizePt)
{
	int fontHeight = 0;
	{
		QPushButton pushButton;
		/* Enforce default style. */
		pushButton.setStyleSheet(QString());
		fontHeight = pushButton.fontMetrics().height();
	}

	styleSheet = replaceStyleFontSizeRatios(styleSheet, fontHeight);

	/* Font size. */
	styleSheet.replace("@font_size_pt", QString("%1pt").arg(fontSizePt));

	/* Colour definition for style sheets. */
	styleSheet.replace("@almost_white", "#fafafa");
	styleSheet.replace("@smoke_white", "#f5f5f5");
	styleSheet.replace("@soft_black", "#282828");
	styleSheet.replace("@selection_bg_color_light_theme", "#66a4e1");
	styleSheet.replace("@light_blue", "#0086fb");
	styleSheet.replace("@hover_light_blue", "#0067c0");
	styleSheet.replace("@pressed_light_blue", "#003d72");
	styleSheet.replace("@selection_bg_color_dark_theme", "#f57c00");
	styleSheet.replace("@orange_hover", "#fb8c00");
	styleSheet.replace("@orange_pressed", "#ef6c00");
	styleSheet.replace("@ultra_light_gray", "#d1d1d1");
	styleSheet.replace("@extra_light_gray", "#b6b6b6");
	styleSheet.replace("@light_gray", "#888a85");
	styleSheet.replace("@gray", "#5b5b5b");
	styleSheet.replace("@darker_gray", "#414141");
	styleSheet.replace("@blue_gray", "#454e5e");
	styleSheet.replace("@almost_dark_gray", "#2e2e2e");
	styleSheet.replace("@dark_gray", "#222222");

#if defined(Q_OS_MACOS)
	if (isDarkTheme) {
		styleSheet.replace("@selection_bg_color", "#f57c00");
		styleSheet.replace("@btn_color", "#f57c00");
		styleSheet.replace("@hover_btn_color", "#fb8c00");
		styleSheet.replace("@pressed_btn_color", "#ef6c00");
		styleSheet.replace("@border_color_MAC", "#888a85");
		styleSheet.replace("@disabled_color_MAC", "#5b5b5b");
		styleSheet.replace("@text_color_MAC", "#f5f5f5");
		styleSheet.replace("_unchecked.png", "_unchecked_dark.png");
		styleSheet.replace("_checked.png", "_checked_dark.png");
		styleSheet.replace("_item.png", "_item_dark.png");
		styleSheet.replace("_disabled.png", "_disabled_dark.png");
	} else {
		styleSheet.replace("@selection_bg_color", "#66a4e1");
		styleSheet.replace("@btn_color", "#0086fb");
		styleSheet.replace("@hover_btn_color", "#0067c0");
		styleSheet.replace("@pressed_btn_color", "#003d72");
		styleSheet.replace("@border_color_MAC", "#b6b6b6");
		styleSheet.replace("@disabled_color_MAC", "#d1d1d1");
		styleSheet.replace("@text_color_MAC", "#282828");
		styleSheet.replace("_unchecked.png", "_unchecked_light.png");
		styleSheet.replace("_checked.png", "_checked_light.png");
		styleSheet.replace("_item.png", "_item_light.png");
		styleSheet.replace("_disabled.png", "_disabled_light.png");
	}
#endif /* defined(Q_OS_MACOS) */

	return styleSheet;
}

void GuiStyles::setCalendarDatesStyle(QCalendarWidget *calendarWidget)
{
	if (Q_UNLIKELY(Q_NULLPTR == calendarWidget)) {
		return;
	}

	QTextCharFormat disabledFormat;
	disabledFormat.setForeground(QColor(128, 128, 128)); // extra_light_gray

	QTextCharFormat defaultFormat;

	const QDate firstDayOfMonth(calendarWidget->yearShown(), calendarWidget->monthShown(), 1);
	const QDate lastDayOfMonth = firstDayOfMonth.addMonths(1).addDays(-1);

	for (QDate date = firstDayOfMonth; date <= lastDayOfMonth; date = date.addDays(1)) {
		if (date > calendarWidget->maximumDate() || date < calendarWidget->minimumDate()) {
			calendarWidget->setDateTextFormat(date, disabledFormat);
		} else {
			calendarWidget->setDateTextFormat(date, defaultFormat);
		}
	}
}

void GuiStyles::setCalendarStyle(QCalendarWidget *calendarWidget)
{
	if (Q_UNLIKELY(Q_NULLPTR == calendarWidget)) {
		return;
	}

	if (intToAppUiTheme(PrefsSpecific::appUiTheme(*GlobInstcs::prefsPtr))
	        != NO_THEME) {
		QTextCharFormat headerFormat;
		headerFormat.setBackground(QBrush(QColor(91, 91, 91))); // gray
		headerFormat.setForeground(QBrush(QColor(245, 245, 245))); // smoke_white
		calendarWidget->setHeaderTextFormat(headerFormat);
		setCalendarDatesStyle(calendarWidget);
	}

	QObject::connect(calendarWidget, &QCalendarWidget::currentPageChanged,
	    [calendarWidget]() { setCalendarDatesStyle(calendarWidget); });
}

bool GuiStyles::detectDarkSystemTheme(void)
{
	return QApplication::palette().color(QPalette::WindowText).lightness()
	    > QApplication::palette().color(QPalette::Window).lightness();
}
