/*
 * 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 <cstdlib>
#include <QApplication>
#include <QCommandLineParser>
#include <QDateTime>
#include <QDir>
#include <QMessageBox>
#include <QSettings>
#include <QSplashScreen>
#include <QStyleFactory>
#include <QThread>

#include "src/about.h"
#include "src/cli/cli_parser.h"
#include "src/cli/cli_pin.h"
#include "src/cli/cmd_compose.h"
#include "src/datovka_shared/compat_qt/random.h"
#include "src/datovka_shared/crypto/crypto_funcs.h"
#include "src/datovka_shared/crypto/crypto_threads.h"
#include "src/datovka_shared/crypto/crypto_version.h"
#include "src/datovka_shared/io/db_tables.h"
#include "src/datovka_shared/io/sqlite/db.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/log/memory_log.h"
#include "src/datovka_shared/settings/pin.h"
#include "src/datovka_shared/worker/pool.h"
#include "src/db_repair/gui/mw_db_repair.h"
#include "src/global.h"
#include "src/gui/datovka.h"
#include "src/gui/dlg_pin_input.h"
#include "src/gui/dlg_restore.h"
#include "src/gui/dlg_restore_progress.h"
#include "src/gui/dlg_view_zfo.h"
#include "src/gui/dlg_update_portable.h"
#include "src/initialisation.h"
#include "src/initialisation_gui.h" /* loadAppUiTheme() */
#include "src/io/filesystem.h"
#include "src/settings/accounts.h"
#include "src/settings/convert_to_prefs.h"
#include "src/settings/preferences.h"
#include "src/settings/prefs_defaults.h"
#include "src/settings/proxy.h"
#include "src/single/single_instance.h"
#if defined(Q_OS_WIN)
#  include "src/system/native_event_filter.h" /* NativeEventFilter */
#endif /* !Q_OS_WIN */
#if defined(Q_OS_MACOS) || defined(Q_OS_UNIX) || defined(Q_OS_LINUX)
#  include "src/system/unix_signals.h" /* install_signal_handlers() */
#endif

/*!
 * @brief Specified the mode the executable is being executed with.
 */
enum RunMode {
	RM_GUI, /*!< Start with active GUI. */
	RM_CLI, /*!< Execute command line service. */
	RM_ZFO /*!< View ZFO content. */
};

/*!
 * @brief View data message from ZFO file.
 *
 * @param[in] fileName ZFO file name.
 */
static
int showZfo(const QString &fileName)
{
	if (fileName.isEmpty()) {
		return -1;
	}

	DlgViewZfo::view(fileName, Q_NULLPTR);

	return 0;
}

int main(int argc, char *argv[])
{
#if defined(Q_OS_MACOS) || defined(Q_OS_UNIX) || defined(Q_OS_LINUX)
	if (Q_UNLIKELY(0 != install_signal_handlers())) {
		return EXIT_FAILURE;
	}
#endif

	QApplication app(argc, argv);

	declareTypes();

	if (0 != allocGlobLog()) {
		/* Cannot continue without logging facility. */
		return EXIT_FAILURE;
	}

	/* Set random generator. */
	Compat::seedRand();

	/* Log warnings. */
	GlobInstcs::logPtr->setLogLevelBits(LogDevice::LF_STDERR, LOGSRC_ANY,
	    LOG_UPTO(LOG_WARNING));

	/* Memory log. */
	MemoryLog memLog;
	/* At least 2 or 3 maximum-sized (20 MB) ZFOs should fit into 80 MB. */
	memLog.setMaxMemory(80 * 1024 * 1024);
	GlobInstcs::logPtr->installMemoryLog(&memLog);

	setDefaultLocale();

	/* TODO -- Make the following assignments configurable. */
	QCoreApplication::setApplicationName(APP_NAME);
	QCoreApplication::setApplicationVersion(VERSION " -- [" +
	    libraryDependencies().join("; ") + "]");
	QCoreApplication::setOrganizationDomain(APP_ORG_DOMAIN);
	QCoreApplication::setOrganizationName(APP_ORG_NAME);

	qInstallMessageHandler(globalLogOutput);

	if (0 != allocGlobInfrastruct()) {
		return EXIT_FAILURE;
	}
	if (0 != allocGlobSettings()) {
		return EXIT_FAILURE;
	}

	QCommandLineParser parser;
	if (0 != CLIParser::setupCmdLineParser(parser)) {
		logErrorNL("%s", "Cannot set up command-line parser.");
		return EXIT_FAILURE;
	}
	if (!CLI::CmdCompose::installParserOpt(parser)) {
		logErrorNL("%s", "Cannot set up command-line parser.");
		return EXIT_FAILURE;
	}

#if defined(Q_OS_WIN)
	/* Install native event filter. */
	app.installNativeEventFilter(new (::std::nothrow) NativeEventFilter);
#endif /* !Q_OS_WIN */

	/* Process command-line arguments. */
	parser.process(app);

	preferencesSetUpRegistry(*GlobInstcs::iniPrefsPtr);

	if (Q_UNLIKELY(0 != preferencesSetUp(parser, *GlobInstcs::iniPrefsPtr,
	        *GlobInstcs::logPtr))) {
		logErrorNL("%s",
		    "Cannot apply command line arguments on global settings.");
		return EXIT_FAILURE;
	}

	{
		/* No localisation is loaded here. */
		QString errorMessage;
		if (Q_UNLIKELY(0 != preferencesOk(*GlobInstcs::iniPrefsPtr, errorMessage))) {
			QMessageBox::critical(Q_NULLPTR,
			    "Bad Command Line Argument",
			    errorMessage);
			return EXIT_FAILURE;
		}
	}

	const QStringList cmdLineFileNames(parser.positionalArguments());
	const QStringList srvcArgs(
	    CLIParser::CLIServiceArgs(parser.optionNames()));

	if (CLI::CmdCompose::isSet(parser)) {
		CLI::CmdCompose composeCmd(CLI::CmdCompose::value(parser));
		/* Check basic sanity of compose parameters. */
		if (composeCmd.isNull()) {
			logErrorNL("%s", "Invalid compose command parameters.");
			return EXIT_FAILURE;
		}
	}

	enum RunMode runMode = RM_GUI;
	QSplashScreen *splash = Q_NULLPTR; /* Used only in GUI mode. */

	if (!srvcArgs.isEmpty()) {
		runMode = RM_CLI;
	} else if (!cmdLineFileNames.isEmpty()) {
		runMode = RM_ZFO;
	} else {
		splash = new (::std::nothrow) QSplashScreen;
		splash->setPixmap(QPixmap(":/splash/datovka-splash.png"));
		splash->show();
	}

	/* Ensure only one instance with given configuration file. */
	SingleInstance singleInstance(GlobInstcs::iniPrefsPtr->loadConfPath());
	if (singleInstance.existsInSystem()) {
		int ret = EXIT_FAILURE;
		logErrorNL("%s",
		    "Another application already uses same configuration.");
		logInfoNL("%s", "Notifying key owner and exiting.");
		if (!singleInstance.sendMessage(
		        SingleInstance::MTYPE_RAISE_MAIN_WIN)) {
			logErrorNL("%s", "Could not send message to owner.");
		}
		if (CLI::CmdCompose::isSet(parser)) {
			if (singleInstance.sendMessage(
			        SingleInstance::MTYPE_COMPOSE,
			        CLI::CmdCompose::value(parser).serialise())) {
				ret = EXIT_SUCCESS;
			} else {
				logErrorNL("%s",
				    "Could not send message to owner.");
			}
		}
		return ret;
	}

	qint64 start, stop, diff;
	start = QDateTime::currentMSecsSinceEpoch();
	logInfo("Starting at %lld.%03lld .\n", start / 1000, start % 1000);

	/* Create configuration file if file is missing. */
	{
		QString problemPath;
		const enum INIPreferences::ConfPresence presenceCode =
		    GlobInstcs::iniPrefsPtr->ensureConfPresence(&problemPath);
		if (Q_UNLIKELY((runMode == RM_GUI)
		        && (presenceCode != INIPreferences::ACCESSIBLE))) {
			loadLocalisationSystem();
			QString errorMessage =
			    INIPreferences::confPresenceErrorMessage(
			        presenceCode, problemPath);
			errorMessage += "\n\n";
			errorMessage +=
			    QObject::tr("Please check that you own the permissions to read and write the entire content of the directory '%1'.")
			        .arg(GlobInstcs::iniPrefsPtr->confDir());
			QMessageBox::critical(Q_NULLPTR,
			    QObject::tr("Cannot Access Configuration"),
			    errorMessage);
			return EXIT_FAILURE;
		}
	}
	/*
	 * Check whether there are permissions to modify content of
	 * configuration directory.
	 */
	{
		QStringList problemPaths;
		const enum INIPreferences::ConfPresence presenceCode =
		    GlobInstcs::iniPrefsPtr->checkConfiguration(problemPaths);
		if (Q_UNLIKELY((runMode == RM_GUI)
		        && (presenceCode != INIPreferences::ACCESSIBLE))) {
			loadLocalisationSystem();
			QString errorMessage =
			    INIPreferences::confPresenceErrorMessage2(
			        presenceCode, problemPaths);
			errorMessage += "\n\n";
			errorMessage +=
			    QObject::tr("Please check that you own the permissions to read and write the entire content of the directory '%1'.")
			        .arg(GlobInstcs::iniPrefsPtr->confDir());
			QMessageBox::critical(Q_NULLPTR,
			    QObject::tr("Cannot Access Configuration"),
			    errorMessage);
			return EXIT_FAILURE;
		}
	}

	logAppVersion();
	logTimeZone();
	logQtVersion();
	logEnvironment();

	switch (crypto_compiled_lib_ver_check()) {
	case 1:
#if defined(Q_OS_MACOS)
		/* OSX/macOS contains own SSL library. Just log a warning. */
		logWarningNL("%s", "Cryptographic library mismatch.");
#else /* !defined(Q_OS_MACOS) */
		/*
		 * Qt-6 seems to be compiled against Windows SSL implementation.
		 * Just log an error and continue.
		 *
		 * https://stackoverflow.com/questions/77116935/the-backend-cert-only-does-not-support-qsslkey
		 */
		logErrorNL("%s", "Cryptographic library mismatch.");
#endif /* defined(Q_OS_MACOS) */
		break;
	case 0:
		break;
	case -1:
	default:
		logErrorNL("%s",
		    "Error checking the version of the cryptographic library.");
		return EXIT_FAILURE;
		break;
	}

	if (0 != crypto_init()) {
		logError("%s\n", "Cannot load cryptographic back-end.");
		/* TODO -- throw a dialogue notifying the user. */
		/*
		 * TODO -- the function should fail only when all certificates
		 * failed to load.
		 */
		return EXIT_FAILURE;
	}
	crypto_init_threads();

	{
		/* Stuff related to configuration file. */

		logDebugLv0NL("Load: '%s'.",
		    GlobInstcs::iniPrefsPtr->loadConfPath().toUtf8().constData());
		logDebugLv0NL("Save: '%s'.",
		    GlobInstcs::iniPrefsPtr->saveConfPath().toUtf8().constData());

		/* Change "\" to "/" */
		confFileFixBackSlashes(GlobInstcs::iniPrefsPtr->loadConfPath(),
		    true);
	}

	if (!SQLiteDb::dbDriverSupport()) {
		logError("Cannot load database driver '%s'.\n",
		    SQLiteDb::dbDriverType.toUtf8().constData());
		loadLocalisationSystem();
		QMessageBox::critical(Q_NULLPTR,
		    QObject::tr("Missing SQLite Database Driver"),
		    QObject::tr("Missing SQLite database driver module for the Qt framework."));
		return EXIT_FAILURE;
	}

	/* Initialise database-based preferences. */
	if (Q_UNLIKELY(0 != allocGlobPrefs(*GlobInstcs::iniPrefsPtr))) {
		loadLocalisationSystem();
		QString errorMessage =
		    QObject::tr("Cannot open preferences database file '%1'.")
		        .arg(GlobInstcs::iniPrefsPtr->prefsDbPath());
		errorMessage += "\n\n";
		errorMessage +=
		    QObject::tr("You may move, rename or delete this file. It doesn't contain any message data.");
		errorMessage += "\n\n";
		errorMessage +=
		    QObject::tr("If you remove the file then all application settings will be reset to default values.");
		QMessageBox::critical(Q_NULLPTR,
		    QObject::tr("Cannot Access Preferences"),
		    errorMessage);
		return EXIT_FAILURE;
	}

	/* Set default preferences. */
	PrefsDefaults::setDefaults(*GlobInstcs::prefsPtr);
	/* Load preferences from database. */
	if (Q_UNLIKELY(0 != loadPreferencesDatabase())) {
		loadLocalisationSystem();
		QString errorMessage =
		    QObject::tr("Cannot apply content from preferences database file '%1'.")
		        .arg(GlobInstcs::iniPrefsPtr->prefsDbPath());
		errorMessage += "\n\n";
		errorMessage +=
		    QObject::tr("You may try moving, renaming or deleting this file. It doesn't contain any message data.");
		errorMessage += "\n\n";
		errorMessage +=
		    QObject::tr("If you remove the file then all application settings will be reset to default values.");
		QMessageBox::critical(Q_NULLPTR,
		    QObject::tr("Cannot Apply Preferences"),
		    errorMessage);
		return EXIT_FAILURE;
	}

	/*
	 * Load and set application UI theme from preferences.
	 * Dath theme is detected here.
	 */
	loadAppUiTheme(*GlobInstcs::prefsPtr);

	/* Convert INI preferences into database and remove them from INI file. */
	{
		QSettings settings(GlobInstcs::iniPrefsPtr->loadConfPath(),
		    QSettings::IniFormat);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
		/*
		 * Qt-6 does not provide a replacement of QSettings::setIniCodec().
		 * The documentation claims that QSettings assumes the INI file
		 * is UTF-8 encoded.
		 * TODO - It would be great to make really sure the INI is UTF-8 encoded
		 * instead of relying on the developers and documentation.
		 */
#else /* < Qt-6.0 */
		settings.setIniCodec("UTF-8");
#endif /* >= Qt-6.0 */
		iniPreferencesToPrefs(settings, *GlobInstcs::prefsPtr);
	}

	/* Load proxy settings. */
	GlobInstcs::proxSetPtr->loadFromPrefs(*GlobInstcs::prefsPtr);
	/* Apply settings onto the environment because of libcurl. */
	GlobInstcs::proxSetPtr->setProxyEnvVars();

	/* Start downloading the CRL files. */
	downloadCRL();

#if defined(Q_OS_OSX)
	/* Set style. */
	{
		const char *styleKey = "macintosh";
		QStyle *style = QStyleFactory::create(styleKey);
		if (Q_NULLPTR != style) {
			logInfoNL("Setting style '%s'.", styleKey);
			QApplication::setStyle(style);
		} else {
			logErrorNL("Cannot find style '%s'.", styleKey);
		}
	}
#endif /* Q_OS_OSX */

	logDebugLv0NL("GUI main thread: %p.", QThread::currentThreadId());

	logDebugLv0NL("App path: '%s'.",
	    app.applicationDirPath().toUtf8().constData());

	loadLocalisationPrefs(*GlobInstcs::prefsPtr);

	/* Ask for PIN. */
	{
		QSettings settings(GlobInstcs::iniPrefsPtr->loadConfPath(),
		    QSettings::IniFormat);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
		/*
		 * Qt-6 does not provide a replacement of QSettings::setIniCodec().
		 * The documentation claims that QSettings assumes the INI file
		 * is UTF-8 encoded.
		 * TODO - It would be great to make really sure the INI is UTF-8 encoded
		 * instead of relying on the developers and documentation.
		 */
#else /* < Qt-6.0 */
		settings.setIniCodec("UTF-8");
#endif /* >= Qt-6.0 */
		GlobInstcs::pinSetPtr->loadFromSettings(settings);
		if (GlobInstcs::pinSetPtr->pinConfigured()) {
			bool pinOk = false;
			if (RM_GUI == runMode) {
				splash->hide();
				/*
				 * PIN query dialogue cannot be the child of
				 * the splash screen as this window cannot be
				 * focused using Alt+Tab on Windows.
				 */
				pinOk = DlgPinInput::queryPin(
				    *GlobInstcs::pinSetPtr);
				splash->show();
				app.processEvents();
			} else if (RM_CLI == runMode) {
				pinOk = CLIPin::queryPin(
				    *GlobInstcs::pinSetPtr, 2);
			}

			if (!pinOk) {
				delete splash;
				return EXIT_FAILURE;
			}
		}
	}

	/* set splash action text */
	if (runMode == RM_GUI) {
		splash->showMessage(QObject::tr("Application is loading data from\n%1")
		    .arg(QDir::toNativeSeparators(GlobInstcs::iniPrefsPtr->confDir())),
		    Qt::AlignCenter, Qt::white);
		app.processEvents();
	}

	/* Localise description in tables. */
	SQLiteTbls::localiseTableDescriptions();

	/* Backup tag database if it is in old format. */
	createBackupOfOldFormatData(*GlobInstcs::iniPrefsPtr);

	if (Q_UNLIKELY(0 != allocGlobTimestamps(*GlobInstcs::iniPrefsPtr))) {
		loadLocalisationSystem();
		QString errorMessage =
		    QObject::tr("Cannot open timestamp database file '%1'.")
		        .arg(GlobInstcs::iniPrefsPtr->timestampDbPath());
		errorMessage += "\n\n";
		errorMessage +=
		    QObject::tr("You may move, rename or delete this file. It doesn't contain any message data.");
		errorMessage += "\n\n";
		errorMessage +=
		    QObject::tr("If you remove the file then information about message timestamp expiration will be lost and will need to be generated again.");
		QMessageBox::critical(Q_NULLPTR,
		    QObject::tr("Cannot Access Timestamp Data"),
		    errorMessage);
		return EXIT_FAILURE;
	}

	/* Initialise database-based containers. */
	{
		QString problemPath;
		const enum InitialisationError ret = allocGlobContainers(
		    *GlobInstcs::prefsPtr, *GlobInstcs::iniPrefsPtr,
		    &problemPath);

		if (Q_UNLIKELY(SUCCESS != ret)) {
			/* Localisation already loaded. */
			QString errorMessage;
			switch (ret) {
			case ERR_ALLOC_RESOURCES:
				errorMessage = QObject::tr("Cannot allocate resources.");
				break;
			case ERR_OPEN_FILES:
				errorMessage = QObject::tr("Cannot read content of file '%1'.").arg(problemPath);
				errorMessage += "\n\n";
				errorMessage += QObject::tr("File content may be corrupted.");
				errorMessage += "\n\n";
				errorMessage += QObject::tr("You may try moving the affected file into a different location or try renaming it. This may solve the problem.");
				break;
			default:
				break;
			}
			errorMessage += "\n\n";
			errorMessage +=
			    QObject::tr("You may try running the application with enabled logging and check the log for more detailed information.");
			QMessageBox::critical(Q_NULLPTR,
			    QObject::tr("Cannot Load Container Data"),
			    errorMessage);
			return EXIT_FAILURE;
		}
	}

	int ret = EXIT_SUCCESS;

	/* Start worker threads. */
	GlobInstcs::workPoolPtr->start();
	logInfo("%s\n", "Worker pool started.");

#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
	// https://blog.qt.io/blog/2016/01/26/high-dpi-support-in-qt-5-6/
	//logInfoNL("%s", "Enabling high DPI scaling.");
	//QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif /* >= Qt-5.6 */
	/*
	 * Enable icons in menus.
	 * The default value is platform dependent and seems to be true in Qt-6.8 on macOS.
	 */
	QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false);

	if (runMode == RM_CLI) {
		/* Parse account information. */
		{
			QSettings settings(GlobInstcs::iniPrefsPtr->loadConfPath(),
			    QSettings::IniFormat);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
			/*
			 * Qt-6 does not provide a replacement of QSettings::setIniCodec().
			 * The documentation claims that QSettings assumes the INI file
			 * is UTF-8 encoded.
			 * TODO - It would be great to make really sure the INI is UTF-8 encoded
			 * instead of relying on the developers and documentation.
			 */
#else /* < Qt-6.0 */
			settings.setIniCodec("UTF-8");
#endif /* >= Qt-6.0 */
			GlobInstcs::acntMapPtr->loadFromSettings(
			    GlobInstcs::iniPrefsPtr->confDir(), settings);
			GlobInstcs::acntMapPtr->decryptAllPwds(
			    GlobInstcs::pinSetPtr->pinValue());
			GlobInstcs::shadowAcntsPtr->loadFromSettings(
			    GlobInstcs::iniPrefsPtr->confDir(), settings);
			GlobInstcs::shadowAcntsPtr->decryptAllPwds(
			    GlobInstcs::pinSetPtr->pinValue());
		}
		delete splash;
		ret = CLIParser::runCLIService(srvcArgs, parser);
	} else if (runMode == RM_ZFO) {
		delete splash;
		for (const QString &fileName : cmdLineFileNames) {
			ret = showZfo(fileName);
			if (0 != ret) {
				break;
			}
		}
	} else {
		MainWindow mainwin;
		/* Window geometry. */
		mainwin.loadWindowGeometry(*GlobInstcs::prefsPtr);
		mainwin.show();
		splash->finish(&mainwin);
		delete splash;
		/* Main window connects the required slots in its constructor. */
		if (CLI::CmdCompose::isSet(parser)) {
			if (!singleInstance.sendMessage(
			        SingleInstance::MTYPE_COMPOSE,
			        CLI::CmdCompose::value(parser).serialise())) {
				logErrorNL("%s",
				    "Could not send message to owner.");
			}
		}
		ret = (0 == app.exec()) ? EXIT_SUCCESS : EXIT_FAILURE;
	}

	/* Wait until all threads finished. */
	logInfo("%s\n", "Waiting for pending worker threads.");
	GlobInstcs::workPoolPtr->wait();
	GlobInstcs::workPoolPtr->stop();
	logInfo("%s\n", "All worker threads finished");

	if (DlgRestore::restoreDescr != Q_NULLPTR) {
		deallocGlobTimestamps();
		deallocGlobContainers(); /* Databases cannot be loaded here. */
		deallocGlobSettings(); /* Settings cannot be loaded here. */
		DlgRestoreProgress::restore();
	}
	if (DlgUpdatePortable::updatePortableData != Q_NULLPTR) {
		/* Stop everything except logging. */
		deallocGlobTimestamps();
		deallocGlobContainers();
		deallocGlobPrefs();
		deallocGlobSettings();
		deallocGlobInfrastruct();
		DlgUpdatePortable::updatePortable();
	}
	if (MWDbRepair::runDatabaseRepair) {
		/* Stop everything except logging. */
		deallocGlobTimestamps();
		deallocGlobContainers();
		deallocGlobPrefs();
		deallocGlobSettings();
		deallocGlobInfrastruct();
		MWDbRepair mwDbrepair;
		mwDbrepair.show();
		app.exec();
	}

	stop = QDateTime::currentMSecsSinceEpoch();
	diff = stop - start;
	logInfo("Stopping at %lld.%03lld; ran for %lld.%03lld seconds.\n",
	    stop / 1000, stop % 1000, diff / 1000, diff % 1000);

	/*
	 * TODO -- The following clean-up function causes troubles.
	 * on OS X libcurl occasionally seems to operate while the lock
	 * de-initialisation is performed causing the application to crash
	 * on exit.
	 */
	//crypto_cleanup_threads();

	deallocGlobTimestamps();
	deallocGlobContainers();
	deallocGlobPrefs();
	deallocGlobSettings();
	deallocGlobInfrastruct();
	deallocGlobLog();

	return ret;
}
