/*
 *  $Id: about.c 30096 2026-06-21 11:32:46Z 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"
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/version.h"
#include "libgwyui/icons.h"
#include "libgwyapp/module-loader.h"
#include "libgwyapp/app.h"

#include "gwyddion/gwyddion.h"
#include "gwyddion/authors.h"
#include "gwyddion/release.h"
#include "gwyfeatures.h"

#define NA _("not available\n")

enum {
    GWY_ICON_SIZE_ABOUT = 60
};

static void           about_close              (void);
static void           fill_credits             (GtkTextBuffer *buffer);
static void           fill_features            (GtkTextBuffer *buffer);
static GtkTextBuffer* make_buffer_with_tags    (void);
static GtkWidget*     make_scrolled_text_window(GtkTextBuffer *buffer);
static void           construct_datetime_info  (GString *str);

static GtkWidget *about = NULL;

void
show_about_dialog(void)
{
    if (about) {
        gtk_window_present(GTK_WINDOW(about));
        return;
    }

    GString *str = g_string_new(NULL);
    g_string_printf(str, _("About %s"), g_get_application_name());
    about = gtk_dialog_new_with_buttons(str->str, gwy_app_main_window(), GTK_DIALOG_DESTROY_WITH_PARENT, NULL, NULL);
    GtkWindow *window = GTK_WINDOW(about);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(about), GWY_STOCK_CLOSE);
    gtk_dialog_set_default_response(GTK_DIALOG(about), GTK_RESPONSE_CLOSE);
    gtk_container_set_border_width(GTK_CONTAINER(about), 6);
    gtk_window_set_transient_for(window, gwy_app_main_window());
    gtk_window_set_position(window, GTK_WIN_POS_CENTER);
    gtk_window_add_accel_group(window, gwy_app_main_accel_group());

    GtkWidget *vbox = gtk_dialog_get_content_area(GTK_DIALOG(about));
    gtk_box_set_spacing(GTK_BOX(vbox), 8);

    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

    GtkWidget *widget = gtk_image_new_from_icon_name(GWY_ICON_GWYDDION, GTK_ICON_SIZE_DIALOG);
    gtk_image_set_pixel_size(GTK_IMAGE(widget), GWY_ICON_SIZE_ABOUT);

    gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
    gtk_widget_set_valign(widget, GTK_ALIGN_START);

    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);

    widget = gtk_label_new(NULL);
    gtk_label_set_xalign(GTK_LABEL(widget), 0.0);
    gwy_set_widget_margins(widget, 2, 2, 4, 4);
    gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);

    /* If we have unset release date but simple (non-date-extended) version string then we are not compiling from
     * a public tarball. */
    const gchar *verextra = "";
    if (RELEASEDATE == 0 && strlen(GWY_VERSION_STRING) < 9)
        verextra = "+SVN";
    g_string_printf(str, "<span size='x-large' weight='bold'>%s %s%s (unstable branch)</span>\n",
                    g_get_application_name(), GWY_VERSION_STRING, verextra);
    construct_datetime_info(str);
    g_string_append(str, _("An SPM data visualization and analysis tool."));
    gtk_label_set_markup(GTK_LABEL(widget), str->str);

    widget = gtk_link_button_new(PACKAGE_URL);
    gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);

    widget = gtk_label_new(NULL);
    gtk_label_set_xalign(GTK_LABEL(widget), 0.0);
    gwy_set_widget_margins(widget, 2, 2, 4, 4);
    gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);
    g_string_printf(str, "%s <i>%s</i>", _("Report bugs to:"), PACKAGE_BUGREPORT);
    gtk_label_set_markup(GTK_LABEL(widget), str->str);
    gtk_label_set_selectable(GTK_LABEL(widget), TRUE);

    GtkWidget *notebook = gtk_notebook_new();
    gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(about))), notebook, TRUE, TRUE, 0);

    /* Credits */
    GtkTextBuffer *buff = make_buffer_with_tags();
    fill_credits(buff);
    GtkWidget *scwin = make_scrolled_text_window(buff);
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scwin, gtk_label_new(_("Credits")));
    g_object_unref(buff);

    /* License */
    GtkTextIter iter;
    buff = make_buffer_with_tags();
    gtk_text_buffer_get_end_iter(buff, &iter);
    g_string_printf(str,
                    _("%s 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. "
                      "For full license text see file COPYING included in the source tarball."),
                    g_get_application_name());
    gtk_text_buffer_insert(buff, &iter, str->str, str->len);
    gtk_text_buffer_insert(buff, &iter, " ", -1);
    gtk_text_buffer_insert(buff, &iter, _("Or read the license at"), -1);
    gtk_text_buffer_insert(buff, &iter, " ", -1);
    gtk_text_buffer_insert_with_tags_by_name(buff, &iter, "https://www.gnu.org/licenses/gpl-2.0.html", -1, "uri", NULL);

    scwin = make_scrolled_text_window(buff);
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scwin, gtk_label_new(_("License")));
    g_object_unref(buff);

    /* Features */
    buff = make_buffer_with_tags();
    fill_features(buff);
    scwin = make_scrolled_text_window(buff);
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scwin, gtk_label_new(_("Features")));
    g_object_unref(buff);

    g_string_free(str, TRUE);

    gtk_widget_show_all(about);

    g_signal_connect(about, "delete-event", G_CALLBACK(about_close), NULL);
    g_signal_connect(about, "response", G_CALLBACK(about_close), NULL);
}

static void
about_close(void)
{
    gtk_widget_destroy(about);
    about = NULL;
}

static void
add_credits_block(GtkTextBuffer *buffer,
                  const gchar *title,
                  const gchar *body,
                  gboolean italicize)
{
    GtkTextIter iter;

    gtk_text_buffer_get_end_iter(buffer, &iter);
    gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, title, -1, "b", NULL);
    gtk_text_buffer_insert(buffer, &iter, "\n", 1);
    if (italicize)
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, body, -1, "i", NULL);
    else
        gtk_text_buffer_insert(buffer, &iter, body, -1);
    if (g_str_has_suffix(body, "\n"))
        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
    else
        gtk_text_buffer_insert(buffer, &iter, "\n\n", 2);
}

static void
fill_credits(GtkTextBuffer *buffer)
{
    GtkTextIter iter;

    add_credits_block(buffer, _("Developers"), developers, FALSE);
    add_credits_block(buffer, _("Translators"), translators, FALSE);

    gtk_text_buffer_get_end_iter(buffer, &iter);
    gtk_text_buffer_insert(buffer, &iter, _("Development is supported by the Czech Metrology Institute: "), -1);
    gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "https://cmi.gov.cz/", -1, "uri", NULL);
}

static GtkTextBuffer*
make_buffer_with_tags(void)
{
    GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);

    gtk_text_buffer_create_tag(buffer, "uri", "style", PANGO_STYLE_ITALIC, "wrap-mode", GTK_WRAP_NONE, NULL);
    gtk_text_buffer_create_tag(buffer, "b", "weight", PANGO_WEIGHT_BOLD, NULL);
    gtk_text_buffer_create_tag(buffer, "i", "style", PANGO_STYLE_ITALIC, NULL);
    return buffer;
}

static GtkWidget*
make_scrolled_text_window(GtkTextBuffer *buffer)
{
    GtkWidget *text, *scwin;

    scwin = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    gtk_widget_set_size_request(scwin, 340, 220);

    text = gtk_text_view_new_with_buffer(buffer);
    gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
    gtk_container_add(GTK_CONTAINER(scwin), text);

    return scwin;
}

static void
add_file_format_block(GtkTextBuffer *buffer, const gchar *what, gboolean enabled)
{
    gchar *s = g_strdup_printf(_("%s file formats"), what);
    add_credits_block(buffer, s, enabled ? _("enabled") : _("disabled"), !enabled);
    g_free(s);
}

static void
fill_features(GtkTextBuffer *buffer)
{
    G_GNUC_UNUSED static const GwyEnum openmp_vers[] = {
        { "6.0", 202411 },
        { "5.2", 202111 },
        { "5.1", 202011 },
        { "5.0", 201811 },
        { "4.5", 201511 },
        { "4.0", 201307 },
        { "3.1", 201107 },
        { "3.0", 200805 },
        { "2.5", 200505 },
        { "2.0", 200203 },
        { "2.0", 200011 },  /* FORTRAN, probably irrelevant */
        { "1.1", 199911 },  /* FORTRAN, probably irrelevant */
        { "1.0", 199810 },
        { "1.0", 199710 },  /* FORTRAN, probably irrelevant */
    };
    const GwyModuleInfo *modinfo;
    G_GNUC_UNUSED gchar *s;
    G_GNUC_UNUSED const gchar *cs;

#ifdef _OPENMP
    cs = gwy_enum_to_string(_OPENMP, openmp_vers, G_N_ELEMENTS(openmp_vers));
    if (cs && *cs)
        s = g_strdup_printf("%s (%d)\n", cs, _OPENMP);
    else
        s = g_strdup_printf("%d\n", _OPENMP);
    add_credits_block(buffer, _("OpenMP parallelization"), s, FALSE);
    g_free(s);
#else
    add_credits_block(buffer, _("OpenMP parallelization"), NA, TRUE);
#endif

    add_credits_block(buffer, _("16bit image formats"),
                      GWY_APP_FEATURE_have_cxx ? _("enabled") : _("disabled"),
                      !GWY_APP_FEATURE_have_cxx);

    add_file_format_block(buffer, "Bzip2", GWY_APP_FEATURE_enable_bzip2);
    add_file_format_block(buffer, "CFITSIO", GWY_APP_FEATURE_enable_cfitsio);
    add_file_format_block(buffer, "HDF5", GWY_APP_FEATURE_enable_hdf5);
    add_file_format_block(buffer, "JSON-glib", GWY_APP_FEATURE_enable_json_glib);
    add_file_format_block(buffer, "LibXML2", GWY_APP_FEATURE_enable_libxml2);
    add_file_format_block(buffer, "OpenEXR", GWY_APP_FEATURE_enable_exr);
    add_file_format_block(buffer, "WebP", GWY_APP_FEATURE_enable_webp);
    add_file_format_block(buffer, "Zstd", GWY_APP_FEATURE_enable_zstd);

    if (GWY_APP_FEATURE_found_zip) {
        s = g_strdup_printf(_("%s file formats (%s)"), "ZIP", GWY_APP_FEATURE_zip_library);
        add_credits_block(buffer, s, _("enabled"), FALSE);
        g_free(s);
    }
    else
        add_file_format_block(buffer, "ZIP", FALSE);

    if ((modinfo = gwy_module_lookup("pygwy"))) {
        s = g_strdup_printf("pygwy %s\n", modinfo->version);
        add_credits_block(buffer, _("Python Scripting Interface"), s, FALSE);
        g_free(s);
    }
    else
        add_credits_block(buffer, _("Python Scripting Interface"), NA, TRUE);

    s = g_strconcat(GWY_APP_FEATURE_enable_introspection ? "+" : "-", "introspection", " ",
                    GWY_APP_FEATURE_enable_module_bundling ? "+" : "-", "module-bundling", " ",
                    GWY_APP_FEATURE_enable_library_bloat ? "+" : "-", "library-bloat",
                    NULL);

    add_credits_block(buffer, _("Build settings"), s, FALSE);

}

/* The __DATE__ macro is pretty well specified to be a silly American date. Unlike date & time parsing functions,
 * which are an utter locale-dependent non-portable mess.  Just parse it manually.  Can't use g_date_set_parse()
 * because that is also locale-dependent. */
static gboolean
parse_standard_date(const gchar *datestr, guint *y, guint *m, guint *d)
{
    static const gchar months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
    gchar mstr[4];
    const gchar *p;

    if (sscanf(datestr, "%3s %u %u", mstr, d, y) == 3
        && strlen(mstr) == 3
        && (p = strstr(months, mstr))
        && (p - months) % 3 == 0) {
        *m = (p - months)/3 + 1;
        return TRUE;
    }
    return FALSE;
}

gchar*
version_date_info(void)
{
    guint y, m, d;

    if (RELEASEDATE)
        return g_strdup_printf("%04u-%02u-%02u", RELEASEDATE/10000u, RELEASEDATE/100u % 100u, RELEASEDATE % 100u);

    if (parse_standard_date(__DATE__, &y, &m, &d))
        return g_strdup_printf("%04u-%02u-%02u", y, m, d);

    g_warning("Build date %s is invalid.", __DATE__);
    /* Avoid trigraphs. */
    return g_strdup("????" "-" "??" "-" "??");
}

static void
construct_datetime_info(GString *str)
{
    gchar *s = version_date_info();

    g_string_append(str, "<b>");
    if (RELEASEDATE) {
        /* TRANSLATORS: %s is replaced with date in ISO format YYYY-MM-DD. */
        g_string_append_printf(str, _("Released %s"), s);
    }
    else {
        /* TRANSLATORS: %s is replaced with date in ISO format YYYY-MM-DD. */
        g_string_append_printf(str, _("Development version, built %s"), s);
    }
    g_string_append(str, "</b>\n");
    g_free(s);
}

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