/********************************************************************\
 * dialog-account-picker.c -- window for picking a Gnucash account  *
 * from the QIF importer.                                           *
 * Copyright (C) 2000-2001 Bill Gribble <grib@billgribble.com>      *
 * Copyright (c) 2006 David Hampton <hampton@employees.org>         *
 *                                                                  *
 * 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 <glib/gi18n.h>
#include <stdio.h>
#include <libguile.h>

#include "dialog-account-picker.h"
#include "dialog-utils.h"
#include "assistant-qif-import.h"
#include "gnc-gui-query.h"
#include "gnc-prefs.h"
#include "gnc-ui-util.h"
#include "guile-mappings.h"
#include "gnc-guile-utils.h"
#include "gnc-ui.h" /* for GNC_RESPONSE_NEW */

#define GNC_PREFS_GROUP   "dialogs.import.qif.account-picker"

enum account_cols
{
    ACCOUNT_COL_NAME = 0,
    ACCOUNT_COL_FULLNAME,
    ACCOUNT_COL_PLACEHOLDER,
    ACCOUNT_COL_CHECK,
    NUM_ACCOUNT_COLS
};

struct _accountpickerdialog
{
    GtkWidget       * dialog;
    GtkTreeView     * treeview;
    GtkWidget       * pwhbox;
    GtkWidget       * pwarning;
    GtkWidget       * ok_button;
    QIFImportWindow * qif_wind;
    SCM               map_entry;
    gchar           * selected_name;
};

void gnc_ui_qif_account_picker_new_cb (GtkButton * w, gpointer user_data);

/****************************************************************
 * acct_tree_add_accts
 *
 * Given a Scheme list of accounts, this function populates a
 * GtkTreeStore from them. If the search_name and reference
 * parameters are provided, and an account is found whose full
 * name matches search_name, then a GtkTreeRowReference* will be
 * returned in the reference parameter.
 ****************************************************************/
static void
acct_tree_add_accts(SCM accts,
                    GtkTreeStore *store,
                    GtkTreeIter *parent,
                    const char *base_name,
                    const char *search_name,
                    GtkTreeRowReference **reference)
{
    GtkTreeIter  iter;
    char         * compname;
    char         * acctname;
    gboolean     leafnode;
    SCM          current;
    gboolean     checked;
    Account      * account;

    while (!scm_is_null(accts))
    {
        gboolean placeholder = FALSE;
        current = SCM_CAR(accts);

        if (scm_is_null(current))
        {
            g_critical("QIF import: BUG DETECTED in acct_tree_add_accts!");
            accts = SCM_CDR(accts);
            continue;
        }

        if (scm_is_string(SCM_CAR(current)))
            compname = gnc_scm_to_utf8_string (SCM_CAR(current));
        else
            compname = g_strdup("");

        if (!scm_is_null(SCM_CADDR(current)))
        {
            leafnode = FALSE;
        }
        else
        {
            leafnode = TRUE;
        }

        /* compute full name */
        if (base_name && *base_name)
        {
            acctname = g_strjoin(gnc_get_account_separator_string(),
                                 base_name, compname, (char *)NULL);
        }
        else
        {
            acctname = g_strdup(compname);
        }

        checked = (SCM_CADR(current) == SCM_BOOL_T);

        account = gnc_account_lookup_by_full_name (gnc_get_current_root_account(), acctname);
        if (account)
            placeholder = xaccAccountGetPlaceholder (account);

        gtk_tree_store_append(store, &iter, parent);
        gtk_tree_store_set(store, &iter,
                           ACCOUNT_COL_NAME, compname,
                           ACCOUNT_COL_FULLNAME, acctname,
                           ACCOUNT_COL_PLACEHOLDER, placeholder,
                           ACCOUNT_COL_CHECK, checked,
                           -1);

        if (reference && !*reference &&
                search_name && (g_utf8_collate(search_name, acctname) == 0))
        {
            GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
            *reference = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), path);
            gtk_tree_path_free(path);
        }

        if (!leafnode)
        {
            acct_tree_add_accts(SCM_CADDR(current), store, &iter, acctname,
                                search_name, reference);
        }

        g_free(acctname);
        g_free(compname);

        accts = SCM_CDR(accts);
    }
}


/****************************************************************
 * build_acct_tree
 *
 * This function refreshes the contents of the account tree.
 ****************************************************************/
static void
build_acct_tree(QIFAccountPickerDialog * picker, QIFImportWindow * import)
{
    SCM  get_accts = scm_c_eval_string("qif-import:get-all-accts");
    SCM  acct_tree;
    GtkTreeStore *store;
    GtkTreePath *path;
    GtkTreeSelection* selection;
    GtkTreeRowReference *reference = NULL;
    gchar *name_to_select;

    g_return_if_fail(picker && import);

    /* Get an account tree with all existing and to-be-imported accounts. */
    acct_tree = scm_call_1(get_accts,
                           gnc_ui_qif_import_assistant_get_mappings(import));

    /* Rebuild the store.
     * NOTE: It is necessary to save a copy of the name to select, because
     *       when the store is cleared, everything becomes unselected. */
    name_to_select = g_strdup(picker->selected_name);
    store = GTK_TREE_STORE(gtk_tree_view_get_model(picker->treeview));
    gtk_tree_store_clear(store);
    acct_tree_add_accts(acct_tree, store, NULL, NULL, name_to_select, &reference);
    g_free(name_to_select);

    /* Select and display the indicated account (if it was found). */
    if (reference)
    {
        selection = gtk_tree_view_get_selection(picker->treeview);
        path = gtk_tree_row_reference_get_path(reference);
        if (path)
        {
            gtk_tree_view_expand_to_path(picker->treeview, path);
            gtk_tree_selection_select_path(selection, path);
            gtk_tree_view_scroll_to_cell (picker->treeview, path,
                                          NULL, TRUE, 0.5, 0.0);
            gtk_tree_path_free(path);
        }
        gtk_tree_row_reference_free(reference);
    }
}


/****************************************************************
 * gnc_ui_qif_account_picker_new_cb
 *
 * This handler is invoked when the user wishes to create a new
 * account.
 ****************************************************************/
void
gnc_ui_qif_account_picker_new_cb(GtkButton * w, gpointer user_data)
{
    QIFAccountPickerDialog * wind = user_data;
    SCM name_setter = scm_c_eval_string("qif-map-entry:set-gnc-name!");
    const gchar *name;
    int response;
    gchar *fullname;
    GtkWidget *dlg, *entry;

    /* Create a dialog to get the new account name. */
    dlg = gtk_message_dialog_new(GTK_WINDOW(wind->dialog),
                                 GTK_DIALOG_DESTROY_WITH_PARENT,
                                 GTK_MESSAGE_QUESTION,
                                 GTK_BUTTONS_OK_CANCEL,
                                 "%s", _("Enter a name for the account"));
    gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
    entry = gtk_entry_new();
    gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
    gtk_entry_set_max_length(GTK_ENTRY(entry), 250);
    gtk_widget_show(entry);
    gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area (GTK_DIALOG(dlg))), entry);

    /* Run the dialog to get the new account name. */
    response = gtk_dialog_run(GTK_DIALOG(dlg));
    name = gtk_entry_get_text(GTK_ENTRY(entry));

    /* Did the user enter a name and click OK? */
    if (response == GTK_RESPONSE_OK && name && *name)
    {
        /* If an account is selected, this will be a new subaccount. */
        if (wind->selected_name && *(wind->selected_name))
            /* We have the short name; determine the full name. */
            fullname = g_strjoin(gnc_get_account_separator_string(),
                                 wind->selected_name, name, (char *)NULL);
        else
            fullname = g_strdup(name);

        /* Save the full name and update the map entry. */
        g_free(wind->selected_name);
        wind->selected_name = fullname;
        scm_call_2(name_setter, wind->map_entry, scm_from_utf8_string(fullname));
    }
    gtk_widget_destroy(dlg);

    /* Refresh the tree display and give it the focus. */
    build_acct_tree(wind, wind->qif_wind);
    gtk_widget_grab_focus(GTK_WIDGET(wind->treeview));
}


/****************************************************************
 * gnc_ui_qif_account_picker_changed_cb
 *
 ****************************************************************/
static void
gnc_ui_qif_account_picker_changed_cb(GtkTreeSelection *selection,
                                     gpointer          user_data)
{
    QIFAccountPickerDialog * wind = user_data;
    SCM name_setter = scm_c_eval_string("qif-map-entry:set-gnc-name!");
    GtkTreeModel *model;
    GtkTreeIter iter;
    gboolean placeholder;

    gtk_widget_set_sensitive (wind->ok_button, TRUE); // enable OK button

    g_free(wind->selected_name);
    if (gtk_tree_selection_get_selected(selection, &model, &iter))
    {
        gtk_tree_model_get(model, &iter,
                           ACCOUNT_COL_PLACEHOLDER, &placeholder,
                           ACCOUNT_COL_FULLNAME, &wind->selected_name,
                           -1);
        scm_call_2(name_setter, wind->map_entry,
                   wind->selected_name ? scm_from_utf8_string(wind->selected_name) : SCM_BOOL_F);

        if (placeholder)
        {
            gchar *text = g_strdup_printf (_("The account %s is a placeholder account and does not allow "
                                             "transactions. Please choose a different account."), wind->selected_name);

            gtk_label_set_text (GTK_LABEL(wind->pwarning), text);
            gnc_label_set_alignment (wind->pwarning, 0.0, 0.5);
            gtk_widget_show_all (GTK_WIDGET(wind->pwhbox));
            g_free (text);

            gtk_widget_set_sensitive (wind->ok_button, FALSE); // disable OK button
        }
        else
            gtk_widget_hide (GTK_WIDGET(wind->pwhbox)); // hide the placeholder warning
    }
    else
    {
        wind->selected_name = NULL;
    }
}


/****************************************************************
 * gnc_ui_qif_account_picker_row_activated_cb
 *
 ****************************************************************/
static void
gnc_ui_qif_account_picker_row_activated_cb(GtkTreeView *view,
        GtkTreePath *path,
        GtkTreeViewColumn *column,
        gpointer user_data)
{
    QIFAccountPickerDialog *wind = user_data;
    g_return_if_fail(wind);

    gtk_dialog_response(GTK_DIALOG(wind->dialog), GTK_RESPONSE_OK);
}


/****************************************************************
 * gnc_ui_qif_account_picker_map_cb
 *
 ****************************************************************/
static int
gnc_ui_qif_account_picker_map_cb(GtkWidget * w, gpointer user_data)
{
    QIFAccountPickerDialog * wind = user_data;

    /* update the tree display with all the existing accounts plus all
     * the ones the QIF importer thinks it will be creating.  this will
     * also select the map_entry line. */
    build_acct_tree(wind, wind->qif_wind);
    return FALSE;
}


/****************************************************************
 * dialog_response_cb
 *
 ****************************************************************/
static void
dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data)
{
    QIFAccountPickerDialog * wind = user_data;
    GtkTreeModel *model;
    GtkTreeIter iter;
    gboolean placeholder = TRUE;

    if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection
                                        (wind->treeview), &model, &iter))
        gtk_tree_model_get (model, &iter,
                            ACCOUNT_COL_PLACEHOLDER, &placeholder, -1);

    if (response_id == GTK_RESPONSE_OK)
    {
        if (placeholder)
            g_signal_stop_emission_by_name (dialog, "response");
    }
}


/****************************************************************
 * qif_account_picker_dialog
 *
 * Select an account from the ones that the engine knows about,
 * plus those that will be created by the QIF import.  If the
 * user clicks OK, map_entry is changed and TRUE is returned.
 * If the clicks Cancel instead, FALSE is returned. Modal.
 ****************************************************************/
gboolean
qif_account_picker_dialog(GtkWindow *parent, QIFImportWindow * qif_wind, SCM map_entry)
{
    QIFAccountPickerDialog * wind;
    SCM gnc_name     = scm_c_eval_string("qif-map-entry:gnc-name");
    SCM set_gnc_name = scm_c_eval_string("qif-map-entry:set-gnc-name!");
    SCM orig_acct    = scm_call_1(gnc_name, map_entry);
    int response;
    GtkBuilder *builder;

    wind = g_new0(QIFAccountPickerDialog, 1);

    /* Save the map entry. */
    wind->map_entry = map_entry;
    scm_gc_protect_object(wind->map_entry);

    /* Set the initial account to be selected. */
    if (scm_is_string(orig_acct))
        wind->selected_name = gnc_scm_to_utf8_string (orig_acct);

    builder = gtk_builder_new();
    gnc_builder_add_from_file (builder, "dialog-account-picker.glade", "qif_import_account_picker_dialog");

    /* Connect all the signals */
    gtk_builder_connect_signals (builder, wind);

    wind->dialog     = GTK_WIDGET(gtk_builder_get_object (builder, "qif_import_account_picker_dialog"));
    wind->treeview   = GTK_TREE_VIEW(gtk_builder_get_object (builder, "account_tree"));
    wind->pwhbox     = GTK_WIDGET(gtk_builder_get_object (builder, "placeholder_warning_hbox"));
    wind->pwarning   = GTK_WIDGET(gtk_builder_get_object (builder, "placeholder_warning_label"));
    wind->ok_button  = GTK_WIDGET(gtk_builder_get_object (builder, "okbutton"));
    wind->qif_wind   = qif_wind;

    gtk_window_set_transient_for (GTK_WINDOW (wind->dialog), parent);

    {
        GtkTreeSelection *selection;
        GtkTreeStore *store;
        GtkCellRenderer *renderer;
        GtkTreeViewColumn *column;

        store = gtk_tree_store_new(NUM_ACCOUNT_COLS, G_TYPE_STRING, G_TYPE_STRING,
                                   G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
        gtk_tree_view_set_model(wind->treeview, GTK_TREE_MODEL(store));
        g_object_unref(store);

        renderer = gtk_cell_renderer_text_new();
        column = gtk_tree_view_column_new_with_attributes(_("Account"),
                 renderer,
                 "text",
                 ACCOUNT_COL_NAME,
                 NULL);
        g_object_set(column, "expand", TRUE, NULL);
        gtk_tree_view_append_column(wind->treeview, column);

        renderer = gtk_cell_renderer_toggle_new();
        g_object_set(renderer, "activatable", FALSE, NULL);
        column = gtk_tree_view_column_new_with_attributes(_("Placeholder?"),
                 renderer,
                 "active",
                 ACCOUNT_COL_PLACEHOLDER,
                 NULL);
        gtk_tree_view_append_column(wind->treeview, column);

        renderer = gtk_cell_renderer_toggle_new();
        g_object_set(renderer, "activatable", FALSE, NULL);
        column = gtk_tree_view_column_new_with_attributes(_("New?"),
                 renderer,
                 "active",
                 ACCOUNT_COL_CHECK,
                 NULL);
        gtk_tree_view_append_column(wind->treeview, column);

        selection = gtk_tree_view_get_selection(wind->treeview);
        g_signal_connect(selection, "changed",
                         G_CALLBACK(gnc_ui_qif_account_picker_changed_cb), wind);
        g_signal_connect(wind->treeview, "row-activated",
                         G_CALLBACK(gnc_ui_qif_account_picker_row_activated_cb),
                         wind);
    }

    g_signal_connect_after(wind->dialog, "map",
                           G_CALLBACK(gnc_ui_qif_account_picker_map_cb),
                           wind);

    gnc_restore_window_size (GNC_PREFS_GROUP,
                             GTK_WINDOW(wind->dialog), parent);

    /* this is to get the checkmarks set up right.. it will get called
     * again after the window is mapped. */
    build_acct_tree(wind, wind->qif_wind);

    g_signal_connect (wind->dialog, "response",
                      G_CALLBACK (dialog_response_cb), wind);

    do
    {
        response = gtk_dialog_run(GTK_DIALOG(wind->dialog));
    }
    while (response == GNC_RESPONSE_NEW);
    gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(wind->dialog));
    gtk_widget_destroy(wind->dialog);
    g_object_unref(G_OBJECT(builder));

    scm_gc_unprotect_object(wind->map_entry);
    g_free(wind->selected_name);
    g_free(wind);

    if (response == GTK_RESPONSE_OK)
        return TRUE;

    /* Restore the original mapping. */
    scm_call_2(set_gnc_name, map_entry, orig_acct);

    return FALSE;
}
