/* Chrysalide - Outil d'analyse de fichiers binaires
 * hexview.c - affichage de contenus de binaire
 *
 * Copyright (C) 2016-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/>.
 */


#include "hexview.h"


#include <alloca.h>
#include <sys/param.h>


#include "area.h"
#include "hexview-int.h"
#include "../glibext/options/hex.h"



/* ------------------------- BASES D'UN COMPOSANT GRAPHIQUE ------------------------- */


/* Initialise la classe des afficheurs de tampons bruts. */
static void gtk_hex_view_class_init(GtkHexViewClass *);

/* Initialise une instance d'afficheur de tampons bruts. */
static void gtk_hex_view_init(GtkHexView *);

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

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

/* Procède à l'actualisation de l'affichage d'un sous-composant. */
static void gtk_hex_view_dispatch_sub_snapshot(GtkWidget *, GtkSnapshot *, GtkWidget *);

/* Adapte le cache de lignes hexadécimales à la taille courante. */
static void gtk_hex_view_populate_cache(GtkHexView *);



void demo_snapshot (GtkWidget *widget, GtkSnapshot *snapshot, GtkWidget *parent);





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


/* Prend acte de la taille allouée au composant d'affichage. */
static void gtk_hex_view_size_allocate(GtkWidget *, int, int, int);

/* Fournit le mode de calcul pour déterminer la taille. */
static GtkSizeRequestMode gtk_hex_view_get_request_mode(GtkWidget *);

/* Fournit les mesures mainimale et idéale du composant. */
static void gtk_hex_view_measure(GtkWidget *, GtkOrientation, int, int *, int *, int *, int *);



/* ---------------------------------------------------------------------------------- */
/*                           BASES D'UN COMPOSANT GRAPHIQUE                           */
/* ---------------------------------------------------------------------------------- */


/* Détermine le type du composant d'affichage générique. */
G_DEFINE_TYPE(GtkHexView, gtk_hex_view, GTK_TYPE_BUFFER_VIEW);


/******************************************************************************
*                                                                             *
*  Paramètres  : class = classe GTK à initialiser.                            *
*                                                                             *
*  Description : Initialise la classe des afficheurs de tampons bruts.        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

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

    object = G_OBJECT_CLASS(class);

    object->dispose = (GObjectFinalizeFunc/* ! */)gtk_hex_view_dispose;
    object->finalize = (GObjectFinalizeFunc)gtk_hex_view_finalize;

    widget = GTK_WIDGET_CLASS(class);

    // REMME gtk_widget_class_set_css_name(widget, "GtkHexView");

    g_type_ensure(GTK_TYPE_COMPOSING_AREA);

    gtk_widget_class_set_template_from_resource(widget, "/re/chrysalide/framework/gtkext/hexview.ui");

    gtk_widget_class_bind_template_child(widget, GtkHexView, offsets);
    gtk_widget_class_bind_template_child(widget, GtkHexView, hex);
    gtk_widget_class_bind_template_child(widget, GtkHexView, ascii);

    widget->size_allocate = gtk_hex_view_size_allocate;
    widget->get_request_mode = gtk_hex_view_get_request_mode;
    widget->measure = gtk_hex_view_measure;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view = composant GTK à initialiser.                          *
*                                                                             *
*  Description : Initialise une instance d'afficheur de tampons bruts.        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_hex_view_init(GtkHexView *view)
{
    GtkContentView *base;                   /* Base d'instance supérieure  */

    /* Niveau supérieur */

    base = GTK_CONTENT_VIEW(view);

    base->options = G_DISPLAY_OPTIONS(g_hex_options_new());

    base->sub_children = view->children;
    base->sub_count = _CHILDREN_COUNT;

    /* Instance courante */

    gtk_widget_init_template(GTK_WIDGET(view));

    g_raw_scan_cache_register_snapshot(GTK_COMPOSING_AREA(view->offsets),
                                       gtk_hex_view_dispatch_sub_snapshot, GTK_WIDGET(view));

    g_raw_scan_cache_register_snapshot(GTK_COMPOSING_AREA(view->hex),
                                       gtk_hex_view_dispatch_sub_snapshot, GTK_WIDGET(view));

    g_raw_scan_cache_register_snapshot(GTK_COMPOSING_AREA(view->ascii),
                                       gtk_hex_view_dispatch_sub_snapshot, GTK_WIDGET(view));

    view->generator = NULL;

}


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

static void gtk_hex_view_dispose(GtkHexView *view)
{
    gtk_widget_dispose_template(GTK_WIDGET(view), GTK_TYPE_HEX_VIEW);

    g_clear_object(&view->generator);

    G_OBJECT_CLASS(gtk_hex_view_parent_class)->dispose(G_OBJECT(view));

}


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

static void gtk_hex_view_finalize(GtkHexView *view)
{
    G_OBJECT_CLASS(gtk_hex_view_parent_class)->finalize(G_OBJECT(view));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : content = contenu binaire à exposer de façon brute.          *
*                                                                             *
*  Description : Crée un composant d'affichage d'octets bruts et imprimables. *
*                                                                             *
*  Retour      : Centralisateur mis en place pour un composant GTK donné.     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GtkHexView *gtk_hex_view_new(GBinContent *content)
{
    GtkHexView *result;                     /* Nouvelle instance à renvoyer*/

    result = g_object_new(GTK_TYPE_HEX_VIEW, NULL);

    if (!gtk_hex_view_create(result, content))
        g_clear_object(&result);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view    = composant d'affichage à initialiser pleinement.    *
*                content = contenu binaire à exposer de façon brute.          *
*                                                                             *
*  Description : Met en place un nouveau composant d'affichage d'octets bruts.*
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool gtk_hex_view_create(GtkHexView *view, GBinContent *content)
{
    bool result;                            /* Bilan à retourner           */
    GtkBufferView *parent;                  /* Version parente du composant*/
    GBufferCache *cache;                    /* Tampon à représenter        */

    result = true;

    assert(g_display_options_count(GTK_CONTENT_VIEW(view)->options) == 1);

    parent = GTK_BUFFER_VIEW(view);

    cache = g_buffer_cache_new(1, 2);

    parent->view = g_buffer_view_new(cache, parent->style);

    unref_object(cache);

    view->generator = g_hex_generator_new(content);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : widget   = composant GTK à redessiner.                       *
*                snapshot = gestionnaire de noeuds de rendu à solliciter.     *
*                parent   = composant GTK parent et cadre de l'appel.         *
*                                                                             *
*  Description : Procède à l'actualisation de l'affichage d'un sous-composant.*
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_hex_view_dispatch_sub_snapshot(GtkWidget *widget, GtkSnapshot *snapshot, GtkWidget *parent)
{
    GtkHexView *view;                       /* Version spécialisée         */
    size_t column;                          /* Indice de colonne à traiter */
    int width;                              /* Largeur à disposition       */
    int height;                             /* Hauteur à disposition       */
    cairo_t *cr;                            /* Pinceau pour les dessins    */

    view = GTK_HEX_VIEW(parent);

    if (widget == view->offsets)
        column = HCO_OFFSET;

    else if (widget == view->hex)
        column = HCO_COUNT + 0;

    else
    {
        assert(widget == view->ascii);
        column = HCO_COUNT + 1;
    }

    width = gtk_widget_get_width(widget);
    height = gtk_widget_get_height(widget);

    cr = gtk_snapshot_append_cairo(snapshot, &GRAPHENE_RECT_INIT(0, 0, width, height));

    g_buffer_view_draw(GTK_BUFFER_VIEW(parent)->view, cr, column, GTK_BUFFER_VIEW(parent)->virt_top, height);

    cairo_destroy(cr);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view = composant GTK à mettre à jour.                        *
*                                                                             *
*  Description : Adapte le cache de lignes hexadécimales à la taille courante.*
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_hex_view_populate_cache(GtkHexView *view)
{
    GBinContent *content;                   /* Contenu binaire affiché     */
    phys_t full;                            /* Taille totale à représenter */
    phys_t line;                            /* Taille représentée par ligne*/
    size_t needed;                          /* Nombre de lignes nécessaires*/
    GBufferCache *cache;                    /* Tampon à représenter        */
    size_t count;                           /* Nombre actuel de lignes     */

    /* Détermination du besoin */

    content = g_hex_generator_get_content(view->generator);

    full = g_binary_content_compute_size(content);

    unref_object(content);

    line = g_hex_generator_get_bytes_per_line(view->generator);

    needed = full / line;

    if (full % line > 0)
        needed++;

    /* Adaptation du tampon interne ? */

    cache = g_buffer_view_get_cache(GTK_BUFFER_VIEW(view)->view);

    g_buffer_cache_wlock(cache);

    count = g_buffer_cache_count_lines(cache);

    if (needed < count)
        g_buffer_cache_truncate(cache, needed);

    else if (needed > count)
        g_buffer_cache_extend_with(cache, needed, G_TOKEN_GENERATOR(view->generator));

    g_buffer_cache_wunlock(cache);

    unref_object(cache);

    /* Mise à jour de l'affichage ? */

    if (needed != count)
        gtk_widget_queue_resize(GTK_WIDGET(view));

}







void demo_snapshot (GtkWidget *widget, GtkSnapshot *snapshot, GtkWidget *parent)
{
  GdkRGBA red, green, yellow, blue;
  float w, h;

  gdk_rgba_parse (&red, "red");
  gdk_rgba_parse (&green, "green");
  gdk_rgba_parse (&yellow, "yellow");
  gdk_rgba_parse (&blue, "blue");

  w = gtk_widget_get_width (widget) / 2.0;
  h = gtk_widget_get_height (widget) / 2.0;

  h /= 2.0;

  gtk_snapshot_append_color (snapshot, &red,
                             &GRAPHENE_RECT_INIT(0, 0, w, h));
  gtk_snapshot_append_color (snapshot, &green,
                             &GRAPHENE_RECT_INIT(w, 0, w, h));
  gtk_snapshot_append_color (snapshot, &yellow,
                             &GRAPHENE_RECT_INIT(0, h, w, h));
  gtk_snapshot_append_color (snapshot, &blue,
                             &GRAPHENE_RECT_INIT(w, h, w, h));



}













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


/******************************************************************************
*                                                                             *
*  Paramètres  : widget = composant GTK à examiner.                           *
*                width  = largeur affectée au composant graphique.            *
*                height = hauteur affectée au composant graphique.            *
*                baseline = ligne de base affectée au composant graphique.    *
*                                                                             *
*  Description : Prend acte de la taille allouée au composant d'affichage.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_hex_view_size_allocate(GtkWidget *widget, int width, int height, int baseline)
{
    GtkHexView *view;                       /* Version spécialisée         */
    int *min_widths;                        /* Tailles minimales imposées  */
    int *final_widths;                      /* Tailles finales retenues    */
    size_t i;                               /* Boucle de parcours          */
    int available;                          /* Largeur disponible          */
    bool changed;                           /* Détection de variation      */
    GWidthTracker *tracker;                 /* Collecteur de largeurs      */

    view = GTK_HEX_VIEW(widget);

    min_widths = alloca(_CHILDREN_COUNT * sizeof(int));
    final_widths = alloca(_CHILDREN_COUNT * sizeof(int));

    for (i = 0; i < _CHILDREN_COUNT; i++)
        gtk_widget_measure(view->children[i], GTK_ORIENTATION_HORIZONTAL, -1, &min_widths[i], NULL, NULL, NULL);

    /* Passe 1 : tentative sans défilement vertical */

    available = width;

    for (i = 0; i < _CHILDREN_COUNT; i++)
        available -= min_widths[i];

    changed = g_hex_generator_allocate(view->generator,
                                       GTK_CONTENT_VIEW(view)->options,
                                       GTK_BUFFER_VIEW(view)->style,
                                       available, final_widths);

    /* Application des largeurs calculées */

    if (changed)
    {
        gtk_hex_view_populate_cache(view);

        tracker = g_buffer_view_get_tracker(GTK_BUFFER_VIEW(view)->view);

        for (i = 0; i < _CHILDREN_COUNT; i++)
        {
            final_widths[i] += min_widths[i];

            g_width_tracker_set_column_min_width(tracker, i, final_widths[i]);

        }

        unref_object(tracker);

    }

    /* Mise à jour des éléments plus internes */

    GTK_WIDGET_CLASS(gtk_hex_view_parent_class)->size_allocate(widget, width, height, baseline);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : widget = composant GTK à consulter.                          *
*                                                                             *
*  Description : Fournit le mode de calcul pour déterminer la taille.         *
*                                                                             *
*  Retour      : Mode de calcul adapté au composant graphique.                *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GtkSizeRequestMode gtk_hex_view_get_request_mode(GtkWidget *widget)
{
    GtkSizeRequestMode result;              /* Configuration à remonter    */

    result = GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;

    return result;

}













/******************************************************************************
*                                                                             *
*  Paramètres  : widget       = composant GTK à examiner.                     *
*                orientation  = direction à observer pour les calculs.        *
*                for_size     = taille de la direction opposée.               *
*                minimum      = taille minimale pour le composant. [OUT]      *
*                natural      = taille idéale pour le composant. [OUT]        *
*                min_baseline = ligne de base minimale. [OUT]                 *
*                nat_baseline = ligne de base idéale. [OUT]                   *
*                                                                             *
*  Description : Fournit les mesures mainimale et idéale du composant.        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_hex_view_measure(GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *min_baseline, int *nat_baseline)
{
    bool processed;                         /* Calcul de hauteur effectué  */
    GtkHexView *view;                       /* Version spécialisée         */
    int requested;                          /* Taille requise à priori     */
    size_t i;                               /* Boucle de parcours          */
    int min;                                /* Valeur minimale locale      */
    int nat;                                /* Valeur idéale locale        */

    processed = false;

    /* Demande de hauteur minimale / idéale */
    if (orientation == GTK_ORIENTATION_VERTICAL && for_size != -1)
    {
        view = GTK_HEX_VIEW(widget);

        requested = 0;

        for (i = 0; i < _CHILDREN_COUNT; i++)
        {
            gtk_widget_measure(view->children[i], GTK_ORIENTATION_HORIZONTAL, -1, &min, NULL, NULL, NULL);
            requested += min;
        }

        for_size -= requested;

        if (for_size > 0)
        {
            requested = g_hex_generator_mesure_height_for_width(view->generator,
                                                                GTK_CONTENT_VIEW(view)->options,
                                                                GTK_BUFFER_VIEW(view)->style,
                                                                for_size);

            if (minimum != NULL) *minimum = 0;
            if (natural != NULL) *natural = requested;

            for (i = 0; i < _CHILDREN_COUNT; i++)
            {
                gtk_widget_measure(view->children[i], GTK_ORIENTATION_VERTICAL, -1, &min, &nat, NULL, NULL);

                if (minimum != NULL && min > *minimum)
                    *minimum = min;

                if (natural != NULL && nat > *natural)
                    *natural = nat;

            }

            processed = true;

        }

    }

    if (!processed)
        GTK_WIDGET_CLASS(gtk_hex_view_parent_class)->measure(widget, orientation, for_size,
                                                           minimum, natural, min_baseline, nat_baseline);

}