/*
 * Seahorse
 *
 * Copyright (C) 2006 Stefan Walter
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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, see
 * <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include "seahorse-bind.h"
#include "seahorse-object-model.h"
#include "seahorse-marshal.h"

/**
 * SeahorseObjectModel:
 *
 * A GtkTreeModel that can assign certain rows as
 * 'key rows' which are updated when a key is updated.
 */

enum {
    PROP_0,
    PROP_DATA_COLUMN,
};

enum {
    UPDATE_ROW,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

struct _SeahorseObjectModel {
    GtkTreeStore parent;
    GHashTable *rows;
    gint data_column;
};

G_DEFINE_TYPE (SeahorseObjectModel, seahorse_object_model, GTK_TYPE_TREE_STORE);

/* Internal data stored at 0 in the tree store in order to keep track
 * of the location, key-store and key.
 */
typedef struct {
    SeahorseObjectModel *self;
    GPtrArray           *refs;     /* GtkTreeRowReference pointers */
    GObject             *object;     /* The key we're dealing with */
    gpointer            binding;
} SeahorseObjectRow;

/* -----------------------------------------------------------------------------
 * INTERNAL
 */

static void
key_notify (GObject *object,
            SeahorseObjectModel *self)
{
    SeahorseObjectRow *skrow;
    GtkTreeIter iter;
    GtkTreePath *path;
    guint i;

    skrow = g_hash_table_lookup (self->rows, object);
    if (!skrow)
        return;

    for (i = 0; i < skrow->refs->len; i++) {

        path = gtk_tree_row_reference_get_path (g_ptr_array_index (skrow->refs, i));
        if (path) {
            gtk_tree_model_get_iter (GTK_TREE_MODEL (self), &iter, path);
            g_signal_emit (self, signals[UPDATE_ROW], 0, object, &iter);
            gtk_tree_path_free (path);
        }
    }
}

static void
key_destroyed (gpointer data, GObject *was)
{
    SeahorseObjectModel *self = SEAHORSE_OBJECT_MODEL (data);
    SeahorseObjectRow *skrow = g_hash_table_lookup (self->rows, was);
    if (skrow) {
        skrow->object = NULL;
        skrow->binding = NULL;
        g_hash_table_remove (self->rows, was);
    }
}


static gboolean
remove_each (GObject *object,
             gchar *path,
             SeahorseObjectModel *self)
{
    return TRUE;
}

static SeahorseObjectRow *
key_row_new (SeahorseObjectModel *self,
             GObject *object)
{
    SeahorseObjectRow *skrow;

    g_assert (SEAHORSE_IS_OBJECT_MODEL (self));
    g_assert (G_IS_OBJECT (object));

    skrow = g_new0 (SeahorseObjectRow, 1);
    skrow->refs = g_ptr_array_new ();
    skrow->self = self;
    skrow->object = object;
    skrow->binding = seahorse_bind_objects (NULL, object, (SeahorseTransfer)key_notify, self);

    g_object_weak_ref (G_OBJECT (object), key_destroyed, self);

    return skrow;
}

static void
key_row_free (SeahorseObjectRow *skrow)
{
    SeahorseObjectModel *self = skrow->self;
    GtkTreeRowReference *ref;
    GtkTreePath *path;
    GtkTreeIter iter;
    guint i;

    g_return_if_fail (self->data_column != -1);

    for (i = 0; i < skrow->refs->len; i++) {

        ref = (GtkTreeRowReference*)g_ptr_array_index (skrow->refs, i);
        if (ref) {
            path = gtk_tree_row_reference_get_path (ref);
            if (path) {
                gtk_tree_model_get_iter (GTK_TREE_MODEL (self), &iter, path);
                gtk_tree_store_set (GTK_TREE_STORE (self), &iter,
                                    self->data_column, NULL, -1);
                gtk_tree_path_free (path);
            }
            gtk_tree_row_reference_free (ref);
        }

    }

    if (skrow->binding)
        seahorse_bind_disconnect (skrow->binding);
    if (skrow->object)
        g_object_weak_unref (G_OBJECT (skrow->object), key_destroyed, skrow->self);

    g_ptr_array_free (skrow->refs, TRUE);
    g_free (skrow);
}

static void
row_inserted (SeahorseObjectModel *self, GtkTreePath *path, GtkTreeIter *iter,
              gpointer user_data)
{
    g_return_if_fail (self->data_column != -1);
    /* XXX: The following line causes problems with GtkTreeModelFilter */
    /* gtk_tree_store_set (GTK_TREE_STORE (self), iter, self->data_column, NULL, -1); */
}

/* -----------------------------------------------------------------------------
 * OBJECT
 */

static void
seahorse_object_model_init (SeahorseObjectModel *self)
{
    self->rows = g_hash_table_new_full (g_direct_hash, g_direct_equal,
                                      NULL, (GDestroyNotify)key_row_free);
    self->data_column = -1;
    g_signal_connect (self, "row-inserted", G_CALLBACK (row_inserted), NULL);
}

static void
seahorse_object_model_set_property (GObject *gobject, guint prop_id,
                                 const GValue *value, GParamSpec *pspec)
{
    SeahorseObjectModel *self = SEAHORSE_OBJECT_MODEL (gobject);

    switch (prop_id) {
    case PROP_DATA_COLUMN:
        g_assert (self->data_column == -1);
        self->data_column = g_value_get_uint (value);
        break;
    }
}

static void
seahorse_object_model_get_property (GObject *gobject, guint prop_id,
                                 GValue *value, GParamSpec *pspec)
{
    SeahorseObjectModel *self = SEAHORSE_OBJECT_MODEL (gobject);

    switch (prop_id) {
    case PROP_DATA_COLUMN:
        g_value_set_uint (value, self->data_column);
        break;
    }
}

static void
seahorse_object_model_dispose (GObject *gobject)
{
    SeahorseObjectModel *self = SEAHORSE_OBJECT_MODEL (gobject);

    /* Release all our pointers and stuff */
    g_hash_table_foreach_remove (self->rows, (GHRFunc)remove_each, self);
    G_OBJECT_CLASS (seahorse_object_model_parent_class)->dispose (gobject);
}

static void
seahorse_object_model_finalize (GObject *gobject)
{
    SeahorseObjectModel *self = SEAHORSE_OBJECT_MODEL (gobject);

    if (self->rows)
        g_hash_table_destroy (self->rows);
    self->rows = NULL;

    G_OBJECT_CLASS (seahorse_object_model_parent_class)->finalize (gobject);
}

static void
seahorse_object_model_class_init (SeahorseObjectModelClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->dispose = seahorse_object_model_dispose;
    gobject_class->finalize = seahorse_object_model_finalize;
    gobject_class->set_property = seahorse_object_model_set_property;
    gobject_class->get_property = seahorse_object_model_get_property;

    g_object_class_install_property (gobject_class, PROP_DATA_COLUMN,
        g_param_spec_uint ("data-column", "Column data is stored", "Column where internal data is stored",
                           0, ~0, 0, G_PARAM_READWRITE | G_PARAM_READWRITE));

    signals[UPDATE_ROW] = g_signal_new ("update-row", SEAHORSE_TYPE_OBJECT_MODEL,
                G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
                seahorse_marshal_VOID__OBJECT_POINTER,
                G_TYPE_NONE, 2, SEAHORSE_TYPE_OBJECT, G_TYPE_POINTER);
}

/* -----------------------------------------------------------------------------
 * PUBLIC
 */


SeahorseObjectModel*
seahorse_object_model_new (gint n_columns, GType *types)
{
    SeahorseObjectModel *model;

    model = g_object_new (SEAHORSE_TYPE_OBJECT_MODEL, NULL);
    seahorse_object_model_set_column_types (model, n_columns, types);

    return model;
}

void
seahorse_object_model_set_column_types (SeahorseObjectModel *self, gint n_columns,
                                     GType *types)
{
    GType *itypes;

    g_return_if_fail (SEAHORSE_IS_OBJECT_MODEL (self));

    itypes = g_new0(GType, n_columns + 1);
    memcpy (itypes, types, n_columns * sizeof (GType));

    itypes[n_columns] = G_TYPE_POINTER;
    self->data_column = n_columns;
    gtk_tree_store_set_column_types (GTK_TREE_STORE (self), n_columns + 1, itypes);

    g_free (itypes);
}

void
seahorse_object_model_set_row_object (SeahorseObjectModel *self,
                                      GtkTreeIter *iter,
                                      GObject *object)
{
    SeahorseObjectRow *skrow;
    GtkTreePath *path;
    GtkTreePath *ipath;
    guint i;

    g_return_if_fail (SEAHORSE_IS_OBJECT_MODEL (self));
    g_return_if_fail (G_IS_OBJECT (object) || object == NULL);
    g_return_if_fail (self->data_column >= 0);

    /* Add the row/key association */
    if (object) {

        /* Do we already have a row for this key? */
        skrow = (SeahorseObjectRow*)g_hash_table_lookup (self->rows, object);
        if (!skrow) {
            skrow = key_row_new (self, object);

            /* Put it in our row cache */
            g_hash_table_replace (self->rows, object, skrow);
        }

        path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), iter);
        g_ptr_array_add (skrow->refs, gtk_tree_row_reference_new (GTK_TREE_MODEL (self), path));
        gtk_tree_path_free (path);

    /* Remove the row/key association */
    } else {

        gtk_tree_model_get (GTK_TREE_MODEL (self), iter, self->data_column, &skrow, -1);
        if (skrow) {

            ipath = gtk_tree_model_get_path (GTK_TREE_MODEL (self), iter);
            g_return_if_fail (ipath != NULL);

            for (i = 0; i < skrow->refs->len; i++) {

                path = gtk_tree_row_reference_get_path (g_ptr_array_index (skrow->refs, i));

                /* Check if they're the same or invalid, remove */
                if (!path || gtk_tree_path_compare (path, ipath) == 0) {
                    gtk_tree_row_reference_free (g_ptr_array_index (skrow->refs, i));
                    g_ptr_array_remove_index_fast (skrow->refs, i);
                    i--;
                }

                if (path)
                    gtk_tree_path_free (path);
            }

            /* If we no longer have rows associated with this key, then remove */
            if (skrow->refs->len == 0)
                g_hash_table_remove (self->rows, skrow->object);
        }
    }

    gtk_tree_store_set (GTK_TREE_STORE (self), iter,
                        self->data_column, object ? skrow : NULL, -1);

    if (object)
        key_notify (object, self);
}

GObject *
seahorse_object_model_get_row_key (SeahorseObjectModel *self, GtkTreeIter *iter)
{
    SeahorseObjectRow *skrow;

    g_return_val_if_fail (SEAHORSE_IS_OBJECT_MODEL (self), NULL);
    g_return_val_if_fail (self->data_column >= 0, NULL);

    gtk_tree_model_get (GTK_TREE_MODEL (self), iter, self->data_column, &skrow, -1);
    if (!skrow)
        return NULL;
    g_assert (G_IS_OBJECT (skrow->object));
    return skrow->object;
}

void
seahorse_object_model_remove_rows_for_object (SeahorseObjectModel *self,
                                              GObject *object)
{
    SeahorseObjectRow *skrow;
    GtkTreeIter iter;
    GtkTreePath *path;
    guint i;

    g_return_if_fail (SEAHORSE_IS_OBJECT_MODEL (self));
    g_return_if_fail (G_IS_OBJECT (object));
    g_return_if_fail (self->data_column >= 0);

    skrow = (SeahorseObjectRow*)g_hash_table_lookup (self->rows, object);
    if (!skrow)
        return;

    for (i = 0; i < skrow->refs->len; i++) {

        path = gtk_tree_row_reference_get_path (g_ptr_array_index (skrow->refs, i));
        if (path) {
            gtk_tree_model_get_iter (GTK_TREE_MODEL (self), &iter, path);
            gtk_tree_store_remove (GTK_TREE_STORE (self), &iter);
            gtk_tree_path_free (path);
        }
    }

    /* We no longer have rows associated with this key, then remove */
    g_hash_table_remove (self->rows, object);
}

GList*
seahorse_object_model_get_rows_for_object (SeahorseObjectModel *self,
                                           GObject *object)
{
    GList *rows = NULL;
    SeahorseObjectRow *skrow;
    GtkTreeIter *iter;
    GtkTreePath *path;
    guint i;

    g_return_val_if_fail (SEAHORSE_IS_OBJECT_MODEL (self), NULL);
    g_return_val_if_fail (G_IS_OBJECT (object), NULL);

    skrow = (SeahorseObjectRow*)g_hash_table_lookup (self->rows, object);
    if (!skrow)
        return NULL;

    for (i = 0; i < skrow->refs->len; i++) {

        path = gtk_tree_row_reference_get_path (g_ptr_array_index (skrow->refs, i));
        if (path) {
            iter = g_new0(GtkTreeIter, 1);
            gtk_tree_model_get_iter (GTK_TREE_MODEL (self), iter, path);
            rows = g_list_prepend (rows, iter);
            gtk_tree_path_free (path);
        }
    }

    return rows;
}

void
seahorse_object_model_free_rows (GList *rows)
{
    GList *l;
    for (l = rows; l; l = g_list_next (l))
        g_free (l->data);
    g_list_free (rows);
}
