/*
 * 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 <QDir>
#include <QString>
#include <QStringBuilder>
#include <QTextEdit>
#include <QVBoxLayout>

#include "src/io/message_db_set_container.h"
#include "src/settings/account.h" /* AcntData */
#include "src/settings/accounts.h" /* AccountsMap */
#include "src/widgets/db_status_widget.h"

/*!
 * @brief Generate label text.
 *
 * @param[in] msgDbLoc Message database location.
 * @param[in] acntDbLoc Account database location.
 * @return Label text.
 */
static
QString labelText(enum DbStatusControl::DbLocation msgDbLoc,
    enum DbStatusControl::DbLocation acntDbLoc)
{
	auto locToStr = [](enum DbStatusControl::DbLocation l)
	{
		switch (l) {
		case DbStatusControl::LOC_DISK:
			return DbStatusControl::tr("disk");
			break;
		case DbStatusControl::LOC_MEMORY:
			return DbStatusControl::tr("memory");
			break;
		default:
			return QString();
			break;
		}
	};

	return DbStatusControl::tr("Storage:")
	    % QLatin1String(" ") % locToStr(msgDbLoc)
	    % QLatin1String(" | ") % locToStr(acntDbLoc)
	    % QLatin1String("   ");
}

/*!
 * @brief Generate label tool tip text.
 *
 * @param[in] msgDbLoc Message database location.
 * @param[in] acntDbLoc Account database location.
 * @param[in] hasErrors Whether there are database problems.
 * @return Label tool tip text.
 */
static
QString toolTipText(enum DbStatusControl::DbLocation msgDbLoc,
    enum DbStatusControl::DbLocation acntDbLoc, bool hasErrors)
{
	auto locToStr = [](enum DbStatusControl::DbLocation l)
	{
		switch (l) {
		case DbStatusControl::LOC_DISK:
			return DbStatusControl::tr("on disk");
			break;
		case DbStatusControl::LOC_MEMORY:
			return DbStatusControl::tr("in memory");
			break;
		default:
			return QString();
			break;
		}
	};

	QString toolTip = DbStatusControl::tr("Location of databases:")
	    % QLatin1String("\n") % DbStatusControl::tr("Message databases are located %1.").arg(locToStr(msgDbLoc))
	    % QLatin1String("\n") % DbStatusControl::tr("User databases are located %1.").arg(locToStr(acntDbLoc));

	if (hasErrors) {
	    toolTip += QLatin1String("\n\n") % DbStatusControl::tr("There have been errors when accessing databases.")
	        % QLatin1String("\n") % DbStatusControl::tr("Click here to view more information.");
	}

	return toolTip;
}

#define LAYOUT_MARGIN_PX 3

DbStatusControl::DbStatusControl(AccountsMap *regularAccounts,
    DbContainer *dbCont, QWidget *parent)
    : QLabel(parent),
    m_accounts(regularAccounts),
    m_problemDbFiles(),
    m_dbPopup(Q_NULLPTR),
    m_textEdit(Q_NULLPTR),
    m_msgDbLoc(LOC_DISK),
    m_acntDbLoc(LOC_DISK)
{
	if (Q_NULLPTR != m_accounts) {
		connect(m_accounts, SIGNAL(accountDataChanged(AcntId)),
		    this, SLOT(watchAccountDataChanged(AcntId)));
	}
	if (Q_NULLPTR != dbCont) {
		connect(dbCont, SIGNAL(openingFailed(AcntId, QString)),
		    this, SLOT(watchOpeningFailed(AcntId, QString)));
	}

	this->setText(labelText(m_msgDbLoc, m_acntDbLoc));
	this->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
	{
		const bool hasErrors = !m_problemDbFiles.isEmpty();

		this->setToolTip(toolTipText(m_msgDbLoc, m_acntDbLoc, hasErrors));
		if (hasErrors) {
			this->setStyleSheet("QLabel { color: red }");
		}
	}

	QVBoxLayout *verticalLayout = Q_NULLPTR;

	m_dbPopup = new (::std::nothrow) QWidget(parentWidget());
	if (Q_NULLPTR != m_dbPopup) {
		m_dbPopup->setObjectName(QString::fromUtf8("m_dbPopup"));
		m_dbPopup->setWindowFlags(Qt::Widget | Qt::Popup);

		m_textEdit = new (::std::nothrow) QTextEdit(m_dbPopup);
		if (Q_NULLPTR != m_textEdit) {
			m_textEdit->setObjectName(QString::fromUtf8("m_textEdit"));

			m_textEdit->setReadOnly(true);
		}

		verticalLayout = new (::std::nothrow) QVBoxLayout(m_dbPopup);
		if (Q_NULLPTR != verticalLayout) {
			verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));

			verticalLayout->setContentsMargins(
			    LAYOUT_MARGIN_PX, LAYOUT_MARGIN_PX,
			    LAYOUT_MARGIN_PX, LAYOUT_MARGIN_PX);

			verticalLayout->addWidget(m_textEdit);
		}
	}
}

DbStatusControl::~DbStatusControl(void)
{
	delete m_dbPopup;
}

void DbStatusControl::setStorageLocations(enum DbLocation msgDbLoc,
    enum DbLocation acntDbLoc)
{
	m_msgDbLoc = msgDbLoc;
	m_acntDbLoc = acntDbLoc;

	this->setText(labelText(m_msgDbLoc, m_acntDbLoc));
}

void DbStatusControl::mousePressEvent(QMouseEvent *event)
{
	Q_UNUSED(event);

	if ((Q_NULLPTR != m_textEdit) && (!m_textEdit->toPlainText().isEmpty())) {
		/* Only raise pop up if there is something to display. */
		raisePopup();
	}
}

/*!
 * @brief Generate problem file overview.
 *
 * @return Problem file listing.
 */
static
QString generateText(const AccountsMap *accounts,
    const QMap<AcntId, QSet<QString> > &problemDbFiles)
{
	QString text = DbStatusControl::tr("The listed database files couldn't be opened:");

	for (const AcntId &acntId : problemDbFiles.keys()) {
		text += QLatin1String("\n");
		const QSet<QString> &strSet = problemDbFiles[acntId];
		if ((Q_NULLPTR != accounts) && (accounts->keys().contains(acntId))) {
			const AcntData acntData = accounts->acntData(acntId);
			text += QString("\n%1 (%2):")
			    .arg(acntData.accountName())
			    .arg(acntData.userName());
		}

		for (const QString &str : strSet) {
			text += QLatin1String("\n    ") + QDir::toNativeSeparators(str);
		}
	}

	return text;
}

void DbStatusControl::watchAccountDataChanged(const AcntId &acntId)
{
	if (m_problemDbFiles.keys().contains(acntId)) {
		if (Q_NULLPTR != m_textEdit) {
			m_textEdit->setText(generateText(m_accounts,
			    m_problemDbFiles));
		}
	}
}

void DbStatusControl::watchOpeningFailed(const AcntId &acntId,
    const QString &fileName)
{
	bool newFile = (!m_problemDbFiles.keys().contains(acntId))
	    || (!m_problemDbFiles[acntId].contains(fileName));

	if (newFile) {
		{
			const bool hasErrors = true;

			this->setToolTip(toolTipText(m_msgDbLoc, m_acntDbLoc, hasErrors));
			if (hasErrors) {
				this->setStyleSheet("QLabel { color: red }");
			}
		}

		m_problemDbFiles[acntId].insert(fileName);

		if (Q_NULLPTR != m_textEdit) {
			m_textEdit->setText(generateText(m_accounts,
			    m_problemDbFiles));
			raisePopup();
		}
	}
}

void DbStatusControl::raisePopup(void)
{
	static int viewItemWidth = -1;
	static int viewItemHeight = -1;
	if (Q_UNLIKELY(viewItemWidth < 0)) {
		QSize sizeHint = QLabel(
		    "Quite long string used for view size estimation and a little bit more")
		        .sizeHint();
		viewItemWidth = sizeHint.width() + 40; // 40 for borders etc.
		viewItemHeight = sizeHint.height() * 12; // 12 times the height of list entry
	}

	if (Q_NULLPTR != m_dbPopup) {
		const QPoint parentTopLeft = this->mapToGlobal(QPoint(0, 0));
		const int width = viewItemWidth;
		const int height = viewItemHeight;

		m_dbPopup->setGeometry(
		    parentTopLeft.x() - width + this->width(),
		    parentTopLeft.y() - height, width, height);
		m_dbPopup->show();
		m_dbPopup->raise();
	}
}
