/* Chrysalide - Outil d'analyse de fichiers binaires
 * app.c - fichier d'entrée du programme
 *
 * Copyright (C) 2024 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <string.h>
#include <gio/gdesktopappinfo.h>
#include <gio/gio.h>
#include <sys/auxv.h>


#include "app.h"
#include "common/io.h"
#include "common/xdg.h"
#include "core/core.h"
#include "core/logs.h"
#include "gui/core/core.h"
#include "gui/window.h"
#include "plugins/pglist.h"



/* --------------------- DEFINITION D'APPLICATION PERSONNALISEE --------------------- */


/* Définition de l'application principale graphique (instance) */
struct _GtkChrysalideFramework
{
    GtkApplication parent;                  /* A laisser en premier        */

    GtkApplicationWindow *main_window;      /* Fenêtre principale          */

};

/* Définition de l'application principale graphique (classe) */
struct _GtkChrysalideFrameworkClass
{
    GtkApplicationClass parent;             /* A laisser en premier        */

};


/* Initialise la classe des applications majeures de Chrysalide. */
static void gtk_chrysalide_framework_class_init(GtkChrysalideFrameworkClass *);

/* Initialise une application principale pour Chrysalide. */
static void gtk_chrysalide_framework_init(GtkChrysalideFramework *);

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

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



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


/* Réagit à l'activation de l'application. */
static void gtk_chrysalide_framework_activate(GApplication *);



/* ---------------------- POINT D'ENTREE PRINCIPAL D'EXECUTION ---------------------- */


/* Affiche des indications quant à l'utilisation du programme. */
static void show_chrysalide_help(const char *);

/* Affiche des indications sur la version courante du programme. */
static void show_chrysalide_version(void);

/* Installe au besoin une définition locale pour le système. */
static void ensure_wm_icon_and_name(void);



/* ---------------------------------------------------------------------------------- */
/*                       DEFINITION D'APPLICATION PERSONNALISEE                       */
/* ---------------------------------------------------------------------------------- */


/* Indique le type défini pour une application principale graphique de Chrysalide. */
G_DEFINE_TYPE(GtkChrysalideFramework, gtk_chrysalide_framework, GTK_TYPE_APPLICATION);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des applications majeures de Chrysalide.*
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_chrysalide_framework_class_init(GtkChrysalideFrameworkClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */
    GApplicationClass *app;                 /* Version parente de la classe*/

    object = G_OBJECT_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)gtk_chrysalide_framework_dispose;
    object->finalize = (GObjectFinalizeFunc)gtk_chrysalide_framework_finalize;

    app = G_APPLICATION_CLASS(klass);

    app->activate = gtk_chrysalide_framework_activate;


}


/******************************************************************************
*                                                                             *
*  Paramètres  : app = instance à initialiser.                                *
*                                                                             *
*  Description : Initialise une application principale pour Chrysalide.       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_chrysalide_framework_init(GtkChrysalideFramework *app)
{
    app->main_window = NULL;

}


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

static void gtk_chrysalide_framework_dispose(GtkChrysalideFramework *app)
{
    g_clear_object(&app->main_window);

    G_OBJECT_CLASS(gtk_chrysalide_framework_parent_class)->dispose(G_OBJECT(app));

}


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

static void gtk_chrysalide_framework_finalize(GtkChrysalideFramework *app)
{
    G_OBJECT_CLASS(gtk_chrysalide_framework_parent_class)->finalize(G_OBJECT(app));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Crée une nouvelle application principale pour Chrysalide.    *
*                                                                             *
*  Retour      : Mécanismes mis en place.                                     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GtkChrysalideFramework *gtk_chrysalide_framework_new(void)
{
    GtkChrysalideFramework *result;         /* Instance à retourner        */

    result = g_object_new(GTK_TYPE_CHRYSALIDE_FRAMEWORK,
                          "application-id", FRAMEWORK_WINDOW_ID,
                          "flags", G_APPLICATION_DEFAULT_FLAGS,
                          NULL);

    return result;

}



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


/******************************************************************************
*                                                                             *
*  Paramètres  : app = application concernée par l'événement.                 *
*                                                                             *
*  Description : Réagit à l'activation de l'application.                      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_chrysalide_framework_activate(GApplication *app)
{
    GtkChrysalideFramework *real_app;       /* Version réelle de l'instance*/

    real_app = GTK_CHRYSALIDE_FRAMEWORK(app);

    real_app->main_window = gtk_framework_window_new(GTK_APPLICATION(app));
    g_object_ref(G_OBJECT(real_app->main_window));

    gtk_window_present(GTK_WINDOW(real_app->main_window));

}



/* ---------------------------------------------------------------------------------- */
/*                        POINT D'ENTREE PRINCIPAL D'EXECUTION                        */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : name = nom du programme en question.                         *
*                                                                             *
*  Description : Affiche des indications quant à l'utilisation du programme.  *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void show_chrysalide_help(const char *name)
{
    char *tmp;                              /* Conservation modifiable     */
    char *base;                             /* Version courte du nom       */

    tmp = strdup(name);

    base = basename(tmp);

    printf("\n");

    printf("Usage: %s [--help] [--version]\n", base);
    printf("       %s [args] <filename(s)...>\n", base);

    free(tmp);

    printf("\n");

    printf("\t-h --help\t\tShow this help message.\n");
    printf("\t-v --version\t\tDisplay the program version.\n");

    printf("\n");

    printf("\t-V --verbosity=level\tSet the log level (0 for all messages, %u for none).\n", LMT_COUNT);

    printf("\n");

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Affiche des indications sur la version courante du programme.*
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void show_chrysalide_version(void)
{
    printf("\n");

    printf("-o-  Chrysalide r%u  -o-\n", REVISION);
    printf(_("Compiled on %s at %s\n"), __DATE__, __TIME__);

    printf("\n");

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Installe au besoin une définition locale pour le système.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void ensure_wm_icon_and_name(void)
{
    GDesktopAppInfo *info;                  /* Information du système      */
    GKeyFile *kfile;                        /* Définition d'application    */
    unsigned long exec_path;                /* Chemin du programme         */
    char *filename;                         /* Nom de fichier à écrire     */
    GBytes *res;                            /* Données brutes d'une image  */
    gsize size;                             /* Taille de ces données       */
    gconstpointer data;                     /* Pointeur vers les données   */
    int fd;                                 /* Flux ouvert en écriture     */

    /* Evaluation du besoin */

    info = g_desktop_app_info_new(FRAMEWORK_WINDOW_ID ".desktop");

    /**
     * Si l'exécutable n'est pas valide (inconnu de $PATH),
     * la variable info n'est pas initialisée.
     */

    if (info != NULL)
    {
        unref_object(info);
        goto done;
    }

    /* Mise en place d'une définition d'application */

    exec_path = getauxval(AT_EXECFN);
    assert(exec_path != 0);

    kfile = g_key_file_new();

    g_key_file_set_string(kfile, "Desktop Entry", "Name", "Chrysalide");
    g_key_file_set_string(kfile, "Desktop Entry", "Comment[fr]", "Cadriciel de rétronception ciblant principalement les systèmes embarqués");
    g_key_file_set_string(kfile, "Desktop Entry", "Comment", "Reverse Engineering Framework focused on embedded systems");
    g_key_file_set_string(kfile, "Desktop Entry", "Type", "Application");
    g_key_file_set_string(kfile, "Desktop Entry", "Exec", (const char *)exec_path);
    g_key_file_set_string(kfile, "Desktop Entry", "Icon", "chrysalide-logo");
    g_key_file_set_string(kfile, "Desktop Entry", "StartupNotify", "true");
    g_key_file_set_string(kfile, "Desktop Entry", "MimeType", "application/vnd.android.package-archive");

    filename = get_xdg_data_dir("applications/re.chrysalide.framework.gui.desktop", true);

    g_key_file_save_to_file(kfile, filename, NULL);

    free(filename);

    g_key_file_free(kfile);

    /* Ecriture de l'image */

    res = g_resources_lookup_data("/re/chrysalide/framework/images/chrysalide-logo.svg",
                                  G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
    assert(res != NULL);

    data = g_bytes_get_data(res, &size);

    filename = get_xdg_data_dir("icons/hicolor/scalable/apps/chrysalide-logo.svg", true);

    fd = open(filename, O_WRONLY | O_CREAT);

    if (fd == -1)
        LOG_ERROR_N("open");

    else
    {
        safe_write(fd, data, size);
        close(fd);
    }

    free(filename);

    g_bytes_unref(res);

    /* Sortie */

 done:

    ;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : argc = nombre d'arguments dans la ligne de commande.         *
*                argv = arguments de la ligne de commande.                    *
*                                                                             *
*  Description : Point d'entrée du programme.                                 *
*                                                                             *
*  Retour      : EXIT_SUCCESS si le prgm s'est déroulé sans encombres.        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

int main(int argc, char **argv)
{
    int result;                             /* Bilan de l'exécution        */
    bool show_help;                         /* Affichage de l'aide ?       */
    bool show_version;                      /* Affichage de la version ?   */
    LogMessageType verbosity;               /* Niveau de filtre de message */
    int index;                              /* Indice d'argument           */
    int ret;                                /* Bilan d'un appel            */
    GtkChrysalideFramework *app;            /* Gestion d'application GTK   */

    static struct option long_options[] = {
        { "help",       no_argument,        NULL,   'h' },
        { "version",    no_argument,        NULL,   'v' },
        { "verbosity",  required_argument,  NULL,   'V' },
        { NULL,         0,                  NULL,   0 }
    };

    result = EXIT_FAILURE;

    /* Décodage des options */

    show_help = false;
    show_version = false;

    verbosity = LMT_COUNT;

    while (true)
    {
        ret = getopt_long(argc, argv, "hvV:", long_options, &index);
        if (ret == -1) break;

        switch (ret)
        {
            case 'h':
                show_help = true;
                break;

            case 'v':
                show_version = true;
                break;

            case 'V':
                verbosity = strtoul(optarg, NULL, 10);
                break;

        }

    }

    /* Actions de base */

    if (show_help)
    {
        show_chrysalide_help(argv[0]);
        result = EXIT_SUCCESS;
        goto exit;
    }

    if (show_version)
    {
        show_chrysalide_version();
        result = EXIT_SUCCESS;
        goto exit;
    }

    /* Lancement des choses sérieuses */

    set_log_verbosity(verbosity);

    if (!load_core_components(ACC_GLOBAL_VARS))
        goto exit;

    if (!load_gui_components(AGC_BUFFER_FEATURES | AGC_PANELS))
        goto exit_with_core;

    init_all_plugins(true);

    ensure_wm_icon_and_name();

    g_set_prgname("Chrysalide");

    app = gtk_chrysalide_framework_new();

    result = g_application_run(G_APPLICATION(app), 0, NULL);

    g_object_unref(G_OBJECT(app));

    exit_all_plugins();

    unload_gui_components(AGC_BUFFER_FEATURES | AGC_PANELS);

 exit_with_core:

    unload_core_components(ACC_GLOBAL_VARS);

 exit:

    return result;

}