/********************************************************************\
 * account-quickfill.h -- Create an account-name quick-fill         *
 * Copyright (C) 2004 Linas Vepstas <linas@linas.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 "account-quickfill.h"
#include "gnc-engine.h"
#include "gnc-prefs.h"
#include "gnc-ui-util.h"

/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = GNC_MOD_REGISTER;

static void shared_quickfill_pref_changed (gpointer prefs, gchar* pref,
                                           gpointer qfb);
static void listen_for_account_events (QofInstance* entity,
                                       QofEventId event_type,
                                       gpointer user_data, gpointer event_data);

/* Column indices for the list store */
#define ACCOUNT_NAME        0
#define ACCOUNT_POINTER     1
#define NUM_ACCOUNT_COLUMNS 2

/* ===================================================================== */
/* In order to speed up register starts for registers that have a huge
 * number of accounts in them (where 'huge' is >500) we build a quickfill
 * cache of account names.  This cache is needed because some users on
 * some machines experience register open times in the tens of seconds
 * type timescales.  Building the quickfill list accounts for almost
 * all of that cpu time (about 90% of the xfer_cell build time for 600
 * accounts).
 */

typedef struct
{
    QuickFill* qf;
    gboolean load_list_store;
    GtkListStore* list_store;
    QofBook* book;
    Account* root;
    gint  listener;
    AccountBoolCB dont_add_cb;
    gpointer dont_add_data;
} QFB;

static void
shared_quickfill_destroy (QofBook* book, gpointer key, gpointer user_data)
{
    QFB* qfb = user_data;
    gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
                                 GNC_PREF_ACCOUNT_SEPARATOR,
                                 shared_quickfill_pref_changed,
                                 qfb);
    gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REGISTER,
                                 GNC_PREF_SHOW_LEAF_ACCT_NAMES,
                                 shared_quickfill_pref_changed,
                                 qfb);
    gnc_quickfill_destroy (qfb->qf);
    g_object_unref (qfb->list_store);
    qof_event_unregister_handler (qfb->listener);
    g_free (qfb);
}


typedef struct find_data
{
    GList* accounts;
    GList* refs;
} find_data;

static gboolean
shared_quickfill_find_accounts (GtkTreeModel* model,
                                GtkTreePath* path,
                                GtkTreeIter* iter,
                                gpointer user_data)
{
    Account* account = NULL;
    find_data* data = user_data;
    GtkTreeRowReference* ref;
    GList* tmp;

    gtk_tree_model_get (model, iter, ACCOUNT_POINTER, &account, -1);
    for (tmp = data->accounts; tmp; tmp = g_list_next (tmp))
    {
        if (tmp->data == account)
        {
            ref = gtk_tree_row_reference_new (model, path);
            data->refs = g_list_append (data->refs, ref);
            data->accounts = g_list_delete_link (data->accounts, tmp);
            return (data->accounts == NULL);
        }
    }
    return FALSE;
}


/* Splat the account name into the shared quickfill object */
static void
load_shared_qf_cb (Account* account, gpointer data)
{
    QFB* qfb = data;
    char* name;
    GtkTreeIter iter;

    if (qfb->dont_add_cb)
    {
        gboolean skip = (qfb->dont_add_cb) (account, qfb->dont_add_data);
        if (skip)
            return;
    }

    name = gnc_get_account_name_for_register (account);
    if (NULL == name)
        return;
    gnc_quickfill_insert (qfb->qf, name, QUICKFILL_ALPHA);
    if (qfb->load_list_store)
    {
        gtk_list_store_append (qfb->list_store, &iter);
        gtk_list_store_set (qfb->list_store, &iter,
                            ACCOUNT_NAME, name,
                            ACCOUNT_POINTER, account,
                            -1);
    }
    g_free (name);
}


static void
shared_quickfill_pref_changed (gpointer prefs, gchar* pref, gpointer user_data)
{
    QFB* qfb = user_data;

    /* Reload the quickfill */
    gnc_quickfill_purge (qfb->qf);
    gtk_list_store_clear (qfb->list_store);
    qfb->load_list_store = TRUE;
    gnc_account_foreach_descendant (qfb->root, load_shared_qf_cb, qfb);
    qfb->load_list_store = FALSE;
}


/* Build the quickfill list out of account names.
 * Essentially same loop as in gnc_load_xfer_cell() above.
 */
static QFB*
build_shared_quickfill (QofBook* book, Account* root, const char* key,
                        AccountBoolCB cb, gpointer data)
{
    QFB* qfb;

    qfb = g_new0 (QFB, 1);
    qfb->qf = gnc_quickfill_new();
    qfb->book = book;
    qfb->root = root;
    qfb->listener = 0;
    qfb->dont_add_cb = cb;
    qfb->dont_add_data = data;
    qfb->load_list_store = TRUE;
    qfb->list_store      = gtk_list_store_new (NUM_ACCOUNT_COLUMNS,
                                               G_TYPE_STRING, G_TYPE_POINTER);

    gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
                           GNC_PREF_ACCOUNT_SEPARATOR,
                           shared_quickfill_pref_changed,
                           qfb);

    gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REGISTER,
                           GNC_PREF_SHOW_LEAF_ACCT_NAMES,
                           shared_quickfill_pref_changed,
                           qfb);

    gnc_account_foreach_descendant (root, load_shared_qf_cb, qfb);
    qfb->load_list_store = FALSE;

    qfb->listener = qof_event_register_handler (listen_for_account_events, qfb);

    qof_book_set_data_fin (book, key, qfb, shared_quickfill_destroy);

    return qfb;
}

QuickFill*
gnc_get_shared_account_name_quickfill (Account* root, const char* key,
                                       AccountBoolCB cb, gpointer cb_data)
{
    QFB* qfb;
    QofBook* book;

    book = gnc_account_get_book (root);
    qfb = qof_book_get_data (book, key);

    if (qfb)
        return qfb->qf;

    qfb = build_shared_quickfill (book, root, key, cb, cb_data);
    return qfb->qf;
}

GtkListStore*
gnc_get_shared_account_name_list_store (Account* root, const char* key,
                                        AccountBoolCB cb, gpointer cb_data)
{
    QFB* qfb;
    QofBook* book;

    book = gnc_account_get_book (root);
    qfb = qof_book_get_data (book, key);

    if (qfb)
        return qfb->list_store;

    qfb = build_shared_quickfill (book, root, key, cb, cb_data);
    return qfb->list_store;
}

/* Since we are maintaining a 'global' quickfill list, we need to
 * update it whenever the user creates a new account.  So listen
 * for account modification events, and add new accounts.
 */
static void
listen_for_account_events (QofInstance* entity, QofEventId event_type,
                           gpointer user_data, gpointer event_data)
{
    QFB* qfb = user_data;
    QuickFill* qf = qfb->qf;
    QuickFill* match;
    char* name;
    const char* match_str;
    Account* account;
    GtkTreeIter iter;
    find_data data = { 0 };
    GtkTreePath* path;
    GList* tmp;

    if (0 == (event_type & (QOF_EVENT_MODIFY | QOF_EVENT_ADD | QOF_EVENT_REMOVE)))
        return;

    if (!GNC_IS_ACCOUNT (entity))
        return;
    account = GNC_ACCOUNT (entity);

    ENTER ("entity %p, event type %x, user data %p, ecent data %p",
           entity, event_type, user_data, event_data);

    if (gnc_account_get_root (account) != qfb->root)
    {
        LEAVE ("root account mismatch");
        return;
    }

    name = gnc_get_account_name_for_register (account);
    if (NULL == name)
    {
        LEAVE ("account has no name");
        return;
    }

    switch (event_type)
    {
    case QOF_EVENT_MODIFY:
        DEBUG ("modify %s", name);

        /* Find the account (and all its descendants) in the model.  The
         * full name of all these accounts has changed. */
        data.accounts = gnc_account_get_descendants (account);
        data.accounts = g_list_prepend (data.accounts, account);
        gtk_tree_model_foreach (GTK_TREE_MODEL (qfb->list_store),
                                shared_quickfill_find_accounts, &data);

        /* Update the existing items in the list store.  Its possible
         * that the change has caused an existing item to now become
         * hidden, in which case it needs to be removed from the list
         * store.  Otherwise its a simple update of the name string. */
        for (tmp = data.refs; tmp; tmp = g_list_next (tmp))
        {
            gchar* old_name, *new_name;
            path = gtk_tree_row_reference_get_path (tmp->data);
            gtk_tree_row_reference_free (tmp->data);
            if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (qfb->list_store),
                                          &iter, path))
            {
                gtk_tree_path_free (path);
                continue;
            }
            gtk_tree_path_free (path);
            gtk_tree_model_get (GTK_TREE_MODEL (qfb->list_store), &iter,
                                ACCOUNT_POINTER, &account,
                                ACCOUNT_NAME, &old_name,
                                -1);

            new_name = gnc_get_account_name_for_register (account);

            /* check if the name has changed */
            match = gnc_quickfill_get_string_match (qf, old_name);
            if (match && (g_strcmp0 (old_name, new_name) != 0))
                gnc_quickfill_remove (qf, old_name, QUICKFILL_ALPHA);

            if (qfb->dont_add_cb &&
                qfb->dont_add_cb (account, qfb->dont_add_data))
            {
                gnc_quickfill_remove (qf, new_name, QUICKFILL_ALPHA);
                gtk_list_store_remove (qfb->list_store, &iter);
            }
            else
            {
                gnc_quickfill_insert (qf, new_name, QUICKFILL_ALPHA);
                gtk_list_store_set (qfb->list_store, &iter,
                                    ACCOUNT_NAME, new_name,
                                    -1);
            }
            g_free (old_name);
            g_free (new_name);
        }

        /* Any accounts that weren't found in the tree are accounts that
         * were hidden but have now become visible. Add them to the list
         * store. */
        for (tmp = data.accounts; tmp; tmp = g_list_next (tmp))
        {
            account = tmp->data;
            if (qfb->dont_add_cb)
            {
                if (qfb->dont_add_cb (account, qfb->dont_add_data))
                {
                    continue;
                }
            }
            gnc_quickfill_insert (qf, name, QUICKFILL_ALPHA);
            gtk_list_store_append (qfb->list_store, &iter);
            gtk_list_store_set (qfb->list_store, &iter,
                                ACCOUNT_NAME, name,
                                ACCOUNT_POINTER, account,
                                -1);
        }
        break;

    case QOF_EVENT_REMOVE:
        DEBUG ("remove %s", name);

        /* Remove from qf */
        gnc_quickfill_remove (qfb->qf, name, QUICKFILL_ALPHA);

        /* Does the account exist in the model? */
        data.accounts = g_list_append (NULL, account);
        gtk_tree_model_foreach (GTK_TREE_MODEL (qfb->list_store),
                                shared_quickfill_find_accounts, &data);

        /* Remove from list store */
        for (tmp = data.refs; tmp; tmp = g_list_next (tmp))
        {
            path = gtk_tree_row_reference_get_path (tmp->data);
            gtk_tree_row_reference_free (tmp->data);
            if (gtk_tree_model_get_iter (GTK_TREE_MODEL (qfb->list_store),
                                         &iter, path))
            {
                gtk_list_store_remove (qfb->list_store, &iter);
            }
            gtk_tree_path_free (path);
        }
        break;

    case QOF_EVENT_ADD:
        DEBUG ("add %s", name);

        if (qfb->dont_add_cb &&
            qfb->dont_add_cb (account, qfb->dont_add_data))
            break;

        match = gnc_quickfill_get_string_match (qf, name);
        if (match)
        {
            match_str = gnc_quickfill_string (match);
            if (match_str && (g_strcmp0 (match_str, name) != 0))
            {
                PINFO ("got match for %s", name);
                break;
            }
        }

        PINFO ("insert new account %s into qf=%p", name, qf);
        gnc_quickfill_insert (qf, name, QUICKFILL_ALPHA);
        gtk_list_store_append (qfb->list_store, &iter);
        gtk_list_store_set (qfb->list_store, &iter,
                            ACCOUNT_NAME, name,
                            ACCOUNT_POINTER, account,
                            -1);
        break;

    default:
        DEBUG ("other %s", name);
        break;
    }

    if (data.accounts)
        g_list_free (data.accounts);
    if (data.refs)
        g_list_free (data.refs);
    g_free (name);
    LEAVE (" ");
}

/* ====================== END OF FILE ================================== */
