/*
 * 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.
 */

#ifndef WIN32
#include <clocale>
#endif /* !WIN32 */
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcessEnvironment>
#include <QSysInfo>
#include <QTimeZone>
#include <QTranslator>

#include "src/cli/cli_parser.h"
#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/crypto/crypto_funcs.h"
#include "src/datovka_shared/crypto/crypto_trusted_certs.h"
#include "src/datovka_shared/identifiers/account_id.h"
#include "src/datovka_shared/io/prefs_db.h"
#include "src/datovka_shared/io/records_management_db.h"
#include "src/datovka_shared/json/db_info.h"
#if defined(ENABLE_DRAFTS)
#  include "src/datovka_shared/io/draft_db.h"
#endif /* defined(ENABLE_DRAFTS) */
#include "src/datovka_shared/localisation/localisation.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/pin.h"
#include "src/datovka_shared/settings/prefs.h"
#include "src/datovka_shared/settings/records_management.h"
#include "src/datovka_shared/worker/pool.h"
#include "src/global.h"
#include "src/identifiers/account_id_db.h"
#include "src/identifiers/composite.h"
#include "src/identifiers/message_id.h"
#include "src/initialisation.h"
#include "src/io/account_db.h"
#include "src/io/isds_sessions.h"
#include "src/io/file_downloader.h"
#include "src/io/filesystem.h"
#include "src/io/message_db.h"
#include "src/io/message_db_set_container.h"
#if defined(ENABLE_TAGS)
#  include "src/io/tag_container.h"
#  include "src/io/tag_db.h"
#endif /* defined(ENABLE_TAGS) */
#if defined(ENABLE_TIMESTAMP_TRACKING)
#  include "src/io/timestamp_db.h"
#endif /* defined(ENABLE_TIMESTAMP_TRACKING) */
#include "src/isds/qt_signal_slot.h"
#include "src/settings/accounts.h"
#include "src/settings/preferences.h"
#include "src/settings/prefs_specific.h"
#include "src/settings/proxy.h"
#if defined(Q_OS_WIN)
#  include "src/settings/registry.h"
#endif /* Q_OS_WIN */
#include "src/settings/tag_container_settings.h"
#include "src/single/single_instance.h"
#include "src/worker/message_emitter.h"

void setDefaultLocale(void)
{
#ifndef WIN32
	QString lang(QProcessEnvironment::systemEnvironment().value("LANG"));
	if (lang.isEmpty() || ("c" == lang.toLower())) {
#define LANG_DEF "en_GB.UTF-8"
		logWarning(
		    "The LANG environment variable is missing or unset. Setting to '%s'.\n",
		    LANG_DEF);
		if (Q_UNLIKELY(NULL == setlocale(LC_ALL, LANG_DEF))) {
			logErrorNL("Setting locale to '%s' failed.", LANG_DEF);
		}
#undef LANG_DEF
	}
#endif /* !WIN32 */
}

int preferencesSetUpRegistry(INIPreferences &iniPrefs)
{
#if defined(Q_OS_WIN) && !defined(PORTABLE_APPLICATION)
	/*
	 * Portable application doesn't load directory settings from registry.
	 */
	QString str;

	if (RegPreferences::haveEntry(RegPreferences::LOC_POL, RegPreferences::ENTR_CONF_BASEDIR)) {
		str = RegPreferences::confBaseDir(RegPreferences::LOC_POL);
	} else if (RegPreferences::haveEntry(RegPreferences::LOC_SYS, RegPreferences::ENTR_CONF_BASEDIR)) {
		str = RegPreferences::confBaseDir(RegPreferences::LOC_SYS);
	} else if (RegPreferences::haveEntry(RegPreferences::LOC_USR, RegPreferences::ENTR_CONF_BASEDIR)) {
		str = RegPreferences::confBaseDir(RegPreferences::LOC_USR);
	}
	if (!str.isEmpty()) {
		iniPrefs._confBaseDir = macroStdMove(str);
		str.clear();
	}

	if (RegPreferences::haveEntry(RegPreferences::LOC_POL, RegPreferences::ENTR_CONF_SUBDIR)) {
		str = RegPreferences::confSubdir(RegPreferences::LOC_POL);
	} else if (RegPreferences::haveEntry(RegPreferences::LOC_SYS, RegPreferences::ENTR_CONF_SUBDIR)) {
		str = RegPreferences::confSubdir(RegPreferences::LOC_SYS);
	} else if (RegPreferences::haveEntry(RegPreferences::LOC_USR, RegPreferences::ENTR_CONF_SUBDIR)) {
		str = RegPreferences::confSubdir(RegPreferences::LOC_USR);
	}
	if (!str.isEmpty()) {
		iniPrefs._confSubdir = macroStdMove(str);
		str.clear();
	}

	if (RegPreferences::haveEntry(RegPreferences::LOC_POL, RegPreferences::ENTR_CONF_DIR)) {
		str = RegPreferences::confDir(RegPreferences::LOC_POL);
	} else if (RegPreferences::haveEntry(RegPreferences::LOC_SYS, RegPreferences::ENTR_CONF_DIR)) {
		str = RegPreferences::confDir(RegPreferences::LOC_SYS);
	} else if (RegPreferences::haveEntry(RegPreferences::LOC_USR, RegPreferences::ENTR_CONF_DIR)) {
		str = RegPreferences::confDir(RegPreferences::LOC_USR);
	}
	if (!str.isEmpty()) {
		iniPrefs._confDir = macroStdMove(str);
		str.clear();
	}

#else /* !Q_OS_WIN || PORTABLE_APPLICATION */
	Q_UNUSED(iniPrefs);
#endif /* Q_OS_WIN && !PORTABLE_APPLICATION */

	return 0;
}

int preferencesSetUp(const QCommandLineParser &parser, INIPreferences &iniPrefs,
    LogDevice &log)
{
	int logFileId = -1;

	if (parser.isSet(CONF_BASEDIR_OPT)) {
		iniPrefs._confBaseDir = parser.value(CONF_BASEDIR_OPT);
	}
	if (parser.isSet(CONF_SUBDIR_OPT)) {
		iniPrefs._confSubdir = parser.value(CONF_SUBDIR_OPT);
	}
	if (parser.isSet(CONF_DIR_OPT)) {
		iniPrefs._confDir = parser.value(CONF_DIR_OPT);
	}
	if (parser.isSet(LOAD_CONF_OPT)) {
		iniPrefs._loadFromConf = parser.value(LOAD_CONF_OPT);
	}
	if (parser.isSet(SAVE_CONF_OPT)) {
		iniPrefs._saveToConf = parser.value(SAVE_CONF_OPT);
	}
	if (parser.isSet(LOG_FILE)) {
		QString logFileName = parser.value(LOG_FILE);
		logFileId = log.openFile(logFileName.toUtf8().constData(),
		    LogDevice::LM_APPEND);
		if (Q_UNLIKELY(-1 == logFileId)) {
			logErrorNL("Cannot open log file '%s'.",
			    logFileName.toUtf8().constData());
			return -1;
		}
		/* Log warnings. */
		log.setLogLevelBits(logFileId, LOGSRC_ANY,
		    LOG_UPTO(LOG_WARNING));
	}
#ifdef DEBUG
	if (parser.isSet(DEBUG_OPT) || parser.isSet(DEBUG_VERBOSITY_OPT)) {
		log.setLogLevelBits(LogDevice::LF_STDERR, LOGSRC_ANY,
		    LOG_UPTO(LOG_DEBUG));
		if (-1 != logFileId) {
			log.setLogLevelBits(logFileId, LOGSRC_ANY,
			    LOG_UPTO(LOG_DEBUG));
		}
	}
	if (parser.isSet(DEBUG_VERBOSITY_OPT)) {
		bool ok;
		int value = parser.value(DEBUG_VERBOSITY_OPT).toInt(&ok, 10);
		if (Q_UNLIKELY(!ok)) {
			logErrorNL("%s", "Invalid debug-verbosity parameter.");
			exit(EXIT_FAILURE);
		}
		logInfoNL("Setting debugging verbosity to value '%d'.", value);
		log.setDebugVerbosity(value);
	}
#endif /* DEBUG */
	if (parser.isSet(LOG_VERBOSITY_OPT)) {
		bool ok;
		int value = parser.value(LOG_VERBOSITY_OPT).toInt(&ok, 10);
		if (Q_UNLIKELY(!ok)) {
			logErrorNL("%s", "Invalid log-verbosity parameter.");
			exit(EXIT_FAILURE);
		}
		logInfoNL("Setting logging verbosity to value '%d'.", value);
		log.setLogVerbosity(value);
	}

	return 0;
}

int preferencesOk(const INIPreferences &iniPrefs, QString &errorMessage)
{
	/*
	 * There is no need to call tr() or use QT_TRANSLATE_NOOP() as the
	 * localisation isn't loaded when this function is called.
	 */

	if (Q_UNLIKELY((!iniPrefs._confBaseDir.isEmpty())
	        && (!QDir::isAbsolutePath(iniPrefs._confBaseDir)))) {
		errorMessage = QString("%1: Configuration base directory cannot be set as a relative path '%2'.")
		    .arg(CONF_BASEDIR_OPT)
		    .arg(iniPrefs._confBaseDir);
		return -1;
	}
	if (Q_UNLIKELY((!iniPrefs._confSubdir.isEmpty())
	        && QDir::isAbsolutePath(iniPrefs._confSubdir))) {
		errorMessage = QString("%1: Configuration subdirectory cannot be set as an absolute path '%2'.")
		    .arg(CONF_SUBDIR_OPT)
		    .arg(iniPrefs._confSubdir);
		return -1;
	}
	if (Q_UNLIKELY((!iniPrefs._confDir.isEmpty())
	        && (!QDir::isAbsolutePath(iniPrefs._confDir)))) {
		errorMessage = QString("%1: Configuration directory cannot be set as a relative path '%2'.")
		    .arg(CONF_DIR_OPT)
		    .arg(iniPrefs._confDir);
		return -1;
	}
	if (Q_UNLIKELY((!iniPrefs._confDir.isEmpty())
	        && ((!iniPrefs._confBaseDir.isEmpty()) || (!iniPrefs._confSubdir.isEmpty())))) {
		errorMessage = QString("%1: Configuration directory cannot be set when '%2' or '%3' is set.")
		    .arg(CONF_DIR_OPT)
		    .arg(CONF_BASEDIR_OPT)
		    .arg(CONF_SUBDIR_OPT);
		return -1;
	}

	return 0;
}

void downloadCRL(void)
{
	/* Start downloading the CRL files. */
	QList<QUrl> urlList;
	FileDownloader fDown(true);
	const struct crl_location *crl = crl_locations;
	const char **url;
	while ((NULL != crl) && (NULL != crl->file_name)) {
		urlList.clear();

		url = crl->urls;
		while ((NULL != url) && (NULL != *url)) {
			urlList.append(QUrl(*url));
			++url;
		}

		QByteArray data = fDown.download(urlList, 2000);
		if (!data.isEmpty()) {
			if (0 != crypto_add_crl(data.data(),
			        data.size())) {
				logWarningNL(
				    "Couldn't load downloaded CRL file '%s'.",
				    crl->file_name);
			} else {
				logInfoNL("Loaded CRL file '%s'.",
				    crl->file_name);
			}
		} else {
			logWarningNL("Couldn't download CRL file '%s'.",
			    crl->file_name);
		}

		++crl;
	}
}

/*!
 * @brief Loads application localisation depending on \a language.
 *
 * @param[in] language Language string.
 */
static
void loadLocalisation(const QString &language)
{
	static QTranslator qtTranslator, appTranslator;

	/* Check for application localisation location. */
	QString localisationDir;
	QString localisationFile;

	localisationDir = appLocalisationDir();

	logInfoNL("Loading application localisation from path '%s'.",
	    localisationDir.toUtf8().constData());

	localisationFile = "datovka_" + Localisation::shortLangName(language);

	Localisation::setProgramLocale(language);

	if (!appTranslator.load(localisationFile, localisationDir)) {
		logWarningNL(
		    "Could not load localisation file '%s' from directory '%s'.",
		    localisationFile.toUtf8().constData(),
		    localisationDir.toUtf8().constData());
	}

	QCoreApplication::installTranslator(&appTranslator);

	localisationDir = qtLocalisationDir();

	logInfoNL("Loading Qt localisation from path '%s'.",
	    localisationDir.toUtf8().constData());

	{
		const QString langName(Localisation::shortLangName(language));
		if (langName != Localisation::langEn) {
			localisationFile =
			    "qtbase_" + Localisation::shortLangName(language);
		} else {
			localisationFile = QString();
		}
	}

	if (!localisationFile.isEmpty() &&
	    !qtTranslator.load(localisationFile, localisationDir)) {
		logWarningNL(
		    "Could not load localisation file '%s' from directory '%s'.",
		    localisationFile.toUtf8().constData(),
		    localisationDir.toUtf8().constData());
	}

	QCoreApplication::installTranslator(&qtTranslator);
}

void loadLocalisationSystem(void)
{
	loadLocalisation(Localisation::langSystem);
}

void loadLocalisationPrefs(const Prefs &prefs)
{
	QString language;
	{
		QString val;
		if (prefs.strVal("translation.language", val)) {
			if (!val.isEmpty()) {
				language = macroStdMove(val);
			}
		}
	}

	loadLocalisation(language);
}

void logAppVersion(void)
{
	/* Crude architecture bit width check. */
	logInfoNL("Application version: %s (%u-bit).", VERSION,
	    (unsigned int)(sizeof(void *) * 8));
}

void logEnvironment(void)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
	logInfoNL("Compile-time target architecture: %s.", QSysInfo::buildAbi().toUtf8().constData());
	logInfoNL("Compile-time CPU architecture: %s.", QSysInfo::buildCpuArchitecture().toUtf8().constData());
	logInfoNL("Run-time CPU architecture: %s.", QSysInfo::currentCpuArchitecture().toUtf8().constData());
	logInfoNL("Running on '%s' version '%s'.", QSysInfo::productType().toUtf8().constData(), QSysInfo::productVersion().toUtf8().constData());
#endif /* >= Qt-5.4 */
}

void logQtVersion(void)
{
	logInfoNL("Compile-time Qt version: 0x%x (%s).", QT_VERSION, QT_VERSION_STR);
	logInfoNL("Run-time Qt version: %s.", qVersion());
}

void logTimeZone(void)
{
	const QTimeZone sysZone(
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
	    QTimeZone::systemTimeZone()
#else /* < Qt-5.5 */
	    QTimeZone::systemTimeZoneId()
#endif /* >= Qt-5.5 */
	);
	int offset = sysZone.offsetFromUtc(QDateTime::currentDateTime());
	logInfoNL("Local time zone is %s (UTC%+ds).", sysZone.id().constData(),
	    offset);
}

void declareTypes(void)
{
	AcntId::declareTypes();
	AcntIdDb::declareTypes();
	Identifiers::declareTypes();
	Isds::declareTypes();
	MessageDb::declareTypes();
	MsgId::declareTypes();
#if defined(ENABLE_TIMESTAMP_TRACKING)
	TstValidityHash::declareTypes();
	TstEntry::declareTypes();
#endif /* defined(ENABLE_TIMESTAMP_TRACKING) */
}

int allocGlobLog(void)
{
	GlobInstcs::logPtr = new (::std::nothrow) LogDevice;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::logPtr)) {
		return -1;
	}

	return 0;
}

void deallocGlobLog(void)
{
	if (Q_NULLPTR != GlobInstcs::logPtr) {
		delete GlobInstcs::logPtr;
		GlobInstcs::logPtr = Q_NULLPTR;
	}
}

int allocGlobInfrastruct(void)
{
	GlobInstcs::snglInstEmitterPtr =
	    new (::std::nothrow) SingleInstanceEmitter;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::snglInstEmitterPtr)) {
		logErrorNL("%s",
		    "Cannot allocate single instance message emitter.");
		goto fail;
	}

	GlobInstcs::msgProcEmitterPtr =
	    new (::std::nothrow) MessageProcessingEmitter;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::msgProcEmitterPtr)) {
		logErrorNL("%s", "Cannot allocate task message emitter." );
		goto fail;
	}

	/*
	 * Only one worker thread currently.
	 * TODO -- To be able to run multiple threads in the pool a locking
	 * mechanism over isds context structures must be implemented. Also,
	 * per-context queueing ought to be implemented to avoid unnecessary
	 * waiting.
	 */
	GlobInstcs::workPoolPtr = new (::std::nothrow) WorkerPool(1);
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::workPoolPtr)) {
		logErrorNL("%s", "Cannot allocate worker pool.");
		goto fail;
	}

	return 0;

fail:
	deallocGlobInfrastruct();
	return -1;
}

void deallocGlobInfrastruct(void)
{
	if (Q_NULLPTR != GlobInstcs::workPoolPtr) {
		delete GlobInstcs::workPoolPtr;
		GlobInstcs::workPoolPtr = Q_NULLPTR;
	}
	if (Q_NULLPTR != GlobInstcs::msgProcEmitterPtr) {
		delete GlobInstcs::msgProcEmitterPtr;
		GlobInstcs::msgProcEmitterPtr = Q_NULLPTR;
	}

	if (Q_NULLPTR != GlobInstcs::snglInstEmitterPtr) {
		delete GlobInstcs::snglInstEmitterPtr;
		GlobInstcs::snglInstEmitterPtr = Q_NULLPTR;
	}
}

int allocGlobSettings(void)
{
	GlobInstcs::iniPrefsPtr = new (::std::nothrow) INIPreferences;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::iniPrefsPtr)) {
		logErrorNL("%s", "Cannot allocate INI preferences.");
		goto fail;
	}

	GlobInstcs::proxSetPtr = new (::std::nothrow) ProxiesSettings;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::proxSetPtr)) {
		logErrorNL("%s", "Cannot allocate proxy settings.");
		goto fail;
	}

	GlobInstcs::pinSetPtr = new (::std::nothrow) PinSettings;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::pinSetPtr)) {
		logErrorNL("%s", "Cannot allocated PIN settings.");
		goto fail;
	}

	GlobInstcs::recMgmtSetPtr =
	    new (::std::nothrow) RecordsManagementSettings;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::recMgmtSetPtr)) {
		logErrorNL("%s",
		    "Cannot allocate records management settings.");
		goto fail;
	}

	GlobInstcs::prefsPtr = new (::std::nothrow) Prefs;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::prefsPtr)) {
		logErrorNL("%s", "Cannot allocate database preferences.");
		goto fail;
	}

	return 0;

fail:
	deallocGlobSettings();
	return -1;
}

void deallocGlobSettings(void)
{
	if (Q_NULLPTR != GlobInstcs::recMgmtSetPtr) {
		delete GlobInstcs::recMgmtSetPtr;
		GlobInstcs::recMgmtSetPtr = Q_NULLPTR;
	}
	if (Q_NULLPTR != GlobInstcs::pinSetPtr) {
		delete GlobInstcs::pinSetPtr;
		GlobInstcs::pinSetPtr = Q_NULLPTR;
	}
	if (Q_NULLPTR != GlobInstcs::proxSetPtr) {
		delete GlobInstcs::proxSetPtr;
		GlobInstcs::proxSetPtr = Q_NULLPTR;
	}
	if (Q_NULLPTR != GlobInstcs::iniPrefsPtr) {
		delete GlobInstcs::iniPrefsPtr;
		GlobInstcs::iniPrefsPtr = Q_NULLPTR;
	}
}

int allocGlobPrefs(const INIPreferences &iniPrefs)
{
	SQLiteDb::OpenFlags flags = SQLiteDb::NO_OPTIONS;

	GlobInstcs::prefsDbPtr = new (::std::nothrow) PrefsDb("prefsDb", false);
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::prefsDbPtr)) {
		logErrorNL("%s", "Cannot allocate preference db.");
		goto fail;
	}
	/* Open preferences database. */
	flags = SQLiteDb::CREATE_MISSING;
	if (Q_UNLIKELY(!GlobInstcs::prefsDbPtr->openDb(iniPrefs.prefsDbPath(), flags))) {
		logErrorNL("Error opening preference db '%s'.",
		    iniPrefs.prefsDbPath().toUtf8().constData());
		goto fail;
	}

	return 0;

fail:
	deallocGlobPrefs();
	return -1;
}

void deallocGlobPrefs(void)
{
	if (Q_NULLPTR != GlobInstcs::prefsDbPtr) {
		delete GlobInstcs::prefsDbPtr;
		GlobInstcs::prefsDbPtr = Q_NULLPTR;
	}
}

#if defined(ENABLE_TIMESTAMP_TRACKING)
int allocGlobTimestamps(const INIPreferences &iniPrefs)
{
	SQLiteDb::OpenFlags flags = SQLiteDb::NO_OPTIONS;

	GlobInstcs::timestampDbPtr = new (::std::nothrow) TimestampDb("timestampDb", false);
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::timestampDbPtr)) {
		logErrorNL("%s", "Cannot allocate timestamp db.");
		goto fail;
	}
	/* Open timestamp database. */
	flags = SQLiteDb::CREATE_MISSING;
	if (Q_UNLIKELY(!GlobInstcs::timestampDbPtr->openDb(iniPrefs.timestampDbPath(), flags))) {
		logErrorNL("Error opening timestamp db '%s'.",
		    iniPrefs.timestampDbPath().toUtf8().constData());
		goto fail;
	}

	return 0;

fail:
	deallocGlobTimestamps();
	return -1;
}

void deallocGlobTimestamps(void)
{
	if (Q_NULLPTR != GlobInstcs::timestampDbPtr) {
		delete GlobInstcs::timestampDbPtr;
		GlobInstcs::timestampDbPtr = Q_NULLPTR;
	}
}
#endif /* defined(ENABLE_TIMESTAMP_TRACKING) */

#if defined(ENABLE_TAGS)
void createBackupOfOldFormatData(const INIPreferences &iniPrefs)
{
	bool backUp = false;
	const QString tagDbPath = iniPrefs.tagDbPath();

	{
		if (Q_UNLIKELY(!QFileInfo(tagDbPath).exists())) {
			return;
		}
		TagDb tagDb("tagDb", false);
		if (Q_UNLIKELY(!tagDb.openDb(tagDbPath, SQLiteDb::NO_OPTIONS))) {
			return;
		}
		Json::DbInfo info;
		tagDb.getDbInfo(info);
		backUp = info.formatVersionMajor() < 1;
	}

	if (backUp) {
		const QString target =
		    iniPrefs.confDir() + QDir::separator() + "_tag_v0_bak.db";
		bool ret = QFile::copy(tagDbPath, target);
		if (Q_UNLIKELY(!ret)) {
			logErrorNL("Cannot copy file from '%s' to '%s'.",
			    tagDbPath.toUtf8().constData(),
			    target.toUtf8().constData());
		}
	}
}
#endif /* defined(ENABLE_TAGS) */

enum InitialisationError allocGlobContainers(const Prefs &prefs,
    const INIPreferences &iniPrefs, QString *problemPathPtr)
{
	SQLiteDb::OpenFlags flags = SQLiteDb::NO_OPTIONS;
	QString problemPath;
	enum InitialisationError retCode = SUCCESS;

	/*
	 * These objects cannot be globally accessible static objects.
	 * The unpredictable order of constructing and destructing these
	 * objects causes segmentation faults upon their destruction.
	 *
	 * TODO -- Solve the problem of this globally accessible structures.
	 */

	GlobInstcs::isdsSessionsPtr = new (::std::nothrow) IsdsSessions;
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::isdsSessionsPtr)) {
		logErrorNL("%s", "Cannot allocate session container.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}

	GlobInstcs::accntDbPtr = new (::std::nothrow) AccountDb("accountDb", false);
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::accntDbPtr)) {
		logErrorNL("%s", "Cannot allocate account db.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}
	/* Open accounts database. */
	flags = SQLiteDb::CREATE_MISSING;
	flags |= PrefsSpecific::accountDbOnDisk(prefs) ?
	    SQLiteDb::NO_OPTIONS : SQLiteDb::FORCE_IN_MEMORY;
	if (Q_UNLIKELY(!GlobInstcs::accntDbPtr->openDb(iniPrefs.acntDbPath(), flags))) {
		logErrorNL("Error opening account db '%s'.",
		    iniPrefs.acntDbPath().toUtf8().constData());
		problemPath = iniPrefs.acntDbPath();
		retCode = ERR_OPEN_FILES;
		goto fail;
	}

	/* Create message DB container. */
	GlobInstcs::msgDbsPtr = new (::std::nothrow) DbContainer(
	    PrefsSpecific::messageDbOnDisk(prefs), "GLOBALDBS");
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::msgDbsPtr)) {
		logErrorNL("%s", "Cannot allocate message db container.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}

#if defined(ENABLE_TAGS)
	GlobInstcs::tagContSetPtr = new (::std::nothrow) TagContainerSettings();
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::tagContSetPtr)) {
		logErrorNL("%s", "Cannot allocate tag container settings.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}
	GlobInstcs::tagContPtr = new (::std::nothrow) TagContainer(TagContainer::BACK_DB);
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::tagContPtr)) {
		logErrorNL("%s", "Cannot allocate tag container.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}
	/* Check tag database backend presence. */
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::tagContPtr->backendDb())) {
		logErrorNL("%s", "Cannot allocate tag db.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}
	/* Open tags database. */
	flags = SQLiteDb::CREATE_MISSING;
	if (Q_UNLIKELY(!GlobInstcs::tagContPtr->backendDb()->openDb(iniPrefs.tagDbPath(), flags))) {
		logErrorNL("Error opening tag db '%s'.",
		    iniPrefs.tagDbPath().toUtf8().constData());
		problemPath = iniPrefs.tagDbPath();
		retCode = ERR_OPEN_FILES;
		goto fail;
	}
	/* Check tag client backend presence. */
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::tagContPtr->backendClient())) {
		logErrorNL("%s", "Cannot allocate tag client.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}
#endif /* defined(ENABLE_TAGS) */

	GlobInstcs::recMgmtDbPtr = new (::std::nothrow)
	    RecordsManagementDb("recordsManagementDb", false);
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::recMgmtDbPtr)) {
		logErrorNL("%s", "Cannot allocate records management db.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}
	/* Open records management database. */
	flags = SQLiteDb::CREATE_MISSING;
	if (Q_UNLIKELY(!GlobInstcs::recMgmtDbPtr->openDb(iniPrefs.recMgmtDbPath(), flags))) {
		logErrorNL("Error opening records management db '%s'.",
		    iniPrefs.recMgmtDbPath().toUtf8().constData());
		problemPath = iniPrefs.recMgmtDbPath();
		retCode = ERR_OPEN_FILES;
		goto fail;
	}

#if defined(ENABLE_DRAFTS)
	GlobInstcs::draftDbPtr = new (::std::nothrow) DraftDb("draftDb", false);
	if (Q_UNLIKELY(GlobInstcs::draftDbPtr == Q_NULLPTR)) {
		logErrorNL("%s", "Cannot allocate draft db.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}
	/* Open draft database. */
	flags = SQLiteDb::CREATE_MISSING;
	if (Q_UNLIKELY(!GlobInstcs::draftDbPtr->openDb(iniPrefs.draftDbPath(), flags))) {
		logErrorNL("Error opening draft db '%s'.",
		    iniPrefs.draftDbPath().toUtf8().constData());
		problemPath = iniPrefs.draftDbPath();
		retCode = ERR_OPEN_FILES;
		goto fail;
	}
#endif /* defined(ENABLE_DRAFTS) */

	GlobInstcs::acntMapPtr = new (::std::nothrow) AccountsMap(
	    AcntContainer::CT_REGULAR);
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::acntMapPtr)) {
		logErrorNL("%s", "Cannot allocate account container.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}

	GlobInstcs::shadowAcntsPtr = new (::std::nothrow) AccountsMap(
	    AcntContainer::CT_SHADOW);
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::shadowAcntsPtr)) {
		logErrorNL("%s", "Cannot allocate shadow account container.");
		retCode = ERR_ALLOC_RESOURCES;
		goto fail;
	}

	return SUCCESS;

fail:
	deallocGlobContainers();
	if (Q_NULLPTR != problemPathPtr) {
		*problemPathPtr = macroStdMove(problemPath);
	}
	return retCode;
}

void deallocGlobContainers(void)
{
	if (Q_NULLPTR != GlobInstcs::shadowAcntsPtr) {
		delete GlobInstcs::shadowAcntsPtr;
		GlobInstcs::shadowAcntsPtr = Q_NULLPTR;
	}

	if (Q_NULLPTR != GlobInstcs::acntMapPtr) {
		delete GlobInstcs::acntMapPtr;
		GlobInstcs::acntMapPtr = Q_NULLPTR;
	}

#if defined(ENABLE_DRAFTS)
	if (Q_NULLPTR != GlobInstcs::draftDbPtr) {
		delete GlobInstcs::draftDbPtr;
		GlobInstcs::draftDbPtr = Q_NULLPTR;
	}
#endif /* defined(ENABLE_DRAFTS) */

	if (Q_NULLPTR != GlobInstcs::recMgmtDbPtr) {
		delete GlobInstcs::recMgmtDbPtr;
		GlobInstcs::recMgmtDbPtr = Q_NULLPTR;
	}
#if defined(ENABLE_TAGS)
	if (Q_NULLPTR != GlobInstcs::tagContPtr) {
		delete GlobInstcs::tagContPtr;
		GlobInstcs::tagContPtr = Q_NULLPTR;
	}
	if (Q_NULLPTR != GlobInstcs::tagContSetPtr) {
		delete GlobInstcs::tagContSetPtr;
		GlobInstcs::tagContSetPtr = Q_NULLPTR;
	}
#endif /* defined(ENABLE_TAGS) */
	if (Q_NULLPTR != GlobInstcs::msgDbsPtr) {
		delete GlobInstcs::msgDbsPtr;
		GlobInstcs::msgDbsPtr = Q_NULLPTR;
	}
	if (Q_NULLPTR != GlobInstcs::accntDbPtr) {
		delete GlobInstcs::accntDbPtr;
		GlobInstcs::accntDbPtr = Q_NULLPTR;
	}

	if (Q_NULLPTR != GlobInstcs::isdsSessionsPtr) {
		delete GlobInstcs::isdsSessionsPtr;
		GlobInstcs::isdsSessionsPtr = Q_NULLPTR;
	}
}

int loadPreferencesDatabase(void)
{
	if (Q_UNLIKELY((GlobInstcs::prefsPtr == Q_NULLPTR) ||
	        (GlobInstcs::prefsDbPtr == Q_NULLPTR))) {
		return -1;
	}

	return
	    GlobInstcs::prefsPtr->setDatabase(GlobInstcs::prefsDbPtr) ? 0 : -1;
}
