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

#include "datetime.h"

#include "../config.h"
#include "../mainwindow.h"
#include "../progressbar.h"

#include <QLineEdit>

// DateTimeEdit

// public:

DateTimeEdit::DateTimeEdit() {
	connect(lineEdit(), &QLineEdit::selectionChanged, [this] { onLineEditSelectionChange(); });
}

// private:

void DateTimeEdit::onLineEditSelectionChange() {
	if (displayedSections() != (HourSection | MinuteSection))
		return;

	QString selectedText = lineEdit()->selectedText();
	QLocale cLocale = QLocale::c();

	// HACK: Change selection from "15h" to "15" after double click.
	//       This fixes Up/Down keys and mouse wheel editing.
	if (selectedText == cLocale.toString(time(), "hh'h'"))
		lineEdit()->setSelection(0, 2);
	else if (selectedText == cLocale.toString(time(), "mm'm'"))
		lineEdit()->setSelection(6, 2);
}

// DateTimeTriggerBase

// public

DateTimeTriggerBase::DateTimeTriggerBase(const QString &text, const QString &iconName, const QString &id) :
	Trigger(text, iconName, id)
{
	m_dateTimeEdit = std::make_shared<DateTimeEdit>();

	setSelectedTime(QTime(1, 0, 0));
}

DateTimeTriggerBase::~DateTimeTriggerBase() { }

bool DateTimeTriggerBase::canActivateAction() {
	auto now = QDateTime::currentDateTime();
	seconds secsTo = createStatus(now);

	if (secsTo >= 0s) {
		updateProgressWidgets(secsTo, m_totalSecs);
	}

	if (secsTo > 0s) {
		QString id = QString();

		const seconds gap = 5s;

		if ((secsTo < 1min) && (secsTo > 1min - gap))
			id = "1m";
		else if ((secsTo < 5min) && (secsTo > 5min - gap))
			id = "5m";
		else if ((secsTo < 30min) && (secsTo > 30min - gap))
			id = "30m";
		else if ((secsTo < 1h) && (secsTo > 1h - gap))
			id = "1h";
		else if ((secsTo < 2h) && (secsTo > 2h - gap))
			id = "2h";

		if (!id.isNull()) {
			MainWindow::self()
				->showNotification(id);
		}
	}
	
	return now >= m_endDateTime;
}

QString DateTimeTriggerBase::formatSecsTo(const seconds &secsTo, const bool lcd) {
	if (secsTo < Utils::DAY_HOURS) {
		QString format = lcd ? "hh:mm:ss" : LONG_TIME_DISPLAY_FORMAT;

		const auto MIN = 0s;
		QTime time = Utils::fromSeconds((secsTo < MIN) ? MIN : secsTo);

		return QLocale::c()
			.toString(time, format);
	}
	else {
		auto hoursTo = duration_cast<hours>(secsTo);
		const auto MAX = 999h;

		QString result = QString::number(((hoursTo > MAX) ? MAX : hoursTo).count());

		result += lcd ? " h" : "h";

		return result;
	}
}

void DateTimeTriggerBase::initContainerWidget() {
	m_dateTimeEdit->setObjectName("date-time-edit");
	Utils::setFont(m_dateTimeEdit.get(), 2, true); // larger font

	auto syncDateTime = [this] { updateStatus(); };
	connect(m_dateTimeEdit.get(), &QDateTimeEdit::dateChanged, syncDateTime);
	connect(m_dateTimeEdit.get(), &QDateTimeEdit::timeChanged, syncDateTime);

	auto *layout = makeHBoxLayout();
	layout->addWidget(m_dateTimeEdit.get());

	initDateTimeWidget(m_dateTimeEdit.get());
}

// TODO: review usage and formatting
QString DateTimeTriggerBase::longDateTimeFormat() {
	QString dateFormat = "MMM d dddd";
	QString timeFormat = QLocale::system()
		.timeFormat(QLocale::ShortFormat);

	if (timeFormat.endsWith(" AP"))
		timeFormat = "h:mm AP";
	else
		timeFormat = "hh:mm";
	
	return dateFormat + ' ' + timeFormat;
}

QLocale DateTimeTriggerBase::longDateTimeLocale() {
	QLocale locale = QLocale::system();

	// HACK: use ISO digit characters for consistency
	if (
		((locale.name() == "zh_HK") || (locale.name() == "zh_MO")) &&
		(locale.toString(QTime(1, 1), "hh:mm") != "01:01")
	)
		return QLocale::c();

	return locale;
}

void DateTimeTriggerBase::readConfig() {
	auto dateTime = Config::readVariant(configGroup(), "Date Time", getSelectedDateTime())
		.toDateTime();

	auto *dateTimeTrigger = dynamic_cast<DateTimeTrigger *>(this);
	if (dateTimeTrigger != nullptr) {
// TODO: adjust to +1 hour (?)
		dateTimeTrigger->setSelectedDateTimeAdjusted(dateTime);
	}
	else {
		QDate date = dateTime.date();
		QTime time = dateTime.time();

		if (date.isValid())
			m_dateTimeEdit->setDate(date);

		if (time.isValid())
			setSelectedTime(time);
	}
}

void DateTimeTriggerBase::writeConfig() {
	Config::writeVariant(configGroup(), "Date Time", getSelectedDateTime());
}

void DateTimeTriggerBase::setState(const State state) {
	if (state == State::Start) {
		auto now = QDateTime::currentDateTime();
		m_endDateTime = calcEndTime(now);
		m_totalSecs = seconds(now.secsTo(m_endDateTime));

		// reset
		updateProgressWidgets(0s, 0s);
	}
}

// protected

QDateTime DateTimeTriggerBase::calcEndTime([[maybe_unused]] const QDateTime &now) {
	return getSelectedDateTime();
}

void DateTimeTriggerBase::updateProgressWidgets(const seconds &secsTo, const seconds &totalSecs) {
	auto *mainWindow = MainWindow::self();

	auto progressBar = mainWindow->progressBar();
	progressBar->updateValue(secsTo, totalSecs);

	QString s = (totalSecs == 0s)
		? "--:--:--"
		: formatSecsTo(secsTo, true);

	mainWindow->countdown()->setDigitCount(s.length());
	mainWindow->countdown()->display(s);
}

void DateTimeTriggerBase::updateStatus() {
	auto now = QDateTime::currentDateTime();
	m_endDateTime = calcEndTime(now);
	createStatus(now);
}

// private

seconds DateTimeTriggerBase::createStatus(const QDateTime &now) {
	auto secsTo = seconds(now.secsTo(m_endDateTime));

	if (secsTo > 0s) {
		QString result = "${BEGIN:b}+" + formatSecsTo(secsTo) + "${END:b}";

		result += "\n(";

		if (secsTo >= Utils::DAY_HOURS)
			result += i18n("not recommended") + ", ";

		result += i18n("selected time: %0").arg(longDateTimeLocale().toString(m_endDateTime, longDateTimeFormat()));
		result += ')';
		
		setInfoStatus(
			i18n("Remaining time: %0")
				.arg(result)
		);
	}
	else if (secsTo == 0s) {
		setInfoStatus("");
	}
	else /* if (secsTo < 0) */ {
		setWarningStatus(i18n("Invalid date/time"));
	}

	return secsTo;
}

// DateTimeTrigger

// public

DateTimeTrigger::DateTimeTrigger() :
	DateTimeTriggerBase(i18n("At Date/Time"), "view-pim-calendar", "date-time")
{
	setCanBookmark(true);

	const seconds hour = 1h;
	auto now = QDateTime::currentDateTime();
	setSelectedDateTime(now.addSecs(hour.count()));
}

QString DateTimeTrigger::getStringOption() {
	return getSelectedTime().toString(TIME_PARSE_FORMAT);
}

void DateTimeTrigger::setSelectedDateTimeAdjusted(const QDateTime &dateTime) {
	auto now = QDateTime::currentDateTime();

	// select next day if time is less than current time
	if (dateTime < now)
		setSelectedDateTime(QDateTime(now.addDays(1).date(), dateTime.time()));
	else
		setSelectedDateTime(dateTime);
}

void DateTimeTrigger::setStringOption(const QString &option) {
	QTime time = QTime::fromString(option, TIME_PARSE_FORMAT);//!!!!

	setSelectedDateTime(QDateTime(QDate::currentDate(), time));///!!!!
}

void DateTimeTrigger::initDateTimeWidget(QDateTimeEdit *dateTimeEdit) {

	// init properties

	dateTimeEdit->setCalendarPopup(true);
	dateTimeEdit->setDisplayFormat(DateTimeTriggerBase::longDateTimeFormat());

	QLocale locale = longDateTimeLocale();

	if (locale != QLocale::system())
		dateTimeEdit->setLocale(locale);

	dateTimeEdit->setMinimumDate(QDate::currentDate());//!!!!?
	//dateTimeEdit->setMinimumDateTime(QDateTime::currentDateTime());
	dateTimeEdit->setToolTip(i18n("Enter date and time"));

	// "Now" button

	auto *nowButton = new QPushButton(/*QIcon::fromTheme("go-jump-today"), */i18n("Now"));
	nowButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred));
	nowButton->setToolTip(i18n("Set current date and time"));
	connect(nowButton, &QPushButton::clicked, [this] {
		const seconds minute = 1min;
		auto now = QDateTime::currentDateTime();
		setSelectedDateTime(now.addSecs(minute.count()));
	});
	getContainerHBox()->addWidget(nowButton);
}

// NoDelayTrigger

// public

NoDelayTrigger::NoDelayTrigger() :
	Trigger(i18n("No Delay"), "dialog-warning", "no-delay") {
	
	setCanBookmark(true);
}

// TimeFromNowTrigger

// public

TimeFromNowTrigger::TimeFromNowTrigger() :
	DateTimeTriggerBase(i18n("Time From Now (HH:MM)"), "chronometer", "time-from-now")
{
	setCanBookmark(true);
}

QString TimeFromNowTrigger::getStringOption() {
	return getSelectedTime().toString(TIME_PARSE_FORMAT);
}

void TimeFromNowTrigger::setStringOption(const QString &option) {
	QTime time = QTime::fromString(option, TIME_PARSE_FORMAT);
	setSelectedTime(time);
}

void TimeFromNowTrigger::initDateTimeWidget(QDateTimeEdit *dateTimeEdit) {
// TODO: ":ss" format
	dateTimeEdit->setDisplayFormat(TIME_DISPLAY_FORMAT);
	dateTimeEdit->setLocale(QLocale::c());
	dateTimeEdit->setToolTip(i18n("Enter delay in \"HH:MM\" format (Hour:Minute)"));

	// "Presets" button

	QList<QTime> timeList = {
		// hour, minute
		{ 0,  5 },
		{ 0, 10 },
		{ 0, 15 },
		{ 0, 20 },
		{ 0, 25 },
		{ 0, 30 },
		{ 0, 45 },
		QTime(),
		{ 1,  0 },
		{ 1, 30 },
		{ 2,  0 }
	};

	auto *presetsMenu = new QMenu();

	for (auto &time : timeList) {
		if (time.isNull()) {
			presetsMenu->addSeparator();

			continue; // for
		}

		presetsMenu->addAction(time.toString(TIME_DISPLAY_FORMAT), [this, time] {
			setSelectedTime(time);
		});
	}

	auto *presetsButton = new QPushButton(i18n("Presets"));
	presetsButton->setMenu(presetsMenu);
	presetsButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred));
	getContainerHBox()->addWidget(presetsButton);
}

// protected

QDateTime TimeFromNowTrigger::calcEndTime(const QDateTime &now) {
	QTime time = getSelectedTime();

// TODO: ":ss" format
	return now.addSecs(Utils::toSeconds(time).count());
}
