/* Chrysalide - Outil d'analyse de fichiers binaires
 * helpers.h - prototypes pour la simplification des interactions de base avec GLib
 *
 * 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 Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#ifndef _GLIBEXT_HELPERS_H
#define _GLIBEXT_HELPERS_H


#include <assert.h>
#include <glib-object.h>


#include "../common/compiler.h"



/**
 * Les définitions issues de <glib-2.80>/gobject/gtype.h fournissent des macros
 * facilitant la déclaration de types pour entêtes. Cependant :
 *
 *    - G_DECLARE_FINAL_TYPE impose une structure de classe fixe ;
 *    - G_DECLARE_DERIVABLE_TYPE impose une structure d'objet fixe.
 *
 * Ces deux macros ne peuvent donc pas être employées en l'état dans Chrysalide.
 *
 * Par ailleurs, la fonctionnalité g_autoptr() n'offre pas une séduction totale :
 * elle conduit à un code inconsistant, avec parfois des libérations explicites,
 * parfois des libérations invisible gérées par le compilateur. Cet aspect
 * fonctionnel offert par les macros GLib est donc inutile pour Chrysalide.
 *
 * Une nouvelle macro de déclaration est ainsi constituée ici.
 *
 * De fait de l'absence d'argument, l'inclusion de la définition suivante n'est
 * pas possible :
 *
 *    MOD##_TYPE_##NAME t_n##_get_type()
 *
 * La macro traditionnelle doit ainsi être fournie en parallèle à la déclaration
 * condensée ici. Elle y est d'ailleurs référencée, forçant une déclaration
 * préalable de manière globale et cohérente dans l'ensemble du code d'emploi.
 */

#define DECLARE_GTYPE(TN, t_n, MOD, NAME)                                               \
                                                                                        \
    GType t_n##_get_type(void) G_GNUC_CONST;                                            \
                                                                                        \
    typedef struct _##TN TN;                                                            \
    typedef struct _##TN##Class TN##Class;                                              \
                                                                                        \
    G_GNUC_UNUSED static inline TN *MOD##_##NAME(gconstpointer obj)                     \
    {                                                                                   \
        return G_TYPE_CHECK_INSTANCE_CAST(obj, MOD##_TYPE_##NAME, TN);                  \
    }                                                                                   \
                                                                                        \
    G_GNUC_UNUSED static inline TN##Class *MOD##_##NAME##_CLASS(gconstpointer klass)    \
    {                                                                                   \
        return G_TYPE_CHECK_CLASS_CAST(klass, MOD##_TYPE_##NAME, TN##Class);            \
    }                                                                                   \
                                                                                        \
    G_GNUC_UNUSED static inline gboolean MOD##_IS_##NAME(gconstpointer obj)             \
    {                                                                                   \
        return G_TYPE_CHECK_INSTANCE_TYPE(obj, MOD##_TYPE_##NAME);                      \
    }                                                                                   \
                                                                                        \
    G_GNUC_UNUSED static inline gboolean MOD##_IS_##NAME##_CLASS(gconstpointer klass)   \
    {                                                                                   \
        return G_TYPE_CHECK_CLASS_TYPE(klass, MOD##_TYPE_##NAME);                       \
    }                                                                                   \
                                                                                        \
    G_GNUC_UNUSED static inline TN##Class *MOD##_##NAME##_GET_CLASS(gconstpointer obj)  \
    {                                                                                   \
        return G_TYPE_INSTANCE_GET_CLASS(obj, MOD##_TYPE_##NAME, TN##Class);            \
    }


/**
 * Bis repetita pour les interfaces...
 */

#define DECLARE_INTERFACE(TN, t_n, MOD, NAME)                                               \
                                                                                            \
    GType t_n##_get_type(void) G_GNUC_CONST;                                                \
                                                                                            \
    /* Coquille vide */                                                                     \
    typedef struct _##TN TN;                                                                \
    /* Interface */                                                                         \
    typedef struct _##TN##Interface TN##Interface;                                          \
                                                                                            \
    G_GNUC_UNUSED static inline TN *MOD##_##NAME(gconstpointer obj)                         \
    {                                                                                       \
        return G_TYPE_CHECK_INSTANCE_CAST(obj, MOD##_TYPE_##NAME, TN);                      \
    }                                                                                       \
                                                                                            \
    G_GNUC_UNUSED static inline gboolean MOD##_IS_##NAME(gconstpointer obj)                 \
    {                                                                                       \
        return G_TYPE_CHECK_INSTANCE_TYPE(obj, MOD##_TYPE_##NAME);                          \
    }                                                                                       \
                                                                                            \
    G_GNUC_UNUSED static inline TN##Interface *MOD##_##NAME##_GET_IFACE(gconstpointer obj)  \
    {                                                                                       \
        return G_TYPE_INSTANCE_GET_INTERFACE(obj, MOD##_TYPE_##NAME, TN##Interface);        \
    }


/**
 * Définition sous condition d'une inclusion d'interface. Cette inclusion se réalise
 * lorsque la fonction d'initialisation renseignée est définie.
 *
 * Cette version étendue de la macro G_IMPLEMENT_INTERFACE d'origine est principalement
 * pour les raffinements d'objets en forme graphique.
 */

#define G_IMPLEMENT_INTERFACE_IF_SYM(iface_tp_getter, iface_init)                           \
    do                                                                                      \
    {                                                                                       \
        extern GType iface_tp_getter(void) __weak;                                          \
        extern void iface_init(GTypeInterface *, gpointer) __weak;                          \
        if (&iface_tp_getter != NULL && &iface_init != NULL)                                \
        {                                                                                   \
            GType iface_type = iface_tp_getter();                                           \
            const GInterfaceInfo implementation_info = {                                    \
                (GInterfaceInitFunc)(void (*)(void))iface_init, NULL, NULL                  \
            };                                                                              \
            g_type_add_interface_static(g_define_type_id, iface_type, &implementation_info);\
        }                                                                                   \
    }                                                                                       \
    while (0);



/**
 * Les principales fonctions incrémentant ou réduisant le nombre de références
 * attachées à un objet acceptent de simples pointeurs génériques (cf. définitions
 * du fichier <glib-2.80>/gobject/gobject.h :
 *
 *    [...]
 *    void        g_object_notify_by_pspec          (GObject        *object,
 *                                                   GParamSpec     *pspec);
 *    void        g_object_thaw_notify              (GObject        *object);
 *    gboolean    g_object_is_floating              (gpointer        object);
 *    gpointer    g_object_ref_sink                 (gpointer        object);
 *    gpointer    g_object_take_ref                 (gpointer        object);
 *    gpointer    g_object_ref                      (gpointer        object);
 *    void        g_object_unref                    (gpointer        object);
 *    void        g_object_weak_ref                 (GObject        *object,
 *                                                   GWeakNotify     notify,
 *                                                   gpointer        data);
 *    [...]
 *
 * La fonction g_object_unref() débute bien par exemple par une validation
 * de l'instance, avec un appel à : g_return_if_fail (G_IS_OBJECT (object)).
 *
 * Cependant, cette vérification est désactivée en cas de compilation de GLib
 * avec G_DISABLE_CHECKS.
 *
 * Une conversion vers un type donné (par exemple avec G_OBJECT()) est par
 * ailleurs dépendante d'autres paramètres de compilation, comme le révèle
 * le fichier <glib-2.80>/gobject/gtype.h :
 *
 *    #if defined(G_DISABLE_CAST_CHECKS) || defined(__OPTIMIZE__)
 *    #  define _G_TYPE_CIC(ip, gt, ct)       ((ct*) (void *) ip)
 *    #  define _G_TYPE_CCC(cp, gt, ct)       ((ct*) (void *) cp)
 *    #else
 *    #  define _G_TYPE_CIC(ip, gt, ct) \
 *        ((ct*) (void *) g_type_check_instance_cast ((GTypeInstance*) ip, gt))
 *    #  define _G_TYPE_CCC(cp, gt, ct) \
 *        ((ct*) (void *) g_type_check_class_cast ((GTypeClass*) cp, gt))
 *    #endif
 *
 * Afin d'assurer des conditions de débogage/fuzzing optimales dans tous les
 * cas de figure, les fonctions de manipulation des références font l'objet
 * ici d'une couche intermédiaire pour les appels.
 */

#ifndef NDEBUG

#   define ref_object(ip)                                                           \
    do                                                                              \
    {                                                                               \
        GTypeInstance *__inst;                                                      \
        __inst = g_type_check_instance_cast((GTypeInstance *)ip, G_TYPE_OBJECT);    \
        assert(__inst != NULL);                                                     \
        g_object_ref(ip);                                                           \
    }                                                                               \
    while (0)

#   define unref_object(ip)                                                         \
    do                                                                              \
    {                                                                               \
        GTypeInstance *__inst;                                                      \
        __inst = g_type_check_instance_cast((GTypeInstance *)ip, G_TYPE_OBJECT);    \
        assert(__inst != NULL);                                                     \
        g_object_unref(ip);                                                         \
    }                                                                               \
    while (0)

#else

#   define ref_object(p) g_object_ref(p)
#   define unref_object(p) g_object_unref(p)

#endif



#endif  /* _GLIBEXT_HELPERS_H */