/*
 *  $Id: gwyddion.c 30115 2026-06-22 15:10:37Z yeti-dn $
 *  Copyright (C) 2003-2026 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  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 2 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, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef G_OS_WIN32
#include <windows.h>
#include <winreg.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>

#include "libgwyddion/gwyddion.h"
#include "libgwyddion/grain-value.h"
#include "libgwyapp/module-loader.h"
#include "libgwyapp/module-file.h"
#include "libgwyui/gwyui.h"
#include "libgwyapp/gwyapp.h"

#include "gwyddion/gwyddion.h"
#include "gwyddion/mac_integration.h"
#include "gwyddion/release.h"

#ifdef G_OS_WIN32
#define LOG_TO_FILE_DEFAULT TRUE
#define LOG_TO_CONSOLE_DEFAULT FALSE
#define gwyddion_key "Software\\Gwyddion\\2.0"
#else
#define LOG_TO_FILE_DEFAULT TRUE
#define LOG_TO_CONSOLE_DEFAULT TRUE
#endif

// This matches the file name in data (and stuff inside the file). Define it in one place?
#define APPLICATION_ID "net.gwyddion.Gwyddion3"

typedef enum {
    MODE_GUI,
    MODE_IDENTIFY,
    MODE_CHECK,
    MODE_CONVERT_TO_GWY2,
    MODE_CONVERT_TO_GWY3,
} GwyddionMode;

typedef enum {
    REMOTE_NONE,
    REMOTE_NEW,
    REMOTE_EXISTING,
    REMOTE_QUERY
} GwyddionRemoteType;

typedef struct _GwyddionApplication GwyddionApplication;
typedef GApplicationClass GwyddionApplicationClass;

static gboolean   open_directory_at_startup(gpointer user_data);
static void       block_modules            (GPtrArray *module_list);
static gint       process_files            (GwyddionApplication *gwyapp,
                                            gchar **arguments);
static void       print_help               (void);
static void       print_version            (void);
static GPtrArray* extend_disabled_modules  (GPtrArray *module_list,
                                            const gchar *arg);
static void       timer_checkpoint         (GwyddionApplication *gwyapp,
                                            const gchar *task);
static void       timer_finish             (GwyddionApplication *gwyapp,
                                            const gchar *task);
static void       setup_locale_win32       (void);
static void       warn_broken_settings_file(GtkWidget *parent,
                                            const gchar *settings_file,
                                            const gchar *reason);
static void       check_broken_modules     (GtkWidget *parent);
static void       gwy_app_set_window_icon  (void);
static void       gwy_app_check_version    (void);

#define GWYDDION_TYPE_APPLICATION (gwyddion_application_get_type())
#define GWYDDION_APPLICATION(obj) \
        (G_TYPE_CHECK_INSTANCE_CAST((obj), GWYDDION_TYPE_APPLICATION, GwyddionApplication))
#define GWYDDION_APPLICATION_CLASS(klass) \
        (G_TYPE_CHECK_CLASS_CAST((klass), GWYDDION_TYPE_APPLICATION, GwyddionApplicationClass))
#define GWYDDION_IS_APPLICATION(obj) \
        (G_TYPE_CHECK_INSTANCE_TYPE((obj), GWYDDION_TYPE_APPLICATION))
#define GWYDDION_IS_APPLICATION_CLASS(klass) \
        (G_TYPE_CHECK_CLASS_TYPE((klass), GWYDDION_TYPE_APPLICATION))
#define GWYDDION_APPLICATION_GET_CLASS(obj) \
        (G_TYPE_INSTANCE_GET_CLASS((obj), GWYDDION_TYPE_APPLICATION, GwyddionApplicationClass))

struct _GwyddionApplication {
    GApplication parent_instance;

    GTimer *timer;
    gdouble total;

    gboolean no_splash;
    gboolean startup_time;
    gboolean disable_gl; // XXX: Currently does not do anything, but we may want to keep the option for broken machines.
    gboolean log_to_file;
    gboolean log_to_console;
    gboolean remote_existing_failed;
    GwyddionMode mode;
    GwyddionRemoteType remote;
    GPtrArray *disabled_modules;
    gchar *convert_outfilename;

    gchar *recent_file_file;
    gchar *recent_func_file;
    gchar *accel_file;
    gchar *settings_file;
    gboolean has_settings;
    gboolean settings_ok;

    GtkWidget *toolbox;
};

static GType    gwyddion_application_get_type(void);
static void     gwyddion_finalize            (GObject *object);
static void     gwyddion_startup             (GApplication *gapplication);
static void     gwyddion_shutdown            (GApplication *gapplication);
static gboolean gwyddion_local_command_line  (GApplication *gapplication,
                                              gchar ***arguments,
                                              gint *exit_status);
static void     gwyddion_activate            (GApplication *gapplication);
static void     gwyddion_open                (GApplication *gapplication,
                                              GFile **files,
                                              gint n_files,
                                              const gchar *hint);

static GApplicationClass *parent_class = NULL;

G_DEFINE_TYPE(GwyddionApplication, gwyddion_application, G_TYPE_APPLICATION)

static void
gwyddion_application_class_init(GwyddionApplicationClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);
    GApplicationClass *app_class = G_APPLICATION_CLASS(klass);

    parent_class = G_APPLICATION_CLASS(gwyddion_application_parent_class);

    object_class->finalize = gwyddion_finalize;
    app_class->startup = gwyddion_startup;
    app_class->shutdown = gwyddion_shutdown;
    app_class->local_command_line = gwyddion_local_command_line;
    app_class->open = gwyddion_open;
    app_class->activate = gwyddion_activate;
}

static void
gwyddion_application_init(GwyddionApplication *gwyapp)
{
    gwyapp->log_to_file = LOG_TO_FILE_DEFAULT;
    gwyapp->log_to_console = LOG_TO_CONSOLE_DEFAULT;
    gwyapp->mode = MODE_GUI;
    gwyapp->remote = REMOTE_NEW;
}

int
main(int argc, char *argv[])
{
    /* This is kind of ugly, but we want to start the timer as early as possible. In particular, before creating
     * the GApplication object. */
    GTimer *timer = g_timer_new();

    /* Dump core on Critical errors in development versions. */
    if (RELEASEDATE == 0 || sizeof(GWY_VERSION_STRING) > 9)
        g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL);

    g_assert(g_application_id_is_valid(APPLICATION_ID));
    GwyddionApplication *gwyapp = g_object_new(GWYDDION_TYPE_APPLICATION,
                                               "application-id", APPLICATION_ID,
                                               "flags", G_APPLICATION_HANDLES_OPEN,
                                               NULL);
    GApplication *gapplication = G_APPLICATION(gwyapp);
    gwyapp->timer = timer;
    g_application_set_version(gapplication, gwy_version_string());

    g_application_run(gapplication, argc, argv);

    if (gwyapp->remote_existing_failed)
        return 1;

    return 0;
}

static gboolean
handle_simple_option(GwyddionApplication *gwyapp, const gchar *arg)
{
    if (gwy_strequal(arg, "--no-splash"))
        gwyapp->no_splash = TRUE;
    else if (gwy_strequal(arg, "--remote-existing")) {
        gwyapp->mode = MODE_GUI;
        gwyapp->remote = REMOTE_EXISTING;
    }
    else if (gwy_strequal(arg, "--remote-new")) {
        gwyapp->mode = MODE_GUI;
        gwyapp->remote = REMOTE_NEW;
    }
    else if (gwy_strequal(arg, "--remote-query")) {
        gwyapp->mode = MODE_GUI;
        gwyapp->remote = REMOTE_QUERY;
    }
    else if (gwy_strequal(arg, "--no-remote")) {
        gwyapp->mode = MODE_GUI;
        gwyapp->remote = REMOTE_NONE;
    }
    else if (gwy_strequal(arg, "--startup-time"))
        gwyapp->startup_time = TRUE;
    else if (gwy_strequal(arg, "--log-to-file"))
        gwyapp->log_to_file = TRUE;
    else if (gwy_strequal(arg, "--no-log-to-file"))
        gwyapp->log_to_file = FALSE;
    else if (gwy_strequal(arg, "--log-to-console"))
        gwyapp->log_to_console = TRUE;
    else if (gwy_strequal(arg, "--no-log-to-console"))
        gwyapp->log_to_console = FALSE;
    else if (gwy_strequal(arg, "--disable-gl"))
        gwyapp->disable_gl = TRUE;
    else if (gwy_strequal(arg, "--identify") || gwy_strequal(arg, "-i"))
        gwyapp->mode = MODE_IDENTIFY;
    else if (gwy_strequal(arg, "--check"))
        gwyapp->mode = MODE_CHECK;
    else if (g_str_has_prefix(arg, "--convert-to-gwy=")
             || g_str_has_prefix(arg, "--convert-to-gwy2=")
             || g_str_has_prefix(arg, "--convert-to-gwy3=")) {
        gwyapp->mode = MODE_CONVERT_TO_GWY3;
        if (arg[16] == '2' || g_str_has_suffix(arg, ".gwy2"))
            gwyapp->mode = MODE_CONVERT_TO_GWY2;
        gwy_assign_string(&gwyapp->convert_outfilename, strchr(arg, '=') + 1);
    }
    else if (g_str_has_prefix(arg, "--disable-modules="))
        gwyapp->disabled_modules = extend_disabled_modules(gwyapp->disabled_modules, strchr(arg, '=') + 1);
    else
        return FALSE;

    return TRUE;
}

static guint
pack_remaining_arguments(gchar **arguments, guint i, guint n, gboolean free_it)
{
    gchar *arg;
    while ((arg = arguments[i++]))
        arguments[n++] = arg;
    arguments[n] = NULL;

    if (!free_it)
        return n;

    for (i = 1; i < n; i++)
        g_free(arguments[i]);
    arguments[1] = NULL;
    return 1;
}

/* This is run early and all kinds of shenanigans can happen here. We can chain to the parent class'
 * local_command_line() method. But we only do so in the GUI mode, i.e. running the GUI (at least potentially).
 * Non-GUI operations are just handled here, we do not even register the application and are just done.
 *
 * We need to basically all our option processing here, not relying on "handle-local-options" signal handler because:
 * - We have order-dependent options (--foo and --no-foo, last given wins), but the signal handler just gets a dict.
 * - We want things like logging an startup time measurement set up very early.
 * - We need to avoid the default handlers of --help (or --version), which are stupid and break nice things like having
 *   visually grouped options but still printing the entire help with --help. */
static gboolean
gwyddion_local_command_line(GApplication *gapplication,
                            gchar ***arguments,
                            gint *exit_status)
{
    GwyddionApplication *gwyapp = GWYDDION_APPLICATION(gapplication);
    guint i = 1, n = 1;

    while ((*arguments)[i]) {
        gchar *arg = (*arguments)[i];

        if (gwy_strequal(arg, "--")) {
            g_free(arg);
            n = pack_remaining_arguments(*arguments, i+1, n, FALSE);
            break;
        }

        if (gwy_stramong(arg, "-h", "-v", "--help", "--version", NULL)) {
            if (gwy_stramong(arg, "-h", "--help", NULL))
                print_help();
            else
                print_version();
            /* Get arguments[] to a well-defined clean state in case someone is still going to access it. */
            pack_remaining_arguments(*arguments, i, n, TRUE);
            *exit_status = 0;
            return TRUE;
        }

        if (handle_simple_option(gwyapp, arg))
            g_free(arg);
        else
            (*arguments)[n++] = arg;

        i++;
    }
    (*arguments)[n] = NULL;

    /* Set up logging here. This is the earliest we can do it. So that is when we should do it. */
    gwy_app_setup_logging((gwyapp->log_to_file ? GWY_APP_LOGGING_TO_FILE : 0)
                          | (gwyapp->log_to_console ? GWY_APP_LOGGING_TO_CONSOLE : 0));
    gwy_app_check_version();

    /* Here if we chain to parent's method, it will register the application. So only do that in GUI mode. */
    if (gwyapp->mode == MODE_GUI) {
        if (gwyapp->remote == REMOTE_NONE)
            g_application_set_flags(gapplication, g_application_get_flags(gapplication) | G_APPLICATION_NON_UNIQUE);
        return parent_class->local_command_line(gapplication, arguments, exit_status);
    }

    /* Process files locally and immediately. */
    *exit_status = !!process_files(gwyapp, *arguments);
    timer_checkpoint(gwyapp, "process local files");

    for (i = 1; i < n; i++)
        g_free((*arguments)[i]);
    (*arguments)[1] = NULL;

    return TRUE;
}

static void
gwyddion_startup(GApplication *gapplication)
{
    GwyddionApplication *gwyapp = GWYDDION_APPLICATION(gapplication);

    g_assert(gwyapp->mode == MODE_GUI);
    /* We have become the primary instance but the user asked to open files *if* there is already a running instance
     * which can open the files. */
    if (gwyapp->remote == REMOTE_EXISTING) {
        gwyapp->remote_existing_failed = TRUE;
        parent_class->startup(gapplication);
        g_application_quit(gapplication);
        return;
    }

    // FIXME: Obsolete?
    g_unsetenv("UBUNTU_MENUPROXY");
    gwy_threads_set_enabled(TRUE);
    gwyddion_mac_startup();
    gwyddion_mac_set_locale();
    setup_locale_win32();
    textdomain(GETTEXT_PACKAGE);

    /* TODO: handle failure */
    gwy_app_settings_create_config_dir(NULL);
    timer_checkpoint(gwyapp, "init");

    gtk_init(NULL, NULL);
    timer_checkpoint(gwyapp, "gtk init");

    gwy_ui_init();
    // FIXME: This is awkward. The price for not using gwy_app_init().
    g_type_class_peek(GWY_TYPE_FILE);
    g_set_application_name(PACKAGE_NAME);

    gwy_app_set_window_icon();
    gwy_app_init_widget_styles(gdk_screen_get_default());

    timer_checkpoint(gwyapp, "app init");

    gwyapp->settings_file = gwy_app_settings_get_settings_filename();
    gwyapp->has_settings = g_file_test(gwyapp->settings_file, G_FILE_TEST_IS_REGULAR);
    gwy_debug("Text settings file is `%s'. Do we have it: %s",
              gwyapp->settings_file, gwyapp->has_settings ? "TRUE" : "FALSE");

    splash_start(!gwyapp->no_splash);
    timer_checkpoint(gwyapp, "create splash");

    gwyapp->accel_file = g_build_filename(gwy_get_user_dir(), "ui", "accel_map", NULL);
    gtk_accel_map_load(gwyapp->accel_file);
    timer_checkpoint(gwyapp, "load accel map");

    splash_set_message(_("Loading history"));
    gwyapp->recent_file_file = gwy_app_settings_get_recent_file_list_filename();
    gwy_app_recent_file_list_load(gwyapp->recent_file_file);
    timer_checkpoint(gwyapp, "load document history");

    gwyapp->recent_func_file = g_build_filename(gwy_get_user_dir(), "recent-funcs", NULL);
    gwy_recent_funcs_load(gwyapp->recent_func_file, NULL);
    timer_checkpoint(gwyapp, "load function history");

    splash_set_message(_("Registering resource"));
    gwy_load_resources();
    timer_checkpoint(gwyapp, "load resources");

    GError *settings_err = NULL;
    gwyapp->settings_ok = FALSE;
    splash_set_message(_("Loading settings"));
    if (gwyapp->has_settings)
        gwyapp->settings_ok = gwy_app_settings_load(gwyapp->settings_file, &settings_err);
    gwy_debug("Loading settings was: %s", gwyapp->settings_ok ? "OK" : "Not OK");
    gwy_app_settings_get();
    timer_checkpoint(gwyapp, "load settings");

    /* Modules load pretty fast with bundling.  Most time is taken by:
     * 1) pygwy, but only if it registers some Python modules; when it is no-op it is fast, so you only pay the price
     *    when you get the benefits
     * 2) a few external libraries that are noticeably slow to init, in particular cfitsio (we do not call any its
     *    function during registration, so it is its internal init)
     * Running gwyddion --disable-modules=pygwy,fitsfile can take module registration down to ~6 ms. */
    splash_set_message(_("Registering modules"));
    block_modules(gwyapp->disabled_modules);

    gchar **module_dirs = gwy_app_settings_get_module_dirs();
    for (guint i = 0; module_dirs && module_dirs[i]; i++) {
        gwy_module_register_modules(module_dirs[i], NULL);
        g_free(module_dirs[i]);
    }
    g_free(module_dirs);

    /* The Python initialisation overrides SIGINT and Gwyddion can no longer be terminated with Ctrl-C.  Fix it. */
    signal(SIGINT, SIG_DFL);
    /* TODO: The Python initialisation also overrides where the warnings go. Restore the handlers. */
    timer_checkpoint(gwyapp, "register modules");

    /* Destroy splash before creating UI.  Opposite order of actions can apparently lead to strange errors. */
    splash_finish();
    timer_checkpoint(gwyapp, "destroy splash");

    /* Toolbox creation is one of the most time-consuming parts of startup.
     *
     * Most of the time, about 2/3, is taken by show-all on the toolbox widget (this is likely also theme-dependent,
     * etc.).  There may not be much we can do to speed it up -- it is the price of having a GUI at all. */
    GtkWidget *toolbox = gwyapp->toolbox = create_toolbox_window();
    timer_checkpoint(gwyapp, "create toolbox");
    /* A dirty trick, it constructs the recent files menu as a side effect. */
    gwy_app_recent_file_list_update(NULL, NULL, NULL, 0);
    timer_checkpoint(gwyapp, "create recent files menu");

    /* Win32 does not give programs a reasonable physical cwd. So try to set something reasonable here. */
#ifdef G_OS_WIN32
    const gchar *cwd;

    if ((cwd = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP)) && g_file_test(cwd, G_FILE_TEST_IS_DIR))
        gwy_app_set_current_directory(cwd);
    else if ((cwd = g_get_home_dir()) && g_file_test(cwd, G_FILE_TEST_IS_DIR))
        gwy_app_set_current_directory(cwd);
    else if (g_file_test("c:\\", G_FILE_TEST_IS_DIR))
        gwy_app_set_current_directory("c:\\");
#endif

    if (gwyapp->has_settings && !gwyapp->settings_ok) {
        if (!(settings_err->domain == GWY_APP_SETTINGS_ERROR && settings_err->code == GWY_APP_SETTINGS_ERROR_EMPTY))
            warn_broken_settings_file(toolbox, gwyapp->settings_file, settings_err->message);
        g_clear_error(&settings_err);
    }

    check_broken_modules(toolbox);

    /* Move focus to toolbox */
    gtk_widget_show_all(toolbox);
    timer_checkpoint(gwyapp, "show toolbox");

    /* Usually we would chain to parent at the beginning for initialisation-like methods, but this one does not do
     * much and sets did_startup = TRUE. So we want to call it when the startup is actually done. */
    parent_class->startup(gapplication);

    timer_finish(gwyapp, "STARTUP");
}

static void
gwyddion_shutdown(GApplication *gapplication)
{
    GwyddionApplication *gwyapp = GWYDDION_APPLICATION(gapplication);

    if (gwyapp->remote == REMOTE_EXISTING) {
        parent_class->shutdown(gapplication);
        return;
    }

    gwyapp->timer = g_timer_new();

    gwyddion_mac_shutdown();

    /* TODO: handle failure */
    if (gwyapp->settings_ok || !gwyapp->has_settings)
        gwy_app_settings_save(gwyapp->settings_file, NULL);
    gtk_accel_map_save(gwyapp->accel_file);
    timer_checkpoint(gwyapp, "save settings");
    gwy_app_recent_file_list_save(gwyapp->recent_file_file);
    timer_checkpoint(gwyapp, "save document history");
    gwy_recent_funcs_save(gwyapp->recent_func_file, NULL);
    timer_checkpoint(gwyapp, "save function history");
    /*gwy_resource_classes_finalize();*/
    gwy_app_recent_file_list_free();

    /* EXIT-CLEAN-UP */
    gwy_app_settings_free();
    g_free(gwyapp->recent_file_file);
    g_free(gwyapp->accel_file);
    g_free(gwyapp->settings_file);
    timer_checkpoint(gwyapp, "destroy resources");

    parent_class->shutdown(gapplication);

    timer_finish(gwyapp, "SHUTDOWN");
}

static void
ensure_gtk_main(void)
{
    /* XXX: This code looks suspicious – as in ‘are we supposed to do that?’.
     *
     * I would expect to:
     * (a) Call gtk_main() in the startup() handler and then get open() as some kind of signal while the main loop
     *     is running.
     * (b) Have GApplication iterate over the GTK+ main loop, not a separate one.
     *
     * If we do (a), we only get to opening files after the GTK+ main loop quits, because the GTK+ main loop and the
     * loop GApplication iterates over are different loops. I am not sure if GApplication can run the GTK+ main loop,
     * or whether it is even reasonable to ask for such thing. */
    if (!gtk_main_level())
        gtk_main();
}

static void
gwyddion_activate(GApplication *gapplication)
{
    GwyddionApplication *gwyapp = GWYDDION_APPLICATION(gapplication);

    gtk_window_present(GTK_WINDOW(gwyapp->toolbox));
    ensure_gtk_main();
}

/* FIXME GTK3: This is subtly but utterly broken (and it has been broken like this the entire time in Gwyddion 2,
 * too). If a modal dialog is open while we do this (a data processing function, typically):
 * - The files load and get windows created.
 * - The modal dialog is not transient for the newly created windows! So one can switch between them, etc., while the
 *   supposedly modal dialog should be blocking that.
 * - As the data browser absorbs the new files, the current data change. Most functions are unaffected as they only
 *   ask at the beginning, but there are some which use gwy_app_data_browser_get_current() repeatedly. These see
 *   inconsistent results and can possibly even crash, depending on their internal logic.
 *
 * We need a function that just queues up the loading after a modal function finishes, if one is running.
 **/
static void
gwyddion_open(GApplication *gapplication,
              GFile **files,
              gint n_files,
              G_GNUC_UNUSED const gchar *hint)
{
    GwyddionApplication *gwyapp = GWYDDION_APPLICATION(gapplication);

    if (gwyapp->remote_existing_failed)
        return;

    /* We probably want to do this, regardless of whether this is primary instance startup or we are asked to open
     * more files in an already running programs. */
    gtk_window_present(GTK_WINDOW(gwyapp->toolbox));

    GFile *dir_to_open = NULL;
    for (gint i = 0; i < n_files; i++) {
        GFileType filetype = g_file_query_file_type(files[i], 0, NULL);

        /* If an argument is directory, show a file open dialog. But just for the last one. Asking for multiple such
         * dialogs is stupid and most likely the result of something like ‘gwyddion *’. Do not comply. */
        if (filetype == G_FILE_TYPE_DIRECTORY || filetype == G_FILE_TYPE_MOUNTABLE) {
            dir_to_open = files[i];
            continue;
        }

        gchar *filename_sys = g_file_get_path(files[i]);
        gwy_app_file_load(NULL, filename_sys, NULL);
        g_free(filename_sys);
    }

    if (dir_to_open)
        g_idle_add(open_directory_at_startup, g_object_ref(dir_to_open));

    ensure_gtk_main();
}

static void
print_help(void)
{
    puts("Usage: gwyddion3 [OPTIONS...] FILES...\n"
         "An SPM data visualization and analysis tool, written with Gtk+.\n");
    puts("Interaction with other instances:\n"
         "     --remote-query         Check if a Gwyddion instance is already running.\n"
         "     --remote-new           Load FILES to a running instance or run a new one.\n"
         "     --remote-existing      Load FILES to a running instance or fail.\n"
         "     --no-remote            Run a new instance with no remote control.\n"
         "Any of these options also implicitly selects the normal GUI mode.\n");
    puts("Non-GUI operations:\n"
         " -i, --identify             Identify and print the type of SPM data FILES.\n"
         "     --check                Try to load FILES, print problems and terminate.\n"
         "     --convert-to-gwy[23]=OUTFILE.gwy\n"
         "                            Read FILES, merge them and write a GWY 2 or 3 file.\n"
         " -h, --help                 Print this help and terminate.\n"
         " -v, --version              Print version info and terminate.\n");
    puts("Logging:\n"
         "     --log-to-file          Write messages to file set in GWYDDION_LOGFILE.\n"
         "     --no-log-to-file       Do not write messages to any file.\n"
         "     --log-to-console       Print messages to console.\n"
         "     --no-log-to-console    Do not print messages to console.\n");
    puts("Miscellaneous options:\n"
         "     --no-splash            Don't show splash screen.\n"
         "     --disable-gl           Disable OpenGL, including any availability checks.\n"
         "     --disable-modules=MODNAME1,MODNAME2,...\n"
         "                            Prevent registration of given modules.\n"
         "     --startup-time         Measure time of startup tasks.\n");
    puts("Please report bugs to <" PACKAGE_BUGREPORT ">.");
}

static void
print_version(void)
{
    const gchar *verextra = "";
    gchar *s;

    s = version_date_info();
    if (!RELEASEDATE && strlen(GWY_VERSION_STRING) < 9)
        verextra = "+SVN";

    printf("%s %s%s (%s)\n", PACKAGE_NAME, GWY_VERSION_STRING, verextra, s);
    g_free(s);
}

static GPtrArray*
extend_disabled_modules(GPtrArray *module_list, const gchar *arg)
{
    if (!strlen(arg))
        return module_list;

    if (!module_list)
        module_list = g_ptr_array_new_with_free_func(g_free);

    if (!strchr(arg, ','))
        g_ptr_array_add(module_list, g_strdup(arg));
    else {
        gchar **modlist = g_strsplit(arg, ",", -1);
        for (guint i = 0; modlist[i]; i++)
            g_ptr_array_add(module_list, modlist[i]);
        g_free(modlist);
    }

    return module_list;
}

static void
timer_checkpoint(GwyddionApplication *gwyapp, const gchar *task)
{
    if (!gwyapp->startup_time)
        return;

    g_assert(gwyapp->timer);
    GTimer *timer = gwyapp->timer;
    gdouble t;

    gwyapp->total += t = g_timer_elapsed(timer, NULL);
    printf("%24s: %5.1f ms\n", task, 1000.0*t);
    g_timer_start(timer);
}

static void
timer_finish(GwyddionApplication *gwyapp, const gchar *task)
{
    if (!gwyapp->startup_time)
        return;

    printf("%24s: %5.1f ms\n", task, 1000.0*gwyapp->total);
    gwyapp->total = 0.0;
    g_assert(gwyapp->timer);
    g_timer_destroy(gwyapp->timer);
    gwyapp->timer = NULL;
}

static void
warn_broken_settings_file(GtkWidget *parent,
                          const gchar *settings_file,
                          const gchar *reason)
{
    GtkWidget *dialog;

    dialog = gtk_message_dialog_new(GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT,
                                    GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
                                    _("Could not read settings."));
    gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
                                             _("Settings file `%s' cannot be read: %s\n\n"
                                               "To prevent loss of saved settings no attempt to update it will "
                                               "be made until it is repaired or removed."),
                                             settings_file, reason);
    /* parent is usually in a screen corner, centering on it looks ugly */
    gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
    gtk_window_present(GTK_WINDOW(dialog));
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
}

static void
count_failures(gpointer mod_fail_info, gpointer user_data)
{
    GwyModuleFailureInfo *finfo = (GwyModuleFailureInfo*)mod_fail_info;
    guint *n = (guint*)user_data;

    /* Ignore user's modules. */
    if (!g_str_has_prefix(finfo->filename, gwy_get_user_dir()))
        (*n)++;
}

static void
check_broken_modules(GtkWidget *parent)
{
    GtkWidget *dialog;
    gchar *moduledir;
    guint n = 0;

    gwy_module_failure_foreach(count_failures, &n);
    /* Usually, the number should either be less than 3 or huge. */
    if (n < 8)
        return;

    dialog = gtk_message_dialog_new(GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT,
                                    GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
                                    _("Many modules (%u) failed to register."), n);
    moduledir = gwy_find_self_path("lib", "modules", NULL);
    gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
                                             _("Most likely Gwyddion was not upgraded correctly.  "
                                               "Instead, one installation was just overwritten with another, "
                                               "and now it is a mess.\n\n"
                                               "Please remove completely the module directory\n\n"
                                               "%s\n\n"
                                               "and reinstall Gwyddion.\n\n"
                                               "See Info → Module Browser for specific errors."),
                                             moduledir);
    g_free(moduledir);
    /* parent is usually in a screen corner, centering on it looks ugly */
    gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
    gtk_window_present(GTK_WINDOW(dialog));
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
}

static void
setup_locale_win32(void)
{
#ifdef G_OS_WIN32
    gchar locale[65];
    DWORD size = sizeof(locale)-1;
    HKEY reg_key;

    gwy_clear(locale, sizeof(locale));
    if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT(gwyddion_key), 0, KEY_READ, &reg_key) == ERROR_SUCCESS) {
        if (RegQueryValueEx(reg_key, TEXT("Locale"), NULL, NULL, locale, &size) == ERROR_SUCCESS) {
            g_setenv("LANG", locale, TRUE);
            RegCloseKey(reg_key);
            return;
        }
        RegCloseKey(reg_key);
    }
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(gwyddion_key), 0, KEY_READ, &reg_key) == ERROR_SUCCESS) {
        if (RegQueryValueEx(reg_key, TEXT("Locale"), NULL, NULL, locale, &size) == ERROR_SUCCESS)
            g_setenv("LANG", locale, TRUE);
        RegCloseKey(reg_key);
    }
#endif
}

static gboolean
open_directory_at_startup(gpointer user_data)
{
    GFile *dir_to_open = (GFile*)user_data;

    gchar *dirname_sys = g_file_get_path(dir_to_open);
    gwy_app_set_current_directory(dirname_sys);
    gwy_app_file_open();
    g_free(dirname_sys);
    g_object_unref(dir_to_open);

    return G_SOURCE_REMOVE;
}

static gint
process_files(GwyddionApplication *gwyapp, gchar **arguments)
{
    GwyddionMode mode = gwyapp->mode;

    if (mode != MODE_GUI && g_strv_length(arguments) < 2)
        return 0;

    gwyapp->disabled_modules = extend_disabled_modules(gwyapp->disabled_modules, "rawfile");
    block_modules(gwyapp->disabled_modules);

    gwy_app_init(FALSE, "no-logging-setup", TRUE, NULL);

    if (mode == MODE_CONVERT_TO_GWY3)
        return convert_files_to_gwy(arguments, gwyapp->convert_outfilename, 3);
    else if (mode == MODE_CONVERT_TO_GWY2)
        return convert_files_to_gwy(arguments, gwyapp->convert_outfilename, 2);

    gint status = 0;
    for (guint i = 1; arguments[i]; i++) {
        gchar *arg = arguments[i];
        if (mode == MODE_IDENTIFY)
            identify_file(arg, &status);
        else if (mode == MODE_CHECK)
            check_file(arg, &status);
        else
            g_assert_not_reached();
    }

    return status;
}

static void
block_modules(GPtrArray *module_list)
{
    if (module_list) {
        for (guint i = 0; i < module_list->len; i++)
            gwy_module_disable_registration(g_ptr_array_index(module_list, i));
    }
}

static void
gwy_app_set_window_icon(void)
{
    gchar *filename;
    GError *err = NULL;

    filename = gwy_find_self_path("data", "pixmaps", "gwyddion.ico", NULL);
    gtk_window_set_default_icon_from_file(filename, &err);
    if (err) {
        g_warning("Cannot load window icon: %s", err->message);
        g_clear_error(&err);
    }
    g_free(filename);
}

static void
gwy_app_check_version(void)
{
    if (!gwy_strequal(GWY_VERSION_STRING, gwy_version_string()))
        g_warning("Application and library versions do not match: %s vs. %s", GWY_VERSION_STRING, gwy_version_string());
}

static void
gwyddion_finalize(GObject *object)
{
    GwyddionApplication *gwyapp = GWYDDION_APPLICATION(object);

    g_free(gwyapp->convert_outfilename);
    if (gwyapp->disabled_modules)
        g_ptr_array_free(gwyapp->disabled_modules, TRUE);

    G_OBJECT_CLASS(parent_class)->finalize(object);
}

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
