/* OpenIDA - Outil d'analyse de fichiers binaires
 * gcodebuffer.h - prototypes pour l'affichage d'un fragment de code d'assemblage
 *
 * Copyright (C) 2010-2012 Cyrille Bagard
 *
 *  This file is part of OpenIDA.
 *
 *  OpenIDA 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.
 *
 *  OpenIDA 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 Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "gcodebuffer.h"


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



/* -------------------------- TAMPON POUR CODE DESASSEMBLE -------------------------- */


/* Tampon pour code désassemblé (instance) */
struct _GCodeBuffer
{
    GObject parent;                         /* A laisser en premier        */

    GBufferLine **lines;                    /* Liste des lignes intégrées  */
    size_t count;                           /* Quantité en cache           */
    size_t used;                            /* Quantité utilisée           */

};

/* Tampon pour code désassemblé (classe) */
struct _GCodeBufferClass
{
    GObjectClass parent;                    /* A laisser en premier        */

};


/* Taille des allocations de masse */
#define LINE_ALLOC_BULK 20


/* Procède à l'initialisation d'une classe de tampon de code. */
static void g_code_buffer_class_init(GCodeBufferClass *);

/* Procède à l'initialisation d'un tampon pour code désassemblé. */
static void g_code_buffer_init(GCodeBuffer *);



/* ---------------------- VUE PARTICULIERE D'UN TAMPON DE CODE ---------------------- */


/* Vue d'un tampon pour code désassemblé (instance) */
struct _GBufferView
{
    GObject parent;                         /* A laisser en premier        */

    GCodeBuffer *buffer;                    /* Tampon de code visualisé    */
    size_t first;                           /* Première ligne intégrée     */
    size_t last;                            /* Dernière ligne intégrée     */

    gint line_height;                       /* Hauteur maximale des lignes */
    gint max_widths[BLC_COUNT];             /* Taille cachée des colonnes  */
    gint left_margin;                       /* Marge gauche + espace       */
    gint left_text;                         /* Début d'impression du code  */

    buffer_line_draw_fc drawing_extra;      /* Fonction d'accompagnement   */
    void *drawing_data;                     /* Donnée utilisateur          */

};

/* Vue d'un tampon pour code désassemblé (classe) */
struct _GBufferViewClass
{
    GObjectClass parent;                    /* A laisser en premier        */

};


#define WIDTHS_CACHED(view) ((view)->max_widths[0] != -1)


/* Procède à l'initialisation d'une classe de vue de tampon. */
static void g_buffer_view_class_init(GBufferViewClass *);

/* Procède à l'initialisation d'une vue d'un tampon pour code. */
static void g_buffer_view_init(GBufferView *);

/* Réinitialise le cache des largeurs de colonne calculées. */
static void g_buffer_view_reset_required_widths(GBufferView *);

/* Calcule les dimensions requises par une visualisation. */
static void g_buffer_view_compute_required_widths(GBufferView *);



/* ---------------------------------------------------------------------------------- */
/*                            TAMPON POUR CODE DESASSEMBLE                            */
/* ---------------------------------------------------------------------------------- */


/* Détermine le type du composant de tampon pour code désassemblé. */
G_DEFINE_TYPE(GCodeBuffer, g_code_buffer, G_TYPE_OBJECT);


/******************************************************************************
*                                                                             *
*  Paramètres  : class = classe de composant GTK à initialiser.               *
*                                                                             *
*  Description : Procède à l'initialisation d'une classe de tampon de code.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_code_buffer_class_init(GCodeBufferClass *class)
{

}


/******************************************************************************
*                                                                             *
*  Paramètres  : buffer = composant GTK à initialiser.                        *
*                                                                             *
*  Description : Procède à l'initialisation d'un tampon pour code désassemblé.*
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_code_buffer_init(GCodeBuffer *buffer)
{

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Crée un nouveau composant de tampon pour code désassemblé.   *
*                                                                             *
*  Retour      : Composant GTK créé.                                          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GCodeBuffer *g_code_buffer_new(void)
{
    GCodeBuffer *result;                    /* Composant à retourner       */

    result = g_object_new(G_TYPE_CODE_BUFFER, NULL);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : buffer = composant GTK à mettre à jour.                      *
*                                                                             *
*  Description : Ajoute une nouvelle ligne à un tampon pour code désassemblé. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GBufferLine *g_code_buffer_append_new_line(GCodeBuffer *buffer)
{
    GBufferLine *result;                    /* Instance à retourner        */

    if (buffer->used == buffer->count)
    {
        buffer->count += LINE_ALLOC_BULK;
        buffer->lines = (GBufferLine **)realloc(buffer->lines,
                                                buffer->count * sizeof(GBufferLine *));
    }

    result = g_buffer_line_new();
    buffer->lines[buffer->used++] = result;

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                        VUE PARTICULIERE D'UN TAMPON DE CODE                        */
/* ---------------------------------------------------------------------------------- */


/* Détermine le type de la vue d'un tampon pour code désassemblé. */
G_DEFINE_TYPE(GBufferView, g_buffer_view, G_TYPE_OBJECT);


/******************************************************************************
*                                                                             *
*  Paramètres  : class = classe de composant GTK à initialiser.               *
*                                                                             *
*  Description : Procède à l'initialisation d'une classe de vue de tampon.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_buffer_view_class_init(GBufferViewClass *class)
{

}


/******************************************************************************
*                                                                             *
*  Paramètres  : buffer = composant GTK à initialiser.                        *
*                                                                             *
*  Description : Procède à l'initialisation d'une vue d'un tampon pour code.  *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_buffer_view_init(GBufferView *buffer)
{

}


/******************************************************************************
*                                                                             *
*  Paramètres  : buffer = tamon à représenter à l'écran.                      *
*                                                                             *
*  Description : Crée une nouvelle vue d'un tampon pour code désassemblé.     *
*                                                                             *
*  Retour      : Composant GTK créé.                                          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GBufferView *g_buffer_view_new(GCodeBuffer *buffer)
{
    GBufferView *result;                    /* Composant à retourner       */

    result = g_object_new(G_TYPE_BUFFER_VIEW, NULL);

    result->buffer = buffer;
    result->first = 0;
    result->last = buffer->used;

    g_buffer_view_reset_required_widths(result);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view = visualisation à consulter.                            *
*                                                                             *
*  Description : Réinitialise le cache des largeurs de colonne calculées.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_buffer_view_reset_required_widths(GBufferView *view)
{
    unsigned int i;                         /* Boucle de parcours          */

    for (i = 0; i < BLC_COUNT; i++)
        view->max_widths[i] = -1;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view = visualisation à consulter.                            *
*                                                                             *
*  Description : Calcule les dimensions requises par une visualisation.       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_buffer_view_compute_required_widths(GBufferView *view)
{
    GBufferLine **lines;                    /* Liste des lignes à traiter  */
    size_t i;                               /* Boucle de parcours #1       */
    unsigned int j;                         /* Boucle de parcours #2       */
    gint width;                             /* Largeur d'une colonne       */

    lines = view->buffer->lines;

    view->line_height = 17;

    view->left_margin = 2 * view->line_height;
    view->left_text = 2.5 * view->line_height;

    for (i = view->first; i < view->last; i++)
        for (j = 0; j < BLC_COUNT; j++)
        {
            width = g_buffer_line_get_width(lines[i], j);
            view->max_widths[j] = MAX(view->max_widths[j], width);
        }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view = visualisation à consulter.                            *
*                                                                             *
*  Description : Fournit la hauteur d'impression d'une ligne visualisée.      *
*                                                                             *
*  Retour      : Hauteur de ligne en pixel.                                   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

gint g_buffer_view_get_line_height(GBufferView *view)
{
    if (!WIDTHS_CACHED(view))
        g_buffer_view_compute_required_widths(view);

    return view->line_height;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view   = visualisation à consulter.                          *
*                width  = largeur requise pour une pleine visualisation. [OUT]*
*                height = hauteur requise pour une pleine visualisation. [OUT]*
*                addr   = indique si les positions doivent être affichées.    *
*                code   = indique si le code binaire doit être affiché.       *
*                                                                             *
*  Description : Fournit les dimensions requises par une visualisation.       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_buffer_view_get_size(GBufferView *view, gint *width, gint *height, bool addr, bool code)
{
    unsigned int i;                         /* Boucle de parcours          */

    *width = 0;
    *height = view->line_height;

    if (!WIDTHS_CACHED(view))
        g_buffer_view_compute_required_widths(view);

    for (i = 0; i < BLC_COUNT; i++)
    {
        if (i == BLC_ADDRESS && !addr) continue;
        if (i == BLC_BINARY && !code) continue;

        *width += view->max_widths[i];

    }

    *height *= (view->last - view->first);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view   = visualisation à mettre à jour.                      *
*                method = procédure à appeler à chaque dessin de ligne.       *
*                data   = donnée utilisateur à passer lors des appels.        *
*                                                                             *
*  Description : Définit à une procédure à appeler lors des dessins de ligne. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_buffer_view_define_extra_drawing(GBufferView *view, buffer_line_draw_fc method, void *data)
{
    view->drawing_extra = method;
    view->drawing_data = data;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view   = visualisation à représenter.                        *
*                event  = informations liées à l'événement.                   *
*                gc     = contexte graphique à utiliser pour les pinceaux.    *
*                fake_x = abscisse réelle du point 0 à l'écran.               *
*                fake_y = ordonnée réelle du point 0 à l'écran.               *
*                addr   = indique si les positions doivent être affichées.    *
*                code   = indique si le code binaire doit être affiché.       *
*                                                                             *
*  Description : Imprime la visualisation du tempon de code désassemblé.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_buffer_view_draw(const GBufferView *view, const GdkEventExpose *event, GdkGC *gc, gint fake_x, gint fake_y, bool addr, bool code)
{
    GdkDrawable *drawable;                  /* Surface de dessin           */
    gint real_x;                            /* Abscisse réelle pour tampon */
    gint real_y;                            /* Ordonnée réelle pour tampon */

    size_t first;                           /* Première ligne visée        */
    size_t last;                            /* Dernière ligne visée + 1    */
    gint y;                                 /* Point de départ + décallage */


    GBufferLine **lines;                    /* Liste des lignes à traiter  */
    size_t i;                               /* Boucle de parcours          */


    drawable = GDK_DRAWABLE(event->window);


    real_x = fake_x + view->left_text;
    real_y = fake_y + event->area.y;



    first = (real_y / view->line_height);
    last = first + (event->area.height / view->line_height);
    if (event->area.height % view->line_height > 0) last++;

    last = MIN(last, view->buffer->used > 0 ? view->buffer->used - 1 : 0);

    y = event->area.y - (real_y % view->line_height);



    lines = view->buffer->lines;

    if (view->buffer->used > 0)
        for (i = first; i <= last; i++)
        {
            /* TODO : skip if... */

            if (view->drawing_extra != NULL)
                view->drawing_extra(lines[i], drawable, gc, fake_x, y, view->drawing_data);

            g_buffer_line_draw(lines[i], drawable, gc, view->max_widths, real_x, y, addr, code);

            y += view->line_height;

        }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : view = visualisation à consulter.                            *
*                y    = ordonnée comprise dans la ligne recherchée.           *
*                                                                             *
*  Description : Fournit la ligne présente à une ordonnée donnée.             *
*                                                                             *
*  Retour      : Ligne retrouvée ou NULL si aucune.                           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GBufferLine *g_buffer_view_find_line_at(GBufferView *view, gint y)
{
    gint lheight;                           /* Hauteur d'une ligne         */
    size_t index;                           /* Indice attendu              */

    lheight = g_buffer_view_get_line_height(view);
    index = y / lheight;

    /* FIXME : à placer côté tampon ? */
    return (index < view->buffer->used ? view->buffer->lines[index] : NULL);

}