/*
 * Copyright (C) 2014-2024 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 <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
#  include <QStorageInfo>
#else /* < Qt-5.4 */
#  warning "Compiling against version < Qt-5.4 which does not have QStorageInfo."
#endif /* >= Qt-5.4 */
#include <QStringBuilder>

#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/identifiers/account_id.h"
#include "src/dimensions/dimensions.h"
#include "src/global.h"
#include "src/gui/dlg_convert_import_from_mobile_app.h"
#include "src/io/account_db.h"
#include "src/io/db_convert_mobile_to_desktop.h"
#include "src/io/imports.h"
#include "src/io/load_mobile_zip_package.h"
#include "src/io/message_db_set.h"
#include "src/model_interaction/account_interaction.h"
#include "src/settings/accounts.h"
#include "src/settings/preferences.h"
#include "src/settings/prefs_specific.h"
#include "src/views/table_home_end_filter.h"
#include "src/views/table_space_selection_filter.h"
#include "src/views/table_tab_ignore_filter.h"
#include "ui_dlg_convert_import_from_mobile_app.h"

#define UNZIP_WORKING_DIR_NAME "unzip_data_tmp"
#define CONVERTED_SINGLE_DB_DIR_NAME "converted_single_msg_dbs"
#define SPLIT_YEARS_DB_DIR_NAME "split_years_msg_dbs"

static const QString dlgName("convert_import_from_mobile_app");
static const QString accountTableName("account_list");

/*!
 * @brief Move message database to final destination.
 *
 * @param[in] msgDb Message database path.
 * @param[in] targetPath Target location.
 * @return Full path where message database were moved.
 */
static
QString moveMsgDb(const QString &msgDbPath, const QString &targetPath)
{
	QFile f(msgDbPath);
	QFileInfo fi(msgDbPath);
	if (Q_UNLIKELY(!f.rename(targetPath % QDir::separator() % fi.fileName()))) {
		return QString();
	}
	return f.fileName();
}

/*!
 * @brief Create subdirectory in the path.
 *
 * @param[in] sourcePath Source path.
 * @param[in] subDir Subdirectory name.
 * @return Absolute subdirectory path.
 */
static
QString createSubDirPath(const QString &sourcePath, const QString &subDir)
{
	QDir dir(sourcePath % QDir::separator() % subDir);
	dir.removeRecursively();
	if (Q_UNLIKELY((!dir.exists()) && (!dir.mkpath(".")))) {
		return QString();
	}
	return dir.absolutePath();
}

/*!
 * @brief Add accounts into the account model.
 *
 * @param[in,out] model Account model.
 * @param[in] acntIdList account id list.
 */
static
void addAccountsIntoModel(BackupSelectionModel &model,
    const QList<AcntId> &acntIdList)
{
	for (const AcntId &acntId : acntIdList) {
		model.appendData(false, QString(), acntId, QString());
	}
}

/*!
 * @brief Remove directory recursively.
 *
 * @param[in] dirPath Directory path.
 */
static inline
void removeRecursivelyDir(const QString &dirPath)
{
	QDir dir(dirPath);
	dir.removeRecursively();
}

/*!
 * @brief Check if message database exists in the target path.
 *
 * @param[in] filename Message database file name.
 * @param[in] targetPath Target location.
 * @return True if success.
 */
static
bool msgDbExists(const QString &filename, const QString &targetPath)
{
	return QFile::exists(targetPath % QDir::separator() % filename);
}

/*!
 * @brief Check if some account database exists in the application data location.
 *
 * @param[in] dbPathList List of message database paths.
 * @param[in] targetPath Path to application data location.
 * @return True if some account database with same user name exists.
 */
static
bool isSomeDbFileAvailable(const QStringList &dbPathList,
    const QString &targetPath)
{
	for (const QString &dbFile : dbPathList) {
		if (Q_UNLIKELY(msgDbExists(QFileInfo(dbFile).fileName(),
		        targetPath))) {
			return true;
		}
	}
	return false;
}

/*!
 * @brief Check available space.
 *
 * @param[in] dbPathList List of message database paths.
 * @param[in] targetPath Path to application data location.
 * @return True if success.
 */
static
bool checkAvailableSpace(const QStringList &dbPathList,
    const QString &targetPath)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
{
	qint64 dbTotalSizeBytes = 0;
	for (const QString &dbFile : dbPathList) {
		dbTotalSizeBytes += QFileInfo(dbFile).size();
	}
	return (QStorageInfo(targetPath).bytesAvailable() > dbTotalSizeBytes);
}
#else /* < Qt-5.4 */
{
	return true;
}
#endif /* >= Qt-5.4 */

/*!
 * @brief Move converted databases to application data location.
 *
 * @param[in] dbPathList List of message database paths.
 * @param[in] targetPath Path to application data location.
 * @return True if success.
 */
static
bool moveDbToAppDataLocation(const QStringList &dbPathList,
    const QString &targetPath)
{
	for (const QString &dbFile : dbPathList) {
		const QString newFilePath = moveMsgDb(dbFile, targetPath);
		if (Q_UNLIKELY(newFilePath.isEmpty())) {
			return false;
		}
	}
	return true;
}

/*!
 * @brief Create account and add into model.
 *
 * @param[in] regularAccounts Account container.
 * @param[in] acntId Account id.
 * @param[in] targetPath Path to application data location.
 * @return True if success.
 */
static
bool createAccount(AccountsMap &regularAccounts, const AcntId &acntId,
    const QString &targetPath)
{
	/* Create account and add into global account container.*/
	AcntData itemSettings;
	itemSettings.setTestAccount(acntId.testing());
	itemSettings.setAccountName(acntId.username());
	itemSettings.setUserName(acntId.username());
	itemSettings.setLoginMethod(AcntSettings::LIM_UNAME_PWD);
	itemSettings.setPassword(QString());
	itemSettings.setRememberPwd(false);
	itemSettings.setSyncWithAll(false);
	itemSettings.setDbDir(targetPath, QString());

	return (0 == regularAccounts.addAccount(itemSettings));
}

/*!
 * @brief Get list of relevant account databases.
 *
 * @param[in] acntId Account id.
 * @param[in] dbPathList List of message database paths.
 * @return List of relevant account databases.
 */
static
QStringList getAcntMsgDbs(const AcntId &acntId, const QStringList &dbPathList)
{
	QStringList msgDbs;
	for (const QString &dbPath : dbPathList) {

		const QString dbFileName(QFileInfo(dbPath).fileName());
		QString dbUserName, dbYearFlag, errMsg;
		bool dbTestingFlag;

		/* Split and check the database file name. */
		if (MessageDbSet::isValidDbFileName(dbFileName, dbUserName,
		        dbYearFlag, dbTestingFlag, errMsg)) {
			if ((acntId.username() == dbUserName) &&
			        (acntId.testing() == dbTestingFlag)) {
				msgDbs.append(dbPath);
			}
		}
	}
	return msgDbs;
}

/*!
 * @brief Import messages from converted databases into exist account.
 *
 * @param[in] acntId Account id.
 * @param[in] dbPathList List of message database paths.
 * @return True if success.
 */
static
bool importDbMsgsIntoAcnt(const AcntId &acntId,
    const QStringList &dbPathList)
{
	enum AccountInteraction::AccessStatus status =
	    AccountInteraction::AS_ERR;
	QString dbDir, namesStr;

	MessageDbSet *dbSet = AccountInteraction::accessDbSet(acntId, status,
	    dbDir, namesStr);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return false;
	}

	const QString dbId(GlobInstcs::accntDbPtr->dbId(
	    AccountDb::keyFromLogin(acntId.username())));

	Imports::importDbMsgsIntoDatabase(*dbSet, dbPathList,
	    acntId.username(), dbId);
	return true;
}

DlgConvertImportFromMobileApp::DlgConvertImportFromMobileApp(QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgConvertImportFromMobileApp),
    m_zipPath(),
    m_zfoDbPath(),
    m_msgDbPaths(),
    m_accountListProxyModel(this),
    m_dfltFilerLineStyleSheet(SortFilterProxyModel::invalidFilterEditStyle),
    m_accountSelectionModel(this)
{
	m_ui->setupUi(this);

	connect(m_ui->openZipButton, SIGNAL(clicked()),
	    this, SLOT(openZipPackage()));
	connect(m_ui->convertButton, SIGNAL(clicked()),
	    this, SLOT(convertAndSplitMsgDbs()));
	connect(m_ui->importButton, SIGNAL(clicked()),
	    this, SLOT(importDbs()));

	connect(m_ui->accountFilterLine, SIGNAL(textChanged(QString)),
	    this, SLOT(filterAccount(QString)));
	connect(m_ui->allCheckBox, SIGNAL(stateChanged(int)),
	    this, SLOT(applyAllSelection(int)));

	m_ui->zipPackageInfoText->clear();
	m_ui->acntSelectionGroupBox->setEnabled(false);
	m_ui->convertGroupBox->setEnabled(false);
	m_ui->importGroupBox->setEnabled(false);

	/* Set table view model and properties. */
	m_ui->accountTableView->setNarrowedLineHeight();
	m_ui->accountTableView->horizontalHeader()->setDefaultAlignment(
	    Qt::AlignLeft);
	m_ui->accountTableView->setSelectionMode(
	    QAbstractItemView::ExtendedSelection);
	m_ui->accountTableView->setSelectionBehavior(
	    QAbstractItemView::SelectRows);

	m_accountListProxyModel.setSourceModel(&m_accountSelectionModel);
	{
		QList<int> columnList;
		columnList.append(BackupSelectionModel::COL_USERNAME);
		m_accountListProxyModel.setFilterKeyColumns(columnList);
	}
	m_ui->accountTableView->setModel(&m_accountListProxyModel);

	m_ui->accountTableView->installEventFilter(
	    new (::std::nothrow) TableHomeEndFilter(m_ui->accountTableView));
	m_ui->accountTableView->installEventFilter(
	    new (::std::nothrow) TableSpaceSelectionFilter(
	        BackupSelectionModel::COL_MSGS_CHECKBOX, m_ui->accountTableView));
	m_ui->accountTableView->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(m_ui->accountTableView));

	m_ui->accountTableView->setSquareColumnWidth(
	    BackupSelectionModel::COL_MSGS_CHECKBOX);
	m_ui->accountTableView->setColumnHidden(
	    BackupSelectionModel::COL_ACCOUNT_NAME, true);
	m_ui->accountTableView->setColumnHidden(
	    BackupSelectionModel::COL_BOX_ID, true);
	m_ui->accountTableView->setColumnWidth(2, 200);

	connect(&m_accountSelectionModel,
	    SIGNAL(dataChanged(QModelIndex,QModelIndex)),
	    this, SLOT(modifiedAccountSelection()));
}

DlgConvertImportFromMobileApp::~DlgConvertImportFromMobileApp(void)
{
	PrefsSpecific::setDlgTblRelColWidths(*GlobInstcs::prefsPtr,
	    dlgName, accountTableName,
	    Dimensions::relativeTableColumnWidths(m_ui->accountTableView));
	PrefsSpecific::setDlgTblColSortOrder(*GlobInstcs::prefsPtr,
	    dlgName, accountTableName,
	    Dimensions::tableColumnSortOrder(m_ui->accountTableView));

	delete m_ui;
}

void DlgConvertImportFromMobileApp::view(QWidget *parent)
{
	DlgConvertImportFromMobileApp dlg(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 DlgConvertImportFromMobileApp::showEvent(QShowEvent *event)
{
	QDialog::showEvent(event);

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

void DlgConvertImportFromMobileApp::applyAllSelection(int state)
{
	m_accountSelectionModel.disconnect(
	    SIGNAL(dataChanged(QModelIndex, QModelIndex)),
	    this, SLOT(modifiedAccountSelection()));

	switch (state) {
	case Qt::Unchecked:
		m_ui->allCheckBox->setTristate(false);
		m_accountSelectionModel.setAllCheck(false);
		break;
	case Qt::PartiallyChecked:
		/* Do nothing. */
		break;
	case Qt::Checked:
		m_ui->allCheckBox->setTristate(false);
		m_accountSelectionModel.setAllCheck(true);
		break;
	default:
		Q_ASSERT(0);
		break;
	}

	connect(&m_accountSelectionModel,
	    SIGNAL(dataChanged(QModelIndex,QModelIndex)),
	    this, SLOT(modifiedAccountSelection()));

	m_ui->convertGroupBox->setEnabled(
	    m_accountSelectionModel.numChecked() > 0);
}

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

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

void DlgConvertImportFromMobileApp::modifiedAccountSelection(void)
{
	enum Qt::CheckState state = Qt::PartiallyChecked;

	if ((m_accountSelectionModel.rowCount() == 0) ||
	     (m_accountSelectionModel.numChecked()
	     == m_accountSelectionModel.rowCount())) {
		state = Qt::Checked;
	} else if (m_accountSelectionModel.numChecked() == 0) {
		state = Qt::Unchecked;
	}

	m_ui->allCheckBox->setTristate(state == Qt::PartiallyChecked);
	m_ui->allCheckBox->setCheckState(state);

	m_ui->convertGroupBox->setEnabled(
	    m_accountSelectionModel.numChecked() > 0);
}

void DlgConvertImportFromMobileApp::openZipPackage(void)
{
	/* Disable and clear some UI components and clear model. */
	m_ui->labelZipFileName->clear();
	m_ui->zipPackageInfoText->clear();
	m_ui->convertInfoLabel->clear();
	m_ui->convertResultTextEdit->clear();
	m_ui->importInfoLabel->clear();
	m_ui->allCheckBox->setChecked(false);
	m_ui->acntSelectionGroupBox->setEnabled(false);
	m_ui->convertGroupBox->setEnabled(false);
	m_ui->convertResultTextEdit->setEnabled(false);
	m_ui->importGroupBox->setEnabled(false);
	m_accountSelectionModel.removeAllRows();
	m_zfoDbPath.clear();
	m_msgDbPaths.clear();

	/* Select and open ZIP package.*/
	m_zipPath = QFileDialog::getOpenFileName(this, tr("Open ZIP File"),
	    QString(), tr("ZIP file (*.zip)"));

	if (Q_UNLIKELY(m_zipPath.isEmpty())) {
		return;
	}
	QFileInfo fi(m_zipPath);
	m_ui->labelZipFileName->setText(tr("File: %1").arg(fi.fileName()));

	/* Load content of ZIP archive and find all message databases and ZFO. */
	QStringList foundMsgDbs;
	QString backupInfoText;
	bool canUnzip = LoadMobileZipPackage::loadBackupJsonFromZip(m_zipPath,
	    foundMsgDbs, m_zfoDbPath, backupInfoText);
	m_ui->zipPackageInfoText->setText(backupInfoText);
	if (Q_UNLIKELY(!canUnzip)) {
		return;
	}

	/* Get account ids from all message databases. */
	QList<AcntId> acntIdList =
	    DbConvertMobileToDesktop::getAcntIdsFromDbName(foundMsgDbs);
	if (Q_UNLIKELY(acntIdList.isEmpty())) {
		return;
	}

	/* Fill account model. */
	addAccountsIntoModel(m_accountSelectionModel, acntIdList);
	m_ui->acntSelectionGroupBox->setEnabled(true);
}

void DlgConvertImportFromMobileApp::convertAndSplitMsgDbs(void)
{
	m_ui->convertButton->setEnabled(false);
	m_ui->convertResultTextEdit->clear();

	QString convertInfoText(tr("All message databases have been successfully converted."));
	QString unZipPath, convertedMsgDbPath, splitMsgDbPath;
	QFileInfo fi(m_zipPath);
	bool success = true;

	/* Get list of checked account ids from account model. */
	const QList<AcntId> acntIdList = m_accountSelectionModel.checkedAcntIds();
	if (Q_UNLIKELY(acntIdList.count() <= 0)) {
		convertInfoText = tr("No data boxes selected.");
		goto fail1;
	}

	/* Create temporary directory for unzip. */
	unZipPath = createSubDirPath(fi.absolutePath(),
	    UNZIP_WORKING_DIR_NAME);
	if (Q_UNLIKELY(unZipPath.isEmpty())) {
		convertInfoText =
		    tr("Cannot create temporary directory for extracted files.");
		goto fail1;
	}

	/* Unzip ZFO db, selected message databases and their ZFO files. */
	m_msgDbPaths = unZipSelectedAcnts(m_zipPath, unZipPath, acntIdList);
	if (Q_UNLIKELY(m_msgDbPaths.isEmpty())) {
		convertInfoText = tr("No file has been extracted.");
		goto fail;
	}

	/* Create temporary directory for converted databases. */
	convertedMsgDbPath = createSubDirPath(fi.absolutePath(),
	    CONVERTED_SINGLE_DB_DIR_NAME);
	if (Q_UNLIKELY(convertedMsgDbPath.isEmpty())) {
		convertInfoText =
		    tr("Cannot create temporary directory for conversion.");
		goto fail;
	}

	/* Convert databases from mobile format into desktop format. */
	if (Q_UNLIKELY(!convertSelectedMsgDbs(m_msgDbPaths, convertedMsgDbPath))) {
		convertInfoText =
		    tr("Some message databases haven't been converted successfully. See details below.");
	}
	m_ui->convertInfoLabel->setText(convertInfoText);

	/* Delete unzip working directory. */
	removeRecursivelyDir(unZipPath);

	/* Create path for split databases. */
	splitMsgDbPath = createSubDirPath(fi.absolutePath(),
	    SPLIT_YEARS_DB_DIR_NAME);
	if (Q_UNLIKELY(convertedMsgDbPath.isEmpty())) {
		convertInfoText =
		    tr("Cannot create directory in order to split database content.");
		goto fail;
	}

	/* Split converted databases by years. */
	success = splitSelectedMsgDbs(m_msgDbPaths, splitMsgDbPath);
	if (Q_UNLIKELY(!success)) {
		convertInfoText =
		    tr("Some message databases haven't been converted/split successfully. See details below.");
	}

	/* Delete converted directory. */
	removeRecursivelyDir(convertedMsgDbPath);

	/* Remember all split/unsplit databases for import. */
	{
		QStringList fileList(
		    QDir(splitMsgDbPath).entryList(QStringList("*.db"),
		    QDir::Files | QDir::NoSymLinks));
		m_msgDbPaths.clear();
		for (const QString &fileName : fileList) {
			m_msgDbPaths.append(splitMsgDbPath % QDir::separator() % fileName);
		}
	}
fail:
	/* Delete unzip working directory. */
	removeRecursivelyDir(unZipPath);
fail1:
	m_ui->convertInfoLabel->setText(convertInfoText);
	m_ui->convertButton->setEnabled(true);
	m_ui->importGroupBox->setEnabled(true);
}

void DlgConvertImportFromMobileApp::importDbs(void)
{
	m_ui->importButton->setEnabled(false);
	m_ui->convertButton->setEnabled(false);

	if (importAccountsFromDbs(m_msgDbPaths)) {
		m_ui->importInfoLabel->setText(tr("All data boxes have been imported."));
	} else {
		m_ui->importInfoLabel->setText(tr("Some data boxes haven't been imported."));
	}

	m_ui->importButton->setEnabled(true);
}

void DlgConvertImportFromMobileApp::showResults(const QStringList &errorList)
{
	m_ui->convertResultTextEdit->setEnabled(true);
	QString errList = m_ui->convertResultTextEdit->toPlainText();
	for (const QString &text : errorList) {
		errList.append(text);
		errList.append("\n");
	}
	m_ui->convertResultTextEdit->setText(errList);
}

bool DlgConvertImportFromMobileApp::convertSelectedMsgDbs(
    QStringList &msgDbPaths, const QString &destPath)
{
	/* Initialise progressbar for conversion. */
	m_ui->progressBar->setMinimum(0);
	m_ui->progressBar->setMaximum(msgDbPaths.count());
	int pbValue = m_ui->progressBar->minimum();

	/* Convert message databases. */
	QString finalDbPath;
	QStringList convertedDbPaths;
	QStringList errorList;
	bool success = true;

	for (const QString &msgDbPath : msgDbPaths) {

		QFileInfo dbFi(msgDbPath);
		m_ui->convertInfoLabel->setText(tr("Converting database '%1'...").arg(dbFi.fileName()));
		m_ui->progressBar->setValue(pbValue++);
		QCoreApplication::processEvents();

		/* Message database conversion. */
		bool ret = DbConvertMobileToDesktop::convertMsgDb(
		    msgDbPath, m_zfoDbPath, errorList);
		success = success && ret;

		/* Move converted database file into final destination. */
		finalDbPath = moveMsgDb(msgDbPath, destPath);
		if (Q_UNLIKELY(finalDbPath.isEmpty())) {
			errorList.append(tr("Cannot move converted message database file '%1' to '%2'.").arg(dbFi.fileName()).arg(destPath));
			success = false;
			continue;
		}
		convertedDbPaths.append(finalDbPath);
	}
	m_ui->progressBar->setValue(0);

	showResults(errorList);

	msgDbPaths = macroStdMove(convertedDbPaths);

	return success;
}

bool DlgConvertImportFromMobileApp::splitSelectedMsgDbs(
    const QStringList &msgDbPaths, const QString &destPath)
{
	/* Initialise progressbar for database split. */
	m_ui->progressBar->setMinimum(0);
	m_ui->progressBar->setMaximum(msgDbPaths.count());
	int pbValue = m_ui->progressBar->minimum();

	/* Convert message databases and move it into converted file location. */
	QStringList errorList;
	bool success = true;
	for (const QString &msgDbPath : msgDbPaths) {

		QFileInfo dbFi(msgDbPath);
		m_ui->convertInfoLabel->setText(tr("Splitting database '%1' according to years...").arg(dbFi.fileName()));
		m_ui->progressBar->setValue(pbValue++);
		QCoreApplication::processEvents();

		/* Database split. */
		if (Q_UNLIKELY(!DbConvertMobileToDesktop::splitDbByYears(
		        msgDbPath, destPath))) {
			errorList.append(
			    tr("Cannot split converted message database file '%1' according to years.").arg(dbFi.fileName()));
			moveMsgDb(msgDbPath, destPath);
			success = false;
		} else {
			QFile::remove(msgDbPath);
		}
	}
	m_ui->progressBar->setValue(0);

	errorList.append(tr("Converted message databases are located in the following location: '%1'.").arg(destPath));

	showResults(errorList);

	return success;
}

QStringList DlgConvertImportFromMobileApp::unZipSelectedAcnts(
    const QString &zipPath, const QString &unZipPath,
    const QList<AcntId> &selectedAcnts)
{
	if (Q_UNLIKELY(zipPath.isEmpty() || unZipPath.isEmpty())) {
		return QStringList();
	}

	/* Set progressbar to infinite loop. */
	m_ui->progressBar->setMinimum(0);
	m_ui->progressBar->setMaximum(0);
	m_ui->progressBar->setValue(0);
	m_ui->convertInfoLabel->setText(tr("Unpacking archive: %1").arg(QFileInfo(zipPath).fileName()));
	QCoreApplication::processEvents();

	/* Unzip ZFO db, selected message databases and their ZFO files. */
	m_zfoDbPath.clear();
	QStringList msgDbPaths;
	QString unZipInfoText(tr("All files have been unpacked successfully."));
	if (Q_UNLIKELY(!LoadMobileZipPackage::unZipSelectedFilesFromZiP(zipPath,
	    unZipPath, selectedAcnts, msgDbPaths, m_zfoDbPath))) {
		unZipInfoText = tr("No file has been unpacked.");
	}

	/* Disable infinite loop of progressbar. */
	m_ui->progressBar->setMaximum(1);
	m_ui->convertInfoLabel->setText(unZipInfoText);

	return msgDbPaths;
}

bool DlgConvertImportFromMobileApp::importAccountsFromDbs(
    const QStringList &dbPathList)
{
	if (Q_UNLIKELY(dbPathList.isEmpty() ||
	        GlobInstcs::acntMapPtr == Q_NULLPTR)) {
		return false;
	}

	const QList<AcntId> acntIdList =
	    DbConvertMobileToDesktop::getAcntIdsFromDbName(dbPathList);
	bool success = true;

	/* Create account or import message for all account ids. */
	for (const AcntId &acntId : acntIdList) {

		QStringList dbs = getAcntMsgDbs(acntId, dbPathList);

		/* Check whether account already exists. */
		if (GlobInstcs::acntMapPtr->acntIds().contains(acntId)) {
			QMessageBox msgBox(this);
			msgBox.setIcon(QMessageBox::Question);
			msgBox.setWindowTitle(tr("Import data box: %1").arg(acntId.username()));
			msgBox.setText(tr("Data box with username '%1' already exists.").arg(acntId.username()));
			msgBox.setInformativeText(tr("Do you want to import messages from converted database into the data box?"));
			msgBox.setStandardButtons(QMessageBox::Yes|QMessageBox::No);
			msgBox.setDefaultButton(QMessageBox::No);
			if (msgBox.exec() == QMessageBox::No) {
				continue;
			}
			if (Q_UNLIKELY(!importDbMsgsIntoAcnt(acntId, dbs))) {
				success = false;
			}

		} else {

			QString text(tr("Data box with name '%1' has been created (username '%1'). The database files have been set as the actual message databases for this data box.").arg(acntId.username()));
			QString errText;
			if (Q_UNLIKELY(!createAccountFromDb(acntId, dbs, errText))) {
				text = tr("Data box with username '%1' hasn't been created.").arg(acntId.username());
				success = false;
			}
			QMessageBox msgBox(this);
			msgBox.setIcon(QMessageBox::Information);
			msgBox.setWindowTitle(tr("Create data box: %1").arg(acntId.username()));
			msgBox.setText(text);
			msgBox.setInformativeText(errText);
			msgBox.exec();
		}
	}

	return success;
}

bool DlgConvertImportFromMobileApp::createAccountFromDb(const AcntId &acntId,
    const QStringList &dbPathList, QString &errTxt)
{
	const QString appDataLocation(GlobInstcs::iniPrefsPtr->confDir());

	/* Check if some database with username exists in the target location. */
	if (Q_UNLIKELY(isSomeDbFileAvailable(dbPathList, appDataLocation))) {
		errTxt = tr("Some message databases with username '%1' already exist in the target location '%2'. Move or delete them and try importing the data again.").arg(acntId.username()).arg(appDataLocation);
		return false;
	}

	/* Check available space for databases moving. */
	if (Q_UNLIKELY(!checkAvailableSpace(dbPathList, appDataLocation))) {
		errTxt = tr("No free space to move databases into the target location '%1'.").arg(appDataLocation);
		return false;
	}

	/* Move databases to application data location. */
	if (Q_UNLIKELY(!moveDbToAppDataLocation(dbPathList, appDataLocation))) {
		errTxt = tr("Cannot move databases into the target location '%1'.").arg(appDataLocation);
		return false;
	}

	/* Create and add account into account map. */
	if (Q_UNLIKELY(!createAccount(*GlobInstcs::acntMapPtr,
	        acntId, appDataLocation))) {
		errTxt = tr("Cannot add data box into application.");
		return false;
	}

	errTxt = tr("You'll have to modify the data box properties in order to log in to the ISDS server correctly.");
	return true;
}
