/* Chrysalide - Outil d'analyse de fichiers binaires
 * history.c - panneau de la liste des évolutions d'utilisateur(s)
 *
 * Copyright (C) 2015 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "history.h"


#include <cairo-gobject.h>


#include <i18n.h>


#include "panel-int.h"
#include "../../analysis/db/collection.h"
#include "../../glibext/chrysamarshal.h"
#include "../../glibext/signal.h"
#include "../../gtkext/easygtk.h"



/* Panneau de la liste des évolutions utilisateur(s) (instance) */
struct _GHistoryPanel
{
    GPanelItem parent;                      /* A laisser en premier        */

    GtkTreeView *treeview;                  /* Composant d'affichage       */
    GtkTreeStore *store;                    /* Modèle de gestion           */

    GLoadedBinary *binary;                  /* Binaire à prendre en compte */

};

/* Panneau de la liste des évolutions utilisateur(s) (classe) */
struct _GHistoryPanelClass
{
    GPanelItemClass parent;                 /* A laisser en premier        */

};


/* Colonnes de la liste des évolutions */
typedef enum _HistoryColumn
{
    HTC_ITEM,                               /* Elément d'évolution         */

    HTC_PICTURE,                            /* Image de représentation     */
    HTC_FOREGROUND,                         /* Couleur d'impression        */
    HTC_LABEL,                              /* Désignation humaine         */

    HTC_COUNT                               /* Nombre de colonnes          */

} HistoryColumn;


/* Initialise la classe des panneaux de la liste des évolutions utilisateur(s). */
static void g_history_panel_class_init(GHistoryPanelClass *);

/* Initialise une instance de panneau d'aperçu de graphiques. */
static void g_history_panel_init(GHistoryPanel *);

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

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

/* Réagit à un changement d'affichage principal de contenu. */
static void change_history_panel_current_binary(GHistoryPanel *, GLoadedBinary *);

/* Réagit à une modification au sein d'une collection donnée. */
static void on_history_changed(GDbCollection *, DBAction, GDbItem *, GHistoryPanel *);

/* Compare deux lignes entre elles pour le tri des évolutions. */
static gint sort_history_lines(GtkTreeModel *, GtkTreeIter *, GtkTreeIter *, gpointer);

/* Réagit au changement de sélection des éléments d'historique. */
static void on_history_selection_change(GtkTreeSelection *, GHistoryPanel *);

/* Annule l'élément d'évolution courant. */
static void do_history_undo(GtkButton *, GHistoryPanel *);

/* Restaure l'élément d'évolution suivant. */
static void do_history_redo(GtkButton *, GHistoryPanel *);

/* Effectue un nettoyage de l'historique. */
static void do_history_clean(GtkButton *, GHistoryPanel *);



/* Indique le type définit pour un panneau d'aperçu de graphiques. */
G_DEFINE_TYPE(GHistoryPanel, g_history_panel, G_TYPE_PANEL_ITEM);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des panneaux d'aperçu de graphiques.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_history_panel_class_init(GHistoryPanelClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */
    GEditorItemClass *editem;               /* Encore une autre vision...  */

    object = G_OBJECT_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_history_panel_dispose;
    object->finalize = (GObjectFinalizeFunc)g_history_panel_finalize;

    editem = G_EDITOR_ITEM_CLASS(klass);

    editem->update_binary = (update_item_binary_fc)change_history_panel_current_binary;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : panel = instance à initialiser.                              *
*                                                                             *
*  Description : Initialise une instance de panneau d'aperçu de graphiques.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_history_panel_init(GHistoryPanel *panel)
{
    GEditorItem *base;                      /* Version basique d'instance  */
    GObject *ref;                           /* Espace de référencement     */
    GtkWidget *scrollwnd;                   /* Support défilant            */
    GtkWidget *treeview;                    /* Affichage de la liste       */
    GtkCellRenderer *renderer;              /* Moteur de rendu de colonne  */
    GtkTreeViewColumn *column;              /* Colonne de la liste         */
    GtkWidget *box;                         /* Séparation horizontale      */
    GtkWidget *button;                      /* Bouton de cette même barre  */
    GtkTreeSelection *select;               /* Sélection dans la liste     */

    base = G_EDITOR_ITEM(panel);

    base->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
    gtk_container_set_border_width(GTK_CONTAINER(base->widget), 8);
    gtk_widget_show(base->widget);

    ref = G_OBJECT(panel);

    /* Liste des éléments d'évolution */

    scrollwnd = gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_show(scrollwnd);
    gtk_box_pack_start(GTK_BOX(base->widget), scrollwnd, TRUE, TRUE, 0);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwnd), GTK_SHADOW_IN);

    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwnd), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwnd), GTK_SHADOW_IN);

    panel->store = gtk_tree_store_new(HTC_COUNT, G_TYPE_OBJECT, CAIRO_GOBJECT_TYPE_SURFACE,
                                      G_TYPE_STRING, G_TYPE_STRING);

    treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(panel->store));
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
    gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(treeview), TRUE);

    panel->treeview = GTK_TREE_VIEW(treeview);

    gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(panel->store), sort_history_lines, NULL, NULL);

    gtk_widget_show(treeview);
    gtk_container_add(GTK_CONTAINER(scrollwnd), treeview);

    g_object_unref(G_OBJECT(panel->store));

    /* Cellules d'affichage */

    renderer = gtk_cell_renderer_pixbuf_new();
    column = gtk_tree_view_column_new_with_attributes("", renderer,
                                                      "surface", HTC_PICTURE,
                                                      NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes(_("Label"), renderer,
                                                      "foreground", HTC_FOREGROUND,
                                                      "text", HTC_LABEL,
                                                      NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(panel->store),
                                         GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);

    /* Eléments de contrôle inférieurs */

    box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
    gtk_widget_set_halign(box, GTK_ALIGN_CENTER);
    gtk_widget_show(box);
    gtk_box_pack_start(GTK_BOX(base->widget), box, FALSE, TRUE, 0);

    button = qck_create_button_with_css_img(ref, "undo", "img-undo", _("Undo"),
                                            G_CALLBACK(do_history_undo), panel);
    gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);

    button = qck_create_button_with_css_img(ref, "redo", "img-redo", _("Redo"),
                                            G_CALLBACK(do_history_redo), panel);
    gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);

    button = qck_create_button_with_css_img(NULL, NULL, "img-clean", _("Clean"),
                                            G_CALLBACK(do_history_clean), panel);
    gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);

    /* Prise en compte de la sélection */

    select = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
    gtk_tree_selection_set_mode(select, GTK_SELECTION_BROWSE);
    g_signal_connect(G_OBJECT(select), "changed", G_CALLBACK(on_history_selection_change), panel);

}


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

static void g_history_panel_dispose(GHistoryPanel *panel)
{
    if (panel->binary != NULL)
        g_object_unref(G_OBJECT(panel->binary));

    G_OBJECT_CLASS(g_history_panel_parent_class)->dispose(G_OBJECT(panel));

}


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

static void g_history_panel_finalize(GHistoryPanel *panel)
{
    G_OBJECT_CLASS(g_history_panel_parent_class)->finalize(G_OBJECT(panel));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ref = espace de référencement global.                        *
*                                                                             *
*  Description : Crée un panneau d'affichage des symboles.                    *
*                                                                             *
*  Retour      : Adresse de la structure mise en place.                       *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GEditorItem *g_history_panel_new(GObject *ref)
{
    GEditorItem *result;                    /* Structure à retourner       */

    result = g_object_new(G_TYPE_HISTORY_PANEL, NULL);

    g_panel_item_init_ext(G_PANEL_ITEM(result), ref, PANEL_HISTORY_ID,
                          _("Change history"), G_EDITOR_ITEM(result)->widget, "eM");

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ref = espace de référencement global.                        *
*                                                                             *
*  Description : Construit et intègre un panneau d'affichage des symboles.    *
*                                                                             *
*  Retour      : Adresse du panneau mis en place.                             *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GPanelItem *create_history_panel(GObject *ref)
{
    GEditorItem *result;                    /* Elément réactif à renvoyer  */

    result = g_history_panel_new(ref);

    /* Enregistre correctement le tout */
    register_editor_item(result);

    return G_PANEL_ITEM(result);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : panel  = panneau à mettre à jour.                            *
*                binary = nouvelle instance de binaire analysé.               *
*                                                                             *
*  Description : Réagit à un changement d'affichage principal de contenu.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void change_history_panel_current_binary(GHistoryPanel *panel, GLoadedBinary *binary)
{
    GList *collections;                     /* Ensemble de collections     */
    GList *c;                               /* Boucle de parcours #1       */
    GDbCollection *collec;                  /* Collection visée manipulée  */
    GtkTreeStore *store;                    /* Modèle de gestion           */
    GList *items;                           /* Liste des éléments groupés  */
    GList *i;                               /* Boucle de parcours #2       */
    GDbItem *item;                          /* Elément à intégrer          */
    GtkTreeIter iter;                       /* Point d'insertion           */

    /* Basculement du binaire utilisé */

    if (panel->binary != NULL)
    {
        collections = g_loaded_binary_get_all_collections(panel->binary);

        rlock_collections(collections);

        for (c = g_list_first(collections); c != NULL; c = g_list_next(c))
        {
            collec = G_DB_COLLECTION(c->data);
            g_signal_handlers_disconnect_by_func(collec, G_CALLBACK(on_history_changed), panel);
        }

        runlock_collections(collections);

        g_object_unref(G_OBJECT(panel->binary));

    }

    panel->binary = binary;

    if (panel->binary != NULL)
        g_object_ref(G_OBJECT(binary));

    store = GTK_TREE_STORE(gtk_tree_view_get_model(panel->treeview));
    gtk_tree_store_clear(store);

    /* Si le panneau actif ne représente pas un binaire... */

    if (binary == NULL) return;

    /* Actualisation de l'affichage */

    collections = g_loaded_binary_get_all_collections(binary);

    rlock_collections(collections);

    for (c = g_list_first(collections); c != NULL; c = g_list_next(c))
    {
        collec = G_DB_COLLECTION(c->data);
        items = g_db_collection_list_items(collec);

        for (i = g_list_first(items); i != NULL; i = g_list_next(i))
        {
            item = G_DB_ITEM(i->data);

            gtk_tree_store_append(store, &iter, NULL);
            gtk_tree_store_set(store, &iter,
                               HTC_ITEM, item,
                               //HTC_PICTURE, G_BOOKMARKS_PANEL_GET_CLASS(panel)->bookmark_img,
                               HTC_FOREGROUND, g_db_item_is_active(item) ? NULL : "grey",
                               HTC_LABEL, g_db_item_get_label(item),
                               -1);

        }

        g_signal_connect_to_main(collec, "content-changed", G_CALLBACK(on_history_changed), panel,
                                 g_cclosure_user_marshal_VOID__ENUM_OBJECT);

    }

    runlock_collections(collections);

    /* Force une sélection initiale */
    on_history_changed(NULL, DBA_COUNT, NULL, panel);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : collec = collection dont le contenu a évolué.                *
*                action = type d'évolution rencontrée.                        *
*                item   = élément ajouté, modifié ou supprimé.                *
*                panel  = panneau d'historique concerné par la procédure.     *
*                                                                             *
*  Description : Réagit à une modification au sein d'une collection donnée.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void on_history_changed(GDbCollection *collec, DBAction action, GDbItem *item, GHistoryPanel *panel)
{
    GtkTreeModel *model;                    /* Modèle de gestion courant   */
    GtkTreeSelection *selection;            /* Nouvelle sélection à établir*/
    GtkTreeIter iter;                       /* Boucle de parcours          */

    model = GTK_TREE_MODEL(panel->store);

    selection = gtk_tree_view_get_selection(panel->treeview);

    /* Mise à jour de la liste affichée */

    bool find_changed_item(GtkTreeModel *_model, GDbItem *target, GtkTreeIter *_found)
    {
        bool status;
        GtkTreeIter candidate;
        GDbItem *displayed;

        status = false;

        if (gtk_tree_model_get_iter_first(_model, &candidate))
            do
            {
                gtk_tree_model_get(_model, &candidate, HTC_ITEM, &displayed, -1);

                if (target == displayed)
                {
                    *_found = candidate;
                    status = true;
                }

                g_object_unref(G_OBJECT(displayed));

            }
            while (!status && gtk_tree_model_iter_next(_model, &candidate));

        return status;

    }

    switch (action)
    {
        case DBA_ADD_ITEM:

            gtk_tree_store_append(panel->store, &iter, NULL);
            gtk_tree_store_set(panel->store, &iter,
                               HTC_ITEM, item,
                               //HTC_PICTURE, G_BOOKMARKS_PANEL_GET_CLASS(panel)->bookmark_img,
                               HTC_FOREGROUND, g_db_item_is_active(item) ? NULL : "grey",
                               HTC_LABEL, g_db_item_get_label(item),
                               -1);

            break;

        case DBA_REM_ITEM:

            if (find_changed_item(model, item, &iter))
                gtk_tree_store_remove(panel->store, &iter);

            break;

        case DBA_CHANGE_STATE:

            if (find_changed_item(model, item, &iter))
                gtk_tree_store_set(panel->store, &iter,
                                   HTC_FOREGROUND, g_db_item_is_active(item) ? NULL : "grey",
                                   -1);
            break;

        case DBA_COUNT:
            /* Actualisation artificielle de la sélection */
            break;

    }

    /* Redéfinition de la sélection */

    if (gtk_tree_model_get_iter_first(model, &iter))
    {
        gboolean find_last_active(GtkTreeModel *_model, GtkTreePath *_path, GtkTreeIter *_iter, GtkTreeIter *last)
        {
            GDbItem *item;
            gboolean active;

            gtk_tree_model_get(_model, _iter, HTC_ITEM, &item, -1);

            active = g_db_item_is_active(item);

            g_object_unref(G_OBJECT(item));

            if (active)
                *last = *_iter;

            return !active;

        }

        gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc)find_last_active, &iter);

        gtk_tree_selection_select_iter(selection, &iter);

    }

    /* Actualisation des accès */

    on_history_selection_change(selection, panel);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : model = gestionnaire de données pour la liste traitée.       *
*                a     = premier point de comparaison.                        *
*                b     = second point de comparaison.                         *
*                dummy = adresse non utilisée ici.                            *
*                                                                             *
*  Description : Compare deux lignes entre elles pour le tri des évolutions.  *
*                                                                             *
*  Retour      : -1, 0 ou 1 selon le résultat de la comparaison.              *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gint sort_history_lines(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer dummy)
{
    gint result;                            /* Bilan à retourner           */
    GDbItem *item_a;                        /* Elément de collection A     */
    GDbItem *item_b;                        /* Elément de collection B     */

    gtk_tree_model_get(model, a, HTC_ITEM, &item_a, -1);
    gtk_tree_model_get(model, b, HTC_ITEM, &item_b, -1);

    result = g_db_item_cmp(item_a, item_b, true);

    g_object_unref(G_OBJECT(item_a));
    g_object_unref(G_OBJECT(item_b));

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : selection = sélection modifiée.                              *
*                panel     = structure contenant les informations maîtresses. *
*                                                                             *
*  Description : Réagit au changement de sélection des éléments d'historique. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void on_history_selection_change(GtkTreeSelection *selection, GHistoryPanel *panel)
{
    GtkTreeIter iter;                       /* Point de sélection          */
    GtkTreeModel *model;                    /* Modèle de gestion           */
    GDbItem *item;                          /* Elément de collection       */
    GtkWidget *button;                      /* Bouton de barre de contrôle */

    if (gtk_tree_selection_get_selected(selection, &model, &iter))
    {
        gtk_tree_model_get(model, &iter, HTC_ITEM, &item, -1);

        button = GTK_WIDGET(g_object_get_data(G_OBJECT(panel), "undo"));
        gtk_widget_set_sensitive(button, g_db_item_is_active(item));

        button = GTK_WIDGET(g_object_get_data(G_OBJECT(panel), "redo"));
        gtk_widget_set_sensitive(button, !g_db_item_is_active(item));

        g_object_unref(G_OBJECT(item));

    }

    else
    {
        button = GTK_WIDGET(g_object_get_data(G_OBJECT(panel), "undo"));
        gtk_widget_set_sensitive(button, FALSE);

        button = GTK_WIDGET(g_object_get_data(G_OBJECT(panel), "redo"));
        gtk_widget_set_sensitive(button, FALSE);

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : button = bouton d'édition de l'historique d'évolution.       *
*                panel  = panneau d'affichage de l'historique.                *
*                                                                             *
*  Description : Annule l'élément d'évolution courant.                        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void do_history_undo(GtkButton *button, GHistoryPanel *panel)
{
    GtkTreeSelection *selection;            /* Sélection courante          */
    GtkTreeModel *model;                    /* Modèle de gestion de données*/
    GtkTreeIter iter;                       /* Pointeur vers la ligne visée*/
    GDbItem *item;                          /* Elément de collection       */
    GDbClient *client;                      /* Connexion vers la base      */

    selection = gtk_tree_view_get_selection(panel->treeview);

    if (gtk_tree_selection_get_selected(selection, &model, &iter))
    {
        if (!gtk_tree_model_iter_previous(model, &iter))
            return;

        gtk_tree_model_get(model, &iter, HTC_ITEM, &item, -1);

        client = g_loaded_binary_get_db_client(panel->binary);
        g_db_client_set_last_active(client, g_db_item_get_timestamp(item));
        g_object_unref(G_OBJECT(client));

        g_object_unref(G_OBJECT(item));

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : button = bouton d'édition de l'historique d'évolution.       *
*                panel  = panneau d'affichage de l'historique.                *
*                                                                             *
*  Description : Restaure l'élément d'évolution suivant.                      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void do_history_redo(GtkButton *button, GHistoryPanel *panel)
{
    GtkTreeSelection *selection;            /* Sélection courante          */
    GtkTreeModel *model;                    /* Modèle de gestion de données*/
    GtkTreeIter iter;                       /* Pointeur vers la ligne visée*/
    GDbItem *item;                          /* Elément de collection       */
    GDbClient *client;                      /* Connexion vers la base      */

    selection = gtk_tree_view_get_selection(panel->treeview);

    if (gtk_tree_selection_get_selected(selection, &model, &iter))
    {
        gtk_tree_model_get(model, &iter, HTC_ITEM, &item, -1);

        client = g_loaded_binary_get_db_client(panel->binary);
        g_db_client_set_last_active(client, g_db_item_get_timestamp(item));
        g_object_unref(G_OBJECT(client));

        g_object_unref(G_OBJECT(item));

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : button = bouton d'édition de l'historique d'évolution.       *
*                panel  = panneau d'affichage de l'historique.                *
*                                                                             *
*  Description : Effectue un nettoyage de l'historique.                       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void do_history_clean(GtkButton *button, GHistoryPanel *panel)
{
    /* TODO */

}