/*
 * 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 <algorithm> /* ::std::sort */
#include <climits>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>

#include "src/datovka_shared/graphics/colour.h"
#include "src/datovka_shared/io/prefs_db.h"
#include "src/datovka_shared/settings/prefs.h"
#include "src/dimensions/dimensions.h"
#include "src/global.h"
#include "src/gui/dlg_prefs.h"
#include "src/model_interaction/view_interaction.h"
#include "src/settings/prefs_specific.h"
#include "src/views/table_home_end_filter.h"
#include "src/views/table_key_press_filter.h"
#include "src/views/table_tab_ignore_filter.h"
#include "ui_dlg_prefs.h"

#define STACKED_WARNING 0 /* Warning page. */
#define STACKED_PREFS_LIST 1 /* Preferences list. */

static const QString dlgName("prefs");
static const QString prefsTableName("preference_list");

DlgPrefs::DlgPrefs(Prefs &prefs, QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgPrefs),
    m_prefListProxyModel(this),
    m_dfltFilerLineStyleSheet(SortFilterProxyModel::invalidFilterEditStyle),
    m_prefTableModel(prefs, this)
{
	m_ui->setupUi(this);

	/* Set default line height for table views/widgets. */
	m_ui->prefTableView->setNarrowedLineHeight();
	m_ui->prefTableView->horizontalHeader()->setDefaultAlignment(
	    Qt::AlignVCenter | Qt::AlignLeft);
	m_ui->prefTableView->setSelectionMode(
	    QAbstractItemView::ExtendedSelection);
	m_ui->prefTableView->setSelectionBehavior(
	    QAbstractItemView::SelectRows);

	m_prefListProxyModel.setSortRole(Qt::DisplayRole);
	m_prefListProxyModel.setSourceModel(&m_prefTableModel);
	{
		QList<int> columnList;
		columnList.append(PrefsModel::COL_NAME);
		columnList.append(PrefsModel::COL_VALUE);
		m_prefListProxyModel.setFilterKeyColumns(columnList);
	}
	m_ui->prefTableView->setModel(&m_prefListProxyModel);

	m_ui->prefTableView->sortByColumn(PrefsModel::COL_NAME, Qt::AscendingOrder);
	m_ui->prefTableView->setSortingEnabled(true);
	m_ui->prefTableView->resizeColumnsToContents();

	m_ui->filterLine->setToolTip(tr("Enter sought expression"));
	m_ui->filterLine->setClearButtonEnabled(true);

	connect(m_ui->warningAcceptButton, SIGNAL(clicked()),
	    this, SLOT(viewPrefsList()));

	connect(m_ui->filterLine, SIGNAL(textChanged(QString)),
	    this, SLOT(filterPrefs(QString)));
	connect(m_ui->prefTableView, SIGNAL(doubleClicked(QModelIndex)),
	    this, SLOT(prefItemDoubleClicked(QModelIndex)));

	{
		TableKeyPressFilter *filter =
		    new (::std::nothrow) TableKeyPressFilter(m_ui->prefTableView);
		if (Q_NULLPTR != filter) {
			filter->registerAction(Qt::Key_Return,
			    editPrefItemViaFilter, this, true);
			filter->registerAction(Qt::Key_Enter, /* On keypad. */
			    editPrefItemViaFilter, this, true);
#ifdef Q_OS_OSX
			filter->registerAction(Qt::CTRL | Qt::Key_Backspace,
			    prefResetViaFilter, this, true);
#endif /* Q_OS_OSX */
			filter->registerAction(Qt::Key_Delete,
			    prefResetViaFilter, this, true);
			m_ui->prefTableView->installEventFilter(filter);
		}
	}

	m_ui->prefTableView->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(m_ui->prefTableView, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewPrefListContextMenu(QPoint)));

	m_ui->prefTableView->installEventFilter(
	    new (::std::nothrow) TableHomeEndFilter(m_ui->prefTableView));
	m_ui->prefTableView->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(m_ui->prefTableView));
}

DlgPrefs::~DlgPrefs(void)
{
	if (STACKED_PREFS_LIST == m_ui->stackedWidget->currentIndex()) {
		PrefsSpecific::setDlgTblRelColWidths(*GlobInstcs::prefsPtr,
		    dlgName, prefsTableName,
		    Dimensions::relativeTableColumnWidths(m_ui->prefTableView));
		PrefsSpecific::setDlgTblColSortOrder(*GlobInstcs::prefsPtr,
		    dlgName, prefsTableName,
		    Dimensions::tableColumnSortOrder(m_ui->prefTableView));
	}

	delete m_ui;
}

void DlgPrefs::modify(Prefs &prefs, QWidget *parent)
{
	DlgPrefs dlg(prefs, parent);

	const QSize dfltSize = dlg.size();
	{
		const QSize newSize = Dimensions::dialogueSize(&dlg,
		    PrefsSpecific::dlgSize(*GlobInstcs::prefsPtr, dlgName),
		    dfltSize);
		if (newSize.isValid()) {
			dlg.resize(newSize);
		}
	}

	dlg.exec();

	PrefsSpecific::setDlgSize(*GlobInstcs::prefsPtr, dlgName,
	    dlg.size(), dfltSize);
}

void DlgPrefs::viewPrefsList(void)
{
	m_ui->stackedWidget->setCurrentIndex(STACKED_PREFS_LIST);

	Dimensions::setRelativeTableColumnWidths(m_ui->prefTableView,
	    PrefsSpecific::dlgTblRelColWidths(*GlobInstcs::prefsPtr,
	    dlgName, prefsTableName));
	Dimensions::setTableColumnSortOrder(m_ui->prefTableView,
	    PrefsSpecific::dlgTblColSortOrder(*GlobInstcs::prefsPtr,
	    dlgName, prefsTableName));
}

void DlgPrefs::filterPrefs(const QString &text)
{
	/* Store style at first invocation. */
	if (m_dfltFilerLineStyleSheet == SortFilterProxyModel::invalidFilterEditStyle) {
		m_dfltFilerLineStyleSheet = m_ui->filterLine->styleSheet();
	}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
	m_prefListProxyModel.setFilterRegularExpression(
	    QRegularExpression(QRegularExpression::escape(text),
	        QRegularExpression::CaseInsensitiveOption));
#else /* < Qt-5.15 */
	m_prefListProxyModel.setFilterRegExp(QRegExp(text,
	    Qt::CaseInsensitive, QRegExp::FixedString));
#endif /* >= Qt-5.15 */
	/* Set filter field background colour. */
	if (text.isEmpty()) {
		m_ui->filterLine->setStyleSheet(m_dfltFilerLineStyleSheet);
	} else if (m_prefListProxyModel.rowCount() != 0) {
		m_ui->filterLine->setStyleSheet(
		    SortFilterProxyModel::foundFilterEditStyle);
	} else {
		m_ui->filterLine->setStyleSheet(
		    SortFilterProxyModel::notFoundFilterEditStyle);
	}
}

void DlgPrefs::prefItemDoubleClicked(const QModelIndex &index)
{
	const QModelIndex srcIdx(m_prefListProxyModel.mapToSource(index));

	if (srcIdx.isValid() && (srcIdx.column() == PrefsModel::COL_VALUE)) {
		int row = srcIdx.row();
		switch (m_prefTableModel.type(row)) {
		case PrefsDb::VAL_BOOLEAN:
			m_prefTableModel.alterBool(row);
			break;
		case PrefsDb::VAL_INTEGER:
			{
				bool iOk = false;
				int val = m_prefTableModel.data(srcIdx).toString().toInt();
				int newVal = QInputDialog::getInt(this, tr("Enter a Value"),
				    m_prefTableModel.data(srcIdx.sibling(row, PrefsModel::COL_NAME)).toString(),
				    val, INT_MIN, INT_MAX, 1, &iOk);
				if (iOk && (val != newVal)) {
					m_prefTableModel.modifyInt(row, newVal);
				}
			}
			break;
		case PrefsDb::VAL_FLOAT:
			{
				bool iOk = false;
				double val = m_prefTableModel.data(srcIdx).toString().toDouble();
				double newVal = QInputDialog::getDouble(this, tr("Enter a Value"),
				    m_prefTableModel.data(srcIdx.sibling(row, PrefsModel::COL_NAME)).toString(),
				    val, INT_MIN, INT_MAX, 3, &iOk);
				if (iOk && (val != newVal)) {
					m_prefTableModel.modifyFloat(row, newVal);
				}
			}
			break;
		case PrefsDb::VAL_STRING:
			{
				bool iOk = false;
				QString val = m_prefTableModel.data(srcIdx).toString();
				QString newVal = QInputDialog::getText(this, tr("Enter a Value"),
				    m_prefTableModel.data(srcIdx.sibling(row, PrefsModel::COL_NAME)).toString(),
				    QLineEdit::Normal, val, &iOk);
				if (iOk && (val != newVal)) {
					m_prefTableModel.modifyString(row, newVal);
				}
			}
			break;
		case PrefsDb::VAL_COLOUR:
			{
				bool iOk = false;
				QString val = m_prefTableModel.data(srcIdx).toString();
				QString newVal = QInputDialog::getText(this, tr("Enter a Value"),
				    m_prefTableModel.data(srcIdx.sibling(row, PrefsModel::COL_NAME)).toString(),
				    QLineEdit::Normal, val, &iOk);
				if (iOk && (val != newVal)) {
					if (Colour::isValidColourStr(newVal)) {
						m_prefTableModel.modifyColour(row, newVal);
					} else {
						QMessageBox::warning(this, tr("Value Error"),
						    tr("Entered invalid value."),
						    QMessageBox::Ok);
					}
				}
			}
			break;
		case PrefsDb::VAL_DATETIME:
			{
				bool iOk = false;
				QString valStr = m_prefTableModel.data(srcIdx).toString();
				QString newValStr = QInputDialog::getText(this, tr("Enter a Value"),
				    m_prefTableModel.data(srcIdx.sibling(row, PrefsModel::COL_NAME)).toString(),
				    QLineEdit::Normal, valStr, &iOk);
				if (iOk && (valStr != newValStr)) {
					QDateTime newVal = PrefsDb::stringToDateTime(newValStr);
					if (newVal.isValid()) {
						m_prefTableModel.modifyDateTime(row, newVal);
					} else {
						QMessageBox::warning(this, tr("Value Error"),
						    tr("Entered invalid value."),
						    QMessageBox::Ok);
					}
				}
			}
			break;
		case PrefsDb::VAL_DATE:
			{
				bool iOk = false;
				QString valStr = m_prefTableModel.data(srcIdx).toString();
				QString newValStr = QInputDialog::getText(this, tr("Enter a Value"),
				    m_prefTableModel.data(srcIdx.sibling(row, PrefsModel::COL_NAME)).toString(),
				    QLineEdit::Normal, valStr, &iOk);
				if (iOk && (valStr != newValStr)) {
					QDate newVal = QDate::fromString(newValStr, Qt::ISODate);
					if (newVal.isValid()) {
						m_prefTableModel.modifyDate(row, newVal);
					} else {
						QMessageBox::warning(this, tr("Value Error"),
						    tr("Entered invalid value."),
						    QMessageBox::Ok);
					}
				}
			}
			break;
		default:
			break;
		}
	}
}

void DlgPrefs::viewPrefListContextMenu(const QPoint &point)
{
	if (!m_ui->prefTableView->indexAt(point).isValid()) {
		/* Do nothing. */
		return;
	}

	QMenu *menu = new (::std::nothrow) QMenu(m_ui->prefTableView);
	if (Q_UNLIKELY(menu == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	menu->addAction(tr("Reset"), this,
	    SLOT(prefReset()));

	menu->exec(QCursor::pos());
	menu->deleteLater();
}

void DlgPrefs::prefReset(void)
{
	QList<int> rows = ViewInteraction::selectedSrcRowNums(
	    *(m_ui->prefTableView), m_prefListProxyModel, PrefsModel::COL_NAME);

	::std::sort(rows.begin(), rows.end());

	/* Iterate in reverse. */
	QList<int>::const_iterator it = rows.constEnd();
	while (it != rows.constBegin()) {
		--it;
		int row = *it;

		m_prefTableModel.resetEntry(row);
	}
}

void DlgPrefs::editPrefItemViaFilter(QObject *dlgPtr)
{
	if (Q_UNLIKELY(dlgPtr == Q_NULLPTR)) {
		return;
	}

	DlgPrefs *dlg = qobject_cast<DlgPrefs *>(dlgPtr);
	if (Q_UNLIKELY(dlg == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	LoweredTableView *view = dlg->m_ui->prefTableView;
	if (Q_UNLIKELY(Q_NULLPTR == view)) {
		Q_ASSERT(0);
		return;
	}

	/* Must use PrefsModel::COL_VALUE. */
	const QModelIndexList indexes =
	    view->selectionModel()->selectedRows(PrefsModel::COL_VALUE);
	/* Continue only when one message selected. */
	if (indexes.size() != 1) {
		return;
	}

	dlg->prefItemDoubleClicked(indexes.first());
}

void DlgPrefs::prefResetViaFilter(QObject *dlgPtr)
{
	if (Q_UNLIKELY(dlgPtr == Q_NULLPTR)) {
		return;
	}

	DlgPrefs *dlg = qobject_cast<DlgPrefs *>(dlgPtr);
	if (Q_UNLIKELY(dlg == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	dlg->prefReset();
}
