/*
 * gnc-tree-model-account-types.c -- GtkTreeModel implementation
 *	to display account types in a GtkTreeView.
 *
 * Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2005, 2006 Chris Shoemaker <c.shoemaker@cox.net>
 * Copyright (C) 2006 Eskil Bylund <eskil.bylund@gmail.com>
 *
 * 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, contact:
 *
 * Free Software Foundation           Voice:  +1-617-542-5942
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
 * Boston, MA  02110-1301,  USA       gnu@gnu.org
 */

#include <config.h>

#include <gtk/gtk.h>

#include "qof.h"
#include "gnc-tree-model-account-types.h"
#include "Account.h"

static QofLogModule log_module = GNC_MOD_GUI;
static GtkTreeModel *account_types_tree_model = NULL;

#define TYPE_MASK "type-mask"

/* Functions for the type system */
static void
gnc_tree_model_account_types_class_init (GncTreeModelAccountTypesClass *klass);
static void
gnc_tree_model_account_types_init (GncTreeModelAccountTypes * model);
static void
gnc_tree_model_account_types_finalize (GObject * object);


/* Functions implementing GtkTreeModel */
static void
gnc_tree_model_account_types_tree_model_init (GtkTreeModelIface * iface);

typedef struct GncTreeModelAccountTypesPrivate
{
    guint32 selected;
} GncTreeModelAccountTypesPrivate;

#define GNC_TREE_MODEL_ACCOUNT_TYPES_GET_PRIVATE(o)  \
   ((GncTreeModelAccountTypesPrivate*)gnc_tree_model_account_types_get_instance_private((GncTreeModelAccountTypes*)o))

static GObjectClass *parent_class = NULL;

G_DEFINE_TYPE_WITH_CODE(GncTreeModelAccountTypes, gnc_tree_model_account_types, G_TYPE_OBJECT,
			G_ADD_PRIVATE(GncTreeModelAccountTypes)
			G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL,
					      gnc_tree_model_account_types_tree_model_init))

static void
gnc_tree_model_account_types_class_init (GncTreeModelAccountTypesClass * klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = gnc_tree_model_account_types_finalize;
}

static void
gnc_tree_model_account_types_init (GncTreeModelAccountTypes * model)
{
    while (model->stamp == 0)
    {
        model->stamp = g_random_int ();
    }
}

static void
gnc_tree_model_account_types_finalize (GObject * object)
{
    g_return_if_fail (object != NULL);
    g_return_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_TYPES (object));

    G_OBJECT_CLASS (parent_class)->finalize (object);
}

GtkTreeModel *
gnc_tree_model_account_types_new (guint32 selected)
{
    GncTreeModelAccountTypes *model;
    GncTreeModelAccountTypesPrivate *priv;

    model = g_object_new (GNC_TYPE_TREE_MODEL_ACCOUNT_TYPES, NULL);
    priv = GNC_TREE_MODEL_ACCOUNT_TYPES_GET_PRIVATE(model);
    priv->selected = selected;

    return GTK_TREE_MODEL (model);
}

static GtkTreeModel *
gnc_tree_model_account_types_master(void)
{
    if (!account_types_tree_model)
        account_types_tree_model = gnc_tree_model_account_types_new(0);
    return account_types_tree_model;
}


static gboolean
gnc_tree_model_account_types_is_valid (GtkTreeModel *model,
                                       GtkTreeIter *iter, gpointer data)
{
    GNCAccountType type;
    GObject *f_model = G_OBJECT (data);
    guint32 valid_types = GPOINTER_TO_UINT (g_object_get_data (
            f_model, TYPE_MASK));

    gtk_tree_model_get (model, iter,
                        GNC_TREE_MODEL_ACCOUNT_TYPES_COL_TYPE, &type, -1);
    return (valid_types & (1 << type)) ? TRUE : FALSE;
}

GtkTreeModel *
gnc_tree_model_account_types_filter_using_mask (guint32 types)
{
    GtkTreeModel *f_model;

    f_model = gtk_tree_model_filter_new (gnc_tree_model_account_types_master (),
                                         NULL);
    g_object_set_data (G_OBJECT (f_model), TYPE_MASK, GUINT_TO_POINTER (types));
    gtk_tree_model_filter_set_visible_func (
        GTK_TREE_MODEL_FILTER (f_model), gnc_tree_model_account_types_is_valid,
        f_model, NULL);

    return f_model;
}

void
gnc_tree_model_account_types_set_mask (GtkTreeModel *f_model,
                                       guint32 types)
{
    g_return_if_fail (f_model);

    g_object_set_data (G_OBJECT (f_model), TYPE_MASK, GUINT_TO_POINTER (types));
    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (f_model));
}

guint32
gnc_tree_model_account_types_get_mask (GtkTreeModel *f_model)
{
    g_return_val_if_fail (f_model, 0);

    return GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (f_model), TYPE_MASK));
}

guint32
gnc_tree_model_account_types_get_selection (GtkTreeSelection *sel)
{
    GtkTreeModel *f_model, *model;
    GtkTreePath *path;
    GtkTreeView *view;
    GList *list, *node;
    guint32 bits = 0;

    g_return_val_if_fail(GTK_IS_TREE_SELECTION(sel), 0);
    view = gtk_tree_selection_get_tree_view(sel);
    g_return_val_if_fail (view, 0);

    /* circumvent a bug in gtk+ not always filling f_model */
    f_model = NULL;
    list = gtk_tree_selection_get_selected_rows(sel, &f_model);
    if (!f_model)
        f_model = gtk_tree_view_get_model(view);

    model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
    if (model != account_types_tree_model)
        PERR("TreeSelection's TreeModel is not the account-types Model");
    else
    {
        for (node = list; node; node = node->next)
        {
            path = gtk_tree_model_filter_convert_path_to_child_path(
                       GTK_TREE_MODEL_FILTER(f_model), (GtkTreePath*)node->data);
            if (!path || gtk_tree_path_get_depth(path) != 1)
            {
                PERR("Invalid Account-types TreePath.");
                continue;
            }
            bits |= (1 << gtk_tree_path_get_indices(path)[0]);
        }
    }

    g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL);
    g_list_free (list);

    return bits;
}

GNCAccountType
gnc_tree_model_account_types_get_selection_single(GtkTreeSelection *sel)
{
    gint i;
    guint32 selected = gnc_tree_model_account_types_get_selection(sel);

    for (i = 0; i < NUM_ACCOUNT_TYPES; i++)
        if (selected & (1 << i))
            return i;
    return ACCT_TYPE_NONE;
}

GNCAccountType
gnc_tree_model_account_types_get_active_combo (GtkComboBox *combo)
{
    GtkTreeModelSort   *s_model;
    GtkTreeModelFilter *f_model;
    GtkTreeIter iter;
    guint32 bits = 0;
    gint i;

    g_return_val_if_fail (GTK_IS_COMBO_BOX(combo), 0);

    if (gtk_combo_box_get_active_iter (combo, &iter))
    {
        s_model = GTK_TREE_MODEL_SORT(gtk_combo_box_get_model (combo));
        f_model = GTK_TREE_MODEL_FILTER(gtk_tree_model_sort_get_model (s_model));

        if (gtk_tree_model_filter_get_model (f_model) != account_types_tree_model)
            PERR("TreeSelection's TreeModel is not the account-types Model");
        else
        {
            GtkTreePath *s_path = gtk_tree_model_get_path (GTK_TREE_MODEL(s_model), &iter);
            GtkTreePath *f_path = gtk_tree_model_sort_convert_path_to_child_path (s_model, s_path);
            GtkTreePath *path = gtk_tree_model_filter_convert_path_to_child_path (f_model, f_path);

            gtk_tree_path_free (f_path);
            gtk_tree_path_free (s_path);

            if (!path || gtk_tree_path_get_depth (path) != 1)
            {
                PERR("Invalid Account-types TreePath.");
                gtk_tree_path_free (path);
                return ACCT_TYPE_NONE;
            }
            bits |= (1 << gtk_tree_path_get_indices (path)[0]);
            gtk_tree_path_free (path);
        }
    }
    for (i = 0; i < NUM_ACCOUNT_TYPES; i++)
        if (bits & (1 << i))
            return i;
    return ACCT_TYPE_NONE;
}

void
gnc_tree_model_account_types_set_selection (GtkTreeSelection *sel,
        guint32 selected)
{
    GtkTreePath *path;
    GtkTreeModelFilter *f_model;
    gint i;
    GtkTreeView *view;

    g_return_if_fail(GTK_IS_TREE_SELECTION(sel));
    view = gtk_tree_selection_get_tree_view(sel);
    g_return_if_fail (view);
    f_model = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(view));
    g_return_if_fail(gtk_tree_model_filter_get_model(f_model) ==
                     account_types_tree_model);
    gtk_tree_selection_unselect_all(sel);
    path = gtk_tree_path_new_first();

    for (i = 0; i < NUM_ACCOUNT_TYPES; i++)
    {
        if (selected & (1 << i))
        {
            GtkTreePath *f_path = gtk_tree_model_filter_convert_child_path_to_path(
                                      f_model, path);
            gtk_tree_selection_select_path(sel, f_path);
            gtk_tree_view_scroll_to_cell(view, f_path, NULL, FALSE, 0.0, 0.0);
            gtk_tree_path_free(f_path);
        }
        gtk_tree_path_next(path);
    }
    gtk_tree_path_free(path);
}

void
gnc_tree_model_account_types_set_active_combo (GtkComboBox *combo,
                                               guint32 selected)
{
    GtkTreePath *path;
    GtkTreeModelFilter *f_model;
    GtkTreeModelSort   *s_model;
    gint i;

    g_return_if_fail (GTK_IS_COMBO_BOX(combo));

    s_model = GTK_TREE_MODEL_SORT(gtk_combo_box_get_model (combo));
    f_model = GTK_TREE_MODEL_FILTER(gtk_tree_model_sort_get_model (s_model));
    g_return_if_fail (gtk_tree_model_filter_get_model (f_model) ==
                      account_types_tree_model);

    gtk_combo_box_set_active (combo, -1);

    path = gtk_tree_path_new_first ();

    for (i = 0; i < NUM_ACCOUNT_TYPES; i++)
    {
        if (selected & (1 << i))
        {
            GtkTreeIter iter;
            GtkTreePath *f_path = gtk_tree_model_filter_convert_child_path_to_path (f_model, path);
            GtkTreePath *s_path = gtk_tree_model_sort_convert_child_path_to_path (s_model, f_path);

            gtk_tree_model_get_iter (GTK_TREE_MODEL(s_model), &iter, s_path);
            gtk_combo_box_set_active_iter (combo, &iter);
            gtk_tree_path_free (f_path);
            gtk_tree_path_free (s_path);
        }
        gtk_tree_path_next (path);
    }
    gtk_tree_path_free (path);
}

/* Static functions implementing GtkTreeModel */

static GtkTreeModelFlags
gnc_tree_model_account_types_get_flags (GtkTreeModel * tree_model)
{
    return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
}

static int
gnc_tree_model_account_types_get_n_columns (GtkTreeModel * tree_model)
{
    return GNC_TREE_MODEL_ACCOUNT_TYPES_NUM_COLUMNS;
}

static GType
gnc_tree_model_account_types_get_column_type (GtkTreeModel * tree_model,
        int index)
{
    g_return_val_if_fail(GNC_IS_TREE_MODEL_ACCOUNT_TYPES (tree_model),
                         G_TYPE_INVALID);
    g_return_val_if_fail((index < GNC_TREE_MODEL_ACCOUNT_TYPES_NUM_COLUMNS)
                         && (index >= 0), G_TYPE_INVALID);

    switch (index)
    {
    case GNC_TREE_MODEL_ACCOUNT_TYPES_COL_TYPE:
        return G_TYPE_INT;
    case GNC_TREE_MODEL_ACCOUNT_TYPES_COL_NAME:
        return G_TYPE_STRING;
    case GNC_TREE_MODEL_ACCOUNT_TYPES_COL_SELECTED:
        return G_TYPE_BOOLEAN;
    default:
        g_assert_not_reached ();
        return G_TYPE_INVALID;
    }
}

static gboolean
gnc_tree_model_account_types_get_iter (GtkTreeModel * tree_model,
                                       GtkTreeIter * iter, GtkTreePath * path)
{
    GncTreeModelAccountTypes *model = GNC_TREE_MODEL_ACCOUNT_TYPES(tree_model);
    gint i;

    g_return_val_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_TYPES (model), FALSE);
    g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);

    i = gtk_tree_path_get_indices (path)[0];

    if (i > ACCT_TYPE_NONE && i < NUM_ACCOUNT_TYPES)
    {
        iter->stamp = model->stamp;
        iter->user_data = GINT_TO_POINTER (i);
        return TRUE;
    }

    iter->stamp = 0;
    return FALSE;
}

static GtkTreePath *
gnc_tree_model_account_types_get_path (GtkTreeModel * tree_model,
                                       GtkTreeIter * iter)
{
    GncTreeModelAccountTypes *model = GNC_TREE_MODEL_ACCOUNT_TYPES(tree_model);
    GtkTreePath *path;

    g_return_val_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_TYPES (model), NULL);
    g_return_val_if_fail (iter != NULL, NULL);
    g_return_val_if_fail (iter->stamp == model->stamp, NULL);

    path = gtk_tree_path_new ();

    gtk_tree_path_append_index (path, GPOINTER_TO_INT (iter->user_data));

    return path;
}

static void
gnc_tree_model_account_types_get_value (GtkTreeModel * tree_model,
                                        GtkTreeIter * iter, int column,
                                        GValue * value)
{
    GncTreeModelAccountTypes *model = GNC_TREE_MODEL_ACCOUNT_TYPES(tree_model);
    GncTreeModelAccountTypesPrivate *priv;

    g_return_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_TYPES (model));
    g_return_if_fail (iter != NULL);
    g_return_if_fail (iter->stamp == model->stamp);

    priv = GNC_TREE_MODEL_ACCOUNT_TYPES_GET_PRIVATE(model);
    switch (column)
    {
    case GNC_TREE_MODEL_ACCOUNT_TYPES_COL_TYPE:
        g_value_init (value, G_TYPE_INT);
        g_value_set_int (value, GPOINTER_TO_INT (iter->user_data));
        break;
    case GNC_TREE_MODEL_ACCOUNT_TYPES_COL_NAME:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (value, xaccAccountGetTypeStr (
                                GPOINTER_TO_INT (iter->user_data)));
        break;
    case GNC_TREE_MODEL_ACCOUNT_TYPES_COL_SELECTED:
        g_value_init (value, G_TYPE_BOOLEAN);
        g_value_set_boolean (value, priv->selected &
                             (1 << GPOINTER_TO_INT (iter->user_data)));
        break;
    default:
        g_assert_not_reached ();
    }
}

static gboolean
gnc_tree_model_account_types_iter_next (GtkTreeModel * tree_model,
                                        GtkTreeIter * iter)
{
    GncTreeModelAccountTypes *model = GNC_TREE_MODEL_ACCOUNT_TYPES(tree_model);

    g_return_val_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_TYPES (model), FALSE);
    g_return_val_if_fail (iter != NULL, FALSE);
    g_return_val_if_fail (iter->stamp == model->stamp, FALSE);

    if (GPOINTER_TO_INT (iter->user_data) < NUM_ACCOUNT_TYPES - 1)
    {
        iter->user_data = GINT_TO_POINTER(
                              GPOINTER_TO_INT(iter->user_data) + 1);
        return TRUE;
    }

    iter->stamp = 0;
    return FALSE;
}

static gboolean
gnc_tree_model_account_types_iter_children (GtkTreeModel * tree_model,
        GtkTreeIter * iter,
        GtkTreeIter * parent)
{

    g_return_val_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_TYPES(tree_model), FALSE);

    if (parent != NULL)
        return FALSE;

    iter->stamp = GNC_TREE_MODEL_ACCOUNT_TYPES (tree_model)->stamp;
    iter->user_data = GINT_TO_POINTER (0);

    return TRUE;
}

static gboolean
gnc_tree_model_account_types_iter_has_child (GtkTreeModel * tree_model,
        GtkTreeIter * iter)
{
    return FALSE;
}

static int
gnc_tree_model_account_types_iter_n_children (GtkTreeModel * tree_model,
        GtkTreeIter * iter)
{
    g_return_val_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_TYPES (tree_model), -1);

    if (iter == NULL)
        return NUM_ACCOUNT_TYPES;

    g_return_val_if_fail (
        GNC_TREE_MODEL_ACCOUNT_TYPES (tree_model)->stamp == iter->stamp, -1);

    return 0;
}

static gboolean
gnc_tree_model_account_types_iter_nth_child (GtkTreeModel * tree_model,
        GtkTreeIter * iter,
        GtkTreeIter * parent, int n)
{
    GncTreeModelAccountTypes *model;

    g_return_val_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_TYPES (tree_model), FALSE);

    if (parent != NULL)
        return FALSE;

    model = GNC_TREE_MODEL_ACCOUNT_TYPES (tree_model);

    if (n > ACCT_TYPE_NONE && n < NUM_ACCOUNT_TYPES)
    {
        iter->stamp = model->stamp;
        iter->user_data = GINT_TO_POINTER (n);
        return TRUE;
    }

    iter->stamp = 0;
    return FALSE;
}

static gboolean
gnc_tree_model_account_types_iter_parent (GtkTreeModel * tree_model,
        GtkTreeIter * iter,
        GtkTreeIter * child)
{
    return FALSE;
}

static void
gnc_tree_model_account_types_tree_model_init (GtkTreeModelIface * iface)
{
    iface->get_flags = gnc_tree_model_account_types_get_flags;
    iface->get_n_columns = gnc_tree_model_account_types_get_n_columns;
    iface->get_column_type = gnc_tree_model_account_types_get_column_type;
    iface->get_iter = gnc_tree_model_account_types_get_iter;
    iface->get_path = gnc_tree_model_account_types_get_path;
    iface->get_value = gnc_tree_model_account_types_get_value;
    iface->iter_next = gnc_tree_model_account_types_iter_next;
    iface->iter_children = gnc_tree_model_account_types_iter_children;
    iface->iter_has_child = gnc_tree_model_account_types_iter_has_child;
    iface->iter_n_children = gnc_tree_model_account_types_iter_n_children;
    iface->iter_nth_child = gnc_tree_model_account_types_iter_nth_child;
    iface->iter_parent = gnc_tree_model_account_types_iter_parent;
}

