/* Chrysalide - Outil d'analyse de fichiers binaires
 * preferences.c - boîte de dialogue d'édition des préférences de l'utilisateur
 *
 * Copyright (C) 2019-2025 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  Chrysalide 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.
 *
 *  Chrysalide 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 Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "preferences.h"


#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <gdk/gdkkeysyms.h>


#include <config.h>


#include "preferences-int.h"
#include "prefs/security.h"
#include "../../common/cpp.h"
#include "../../gtkext/tweak.h"
#include "../../plugins/pglist.h"
#include "../../plugins/tweakable.h"



/* --------------------------- BASES DE BOITE DE DIALOGUE --------------------------- */


/* Procède à l'initialisation de la fenêtre des paramètres. */
static void gtk_preferences_dialog_class_init(GtkPreferencesDialogClass *);

/* Procède à l'initialisation de la fenêtre des paramètres. */
static void gtk_preferences_dialog_init(GtkPreferencesDialog *);

/* Supprime toutes les références externes. */
static void gtk_preferences_dialog_dispose(GObject *);

/* Procède à la libération totale de la mémoire. */
static void gtk_preferences_dialog_finalize(GObject *);

/* Fournit la liste de section désignée par un nom. */
static GtkListBox *gtk_preferences_dialog_get_navigation(GtkPreferencesDialog *, const char *, bool);

/* Réagit à un changement de sélection dans les sections. */
static void gtk_preferences_dialog_on_row_selected(GtkListBox *, GtkListBoxRow *, GtkPreferencesDialog *);



/* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */





/* ---------------------------------------------------------------------------------- */
/*                             BASES DE BOITE DE DIALOGUE                             */
/* ---------------------------------------------------------------------------------- */


/* Détermine le type du composant d'affichage générique. */
G_DEFINE_TYPE(GtkPreferencesDialog, gtk_preferences_dialog, GTK_TYPE_WINDOW);


/******************************************************************************
*                                                                             *
*  Paramètres  : class = classe GTK à initialiser.                            *
*                                                                             *
*  Description : Procède à l'initialisation de la fenêtre des paramètres.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_preferences_dialog_class_init(GtkPreferencesDialogClass *class)
{
    GObjectClass *object;                   /* Plus haut niveau équivalent */
    GtkWidgetClass *widget;                 /* Classe de haut niveau       */

    object = G_OBJECT_CLASS(class);

    object->dispose = gtk_preferences_dialog_dispose;
    object->finalize = gtk_preferences_dialog_finalize;

    widget = GTK_WIDGET_CLASS(class);

    gtk_widget_class_set_template_from_resource(widget, "/re/chrysalide/framework/gui/dialogs/preferences.ui");

    gtk_widget_class_bind_template_child(widget, GtkPreferencesDialog, side_title);
    gtk_widget_class_bind_template_child(widget, GtkPreferencesDialog, side_content);
    gtk_widget_class_bind_template_child(widget, GtkPreferencesDialog, main_title);
    gtk_widget_class_bind_template_child(widget, GtkPreferencesDialog, main_content);

    /* Active une action native (cf. https://docs.gtk.org/gtk4/class.Window.html#actions) */
    gtk_widget_class_add_binding_action(widget, GDK_KEY_Escape, 0 /* GDK 4.14 : GDK_NO_MODIFIER_MASK */, "window.close", NULL);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : dialog = composant GTK à initialiser.                        *
*                                                                             *
*  Description : Procède à l'initialisation de la fenêtre des paramètres.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_preferences_dialog_init(GtkPreferencesDialog *dialog)
{
    size_t i;                               /* Boucle de parcours          */
    const tweak_info_t *info;               /* Informations à considérer   */
    GtkListBox *navigation;                 /* Liste de sections à afficher*/
    GtkTweakSection *section;               /* Nouvelle section à présenter*/
    tweak_info_t *dyn_infos;                /* Informations supplémentaires*/
    size_t dyn_count;                       /* Quantité de ces informations*/

    tweak_info_t infos[] = {
        TWEAK_SIMPLE_DEF("root", "Basics",
                         "security-high-symbolic", "security", "Security", GTK_TYPE_SECURITY_TWEAK_PANEL),
    };

    gtk_widget_init_template(GTK_WIDGET(dialog));

    dialog->navigations = g_hash_table_new_full(g_str_hash, g_str_equal, free, g_object_unref);

    /* Chargement des sections fixes */

    for (i = 0; i < ARRAY_SIZE(infos); i++)
    {
        info = &infos[i];

        navigation = gtk_preferences_dialog_get_navigation(dialog, info->parent, true);
        assert(navigation != NULL);

        section = gtk_tweak_section_new(info);

        gtk_list_box_append(navigation, GTK_WIDGET(section));

    }

    /* Chargement des sections dynamiques */

    dyn_infos = get_tweakable_plugins_info(&dyn_count);

    for (i = 0; i < dyn_count; i++)
    {
        info = &dyn_infos[i];

        navigation = gtk_preferences_dialog_get_navigation(dialog, info->parent, true);
        assert(navigation != NULL);

        section = gtk_tweak_section_new(info);

        gtk_list_box_append(navigation, GTK_WIDGET(section));

    }

    if (dyn_infos != NULL)
        free(dyn_infos);

    /* Affichage de la liste racine */

    navigation = gtk_preferences_dialog_get_navigation(dialog, "root", false);
    assert(navigation != NULL);

    g_signal_connect(navigation, "row-selected",
                     G_CALLBACK(gtk_preferences_dialog_on_row_selected), dialog);

    gtk_scrolled_window_set_child(dialog->side_content, GTK_WIDGET(navigation));

    unref_object(navigation);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : object = instance d'objet GLib à traiter.                    *
*                                                                             *
*  Description : Supprime toutes les références externes.                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_preferences_dialog_dispose(GObject *object)
{
    GtkPreferencesDialog *dialog;           /* Version spécialisée         */

    dialog = GTK_PREFERENCES_DIALOG(object);

    if (dialog->navigations != NULL)
    {
        /**
         * Cf. documentation de g_hash_table_new_full().
         */
        g_hash_table_remove_all(dialog->navigations);

        g_hash_table_unref(dialog->navigations);
        dialog->navigations = NULL;

    }

    gtk_widget_dispose_template(GTK_WIDGET(dialog), GTK_TYPE_PREFERENCES_DIALOG);

    G_OBJECT_CLASS(gtk_preferences_dialog_parent_class)->dispose(object);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : object = instance d'objet GLib à traiter.                    *
*                                                                             *
*  Description : Procède à la libération totale de la mémoire.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_preferences_dialog_finalize(GObject *object)
{
    G_OBJECT_CLASS(gtk_preferences_dialog_parent_class)->finalize(object);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : parent = fenêtre parente à surpasser.                        *
*                                                                             *
*  Description : Construit une boîte de dialogue pour les préférences.        *
*                                                                             *
*  Retour      : Adresse de la fenêtre mise en place.                         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GtkWindow *gtk_preferences_dialog_new(GtkWindow *parent)
{
    GtkWindow *result;                      /* Boite de dialogue à renvoyer*/

    result = g_object_new(GTK_TYPE_PREFERENCES_DIALOG, NULL);

    if (!gtk_preferences_dialog_create(GTK_PREFERENCES_DIALOG(result), parent))
        g_clear_object(&result);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : dialog = boîte de dialogue à initialiser pleinement.         *
*                parent = fenêtre parente à surpasser.                        *
*                                                                             *
*  Description : Met en place la boîte de dialogue pour les préférences.      *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool gtk_preferences_dialog_create(GtkPreferencesDialog *dialog, GtkWindow *parent)
{
    bool result;                            /* Bilan à retourner           */

    result = true;

    gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : dialog = boîte de dialogue à initialiser pleinement.         *
*                key    = désignation de la liste à fournir.                  *
*                create = autorisation d'une création si besoin est.          *
*                                                                             *
*  Description : Fournit la liste de section désignée par un nom.             *
*                                                                             *
*  Retour      : Composant graphique à utiliser.                              *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GtkListBox *gtk_preferences_dialog_get_navigation(GtkPreferencesDialog *dialog, const char *key, bool create)
{
    GtkListBox *result;                     /* Instance à retourner        */
#ifndef NDEBUG
    gboolean status;                        /* Bilan d'une insertion       */
#endif

    result = g_hash_table_lookup(dialog->navigations, key);

    if (result == NULL && create)
    {
        result = GTK_LIST_BOX(gtk_list_box_new());
        g_object_ref_sink(G_OBJECT(result));

        gtk_list_box_set_selection_mode(result, GTK_SELECTION_BROWSE);
        gtk_widget_add_css_class(GTK_WIDGET(result), "navigation-sidebar");

#ifndef NDEBUG
        status = g_hash_table_insert(dialog->navigations, strdup(key), result);
        assert(status);
#else
        g_hash_table_insert(dialog->navigations, key, result);
#endif

    }

    if (result != NULL)
        ref_object(result);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : navigation = liste concernée par l'événement.                *
*                selected   = élément sélectionné (voire NULL).               *
*                dialog     = boîte de dialogue à initialiser pleinement.     *
*                                                                             *
*  Description : Réagit à un changement de sélection dans les sections.       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_preferences_dialog_on_row_selected(GtkListBox *navigation, GtkListBoxRow *selected, GtkPreferencesDialog *dialog)
{
    GtkTweakSection *section;               /* Nature réelle sélectionnée  */
    GType type;                             /* Type de panneau de config.  */
    GtkWidget *panel;                       /* Nouveau panneau à présenter */

    if (selected != NULL)
    {
        if (!gtk_tweak_section_has_sub_section(GTK_TWEAK_SECTION(selected)))
        {
            section = GTK_TWEAK_SECTION(selected);

            gtk_label_set_text(dialog->main_title, gtk_tweak_section_get_label(section));

            type = gtk_tweak_section_get_panel(section);
            panel = g_object_new(type, NULL);

            gtk_scrolled_window_set_child(dialog->main_content, panel);

        }

        else
        {

            // TODO

        }

    }

}



/* ---------------------------------------------------------------------------------- */
/*                       IMPLEMENTATION DES FONCTIONS DE CLASSE                       */
/* ---------------------------------------------------------------------------------- */