/***************************************************************************
 *            qofreference.c
 *
 *  Mon Feb 13 21:06:44 2006
 *  Copyright  2006  Neil Williams
 *  linux@codehelp.co.uk
 ****************************************************************************/
/*
 *  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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <glib.h>
#include "qofreference.h"

static void
entity_set_reference_cb(QofInstance *ent, gpointer user_data)
{
    void (*reference_setter) (QofInstance*, QofInstance*);
    void (*choice_setter) (QofInstance*, QofInstance*);
    void (*collect_setter)(QofInstance*, QofCollection*);
    QofInstanceReference *ref;
    GList *book_ref_list;
    QofCollection *coll;
    QofIdType type;
    QofInstance *reference;
    QofBook *partial_book;

    partial_book = (QofBook*)user_data;
    g_return_if_fail(partial_book && ent);
    reference = NULL;
    coll = NULL;
    book_ref_list = qof_book_get_data(partial_book, ENTITYREFERENCE);
    while (book_ref_list)
    {
        ref = (QofInstanceReference*)book_ref_list->data;
        if (0 == guid_compare(ref->ref_guid, qof_instance_get_guid(ent)))
        {
            /* avoid setting the entity's own guid as a reference. */
            book_ref_list = g_list_next(book_ref_list);
            continue;
        }
        if (qof_object_is_choice(ent->e_type))
        {
            type = ref->choice_type;
        }
        type = ref->param->param_type;
        coll = qof_book_get_collection(partial_book, type);
        reference = qof_collection_lookup_entity(coll, ref->ref_guid);
        reference_setter = (void(*)(QofInstance*, QofInstance*))ref->param->param_setfcn;
        if ((reference) && (reference_setter))
        {
            qof_begin_edit((QofInstance*)ent);
            qof_begin_edit((QofInstance*)reference);
            reference_setter(ent, reference);
            qof_commit_edit((QofInstance*)ent);
            qof_commit_edit((QofInstance*)reference);
        }
        /* collect and choice handling */
        collect_setter = (void(*)(QofInstance*, QofCollection*))ref->param->param_setfcn;
        choice_setter = (void(*)(QofInstance*, QofInstance*))ref->param->param_setfcn;
        if ((0 == g_strcmp0(ref->param->param_type, QOF_TYPE_COLLECT)) &&
                (0 == guid_compare(qof_instance_get_guid(ent), ref->ent_guid)) &&
                (0 == g_strcmp0(ref->type, ent->e_type)))
        {
            QofCollection *temp_col;
            char cm_sa[GUID_ENCODING_LENGTH + 1];

            temp_col = ref->param->param_getfcn(ent, ref->param);
            coll = qof_book_get_collection(partial_book,
                                           qof_collection_get_type(temp_col));
            guid_to_string_buff(ref->ref_guid, cm_sa);
            reference = qof_collection_lookup_entity(coll, ref->ref_guid);
            if (reference)
            {
                qof_collection_add_entity(temp_col, reference);
                qof_begin_edit((QofInstance*)ent);
                qof_begin_edit((QofInstance*)reference);
                if (collect_setter)
                {
                    collect_setter(ent, temp_col);
                }
                qof_commit_edit((QofInstance*)ent);
                qof_commit_edit((QofInstance*)reference);
                qof_collection_destroy(temp_col);
            }
        }
        if (0 == g_strcmp0(ref->param->param_type, QOF_TYPE_CHOICE))
        {
            coll = qof_book_get_collection(partial_book, ref->type);
            reference = qof_collection_lookup_entity(coll, ref->ref_guid);
            qof_begin_edit((QofInstance*)ent);
            qof_begin_edit((QofInstance*)reference);
            if (choice_setter)
            {
                choice_setter(ent, reference);
            }
            qof_commit_edit((QofInstance*)ent);
            qof_commit_edit((QofInstance*)reference);
        }
        book_ref_list = g_list_next(book_ref_list);
    }
}

static void
set_each_type(QofObject *obj, gpointer user_data)
{
    QofBook *book;

    book = (QofBook*)user_data;
    qof_object_foreach(obj->e_type, book, entity_set_reference_cb, book);
}

static QofInstanceReference*
create_reference(QofInstance *ent, const QofParam *param)
{
    QofInstanceReference *reference;
    QofInstance          *ref_ent;
    const GncGUID         *cm_guid;
    char                cm_sa[GUID_ENCODING_LENGTH + 1];
    gchar              *cm_string;

    g_return_val_if_fail(ent, NULL);
    ref_ent = QOF_INSTANCE(param->param_getfcn(ent, param));
    if (!ref_ent)
    {
        return NULL;
    }
    reference = g_new0(QofInstanceReference, 1);
    reference->type = ent->e_type;
    reference->ref_guid = g_new(GncGUID, 1);
    reference->ent_guid = qof_instance_get_guid(ent);
    if (qof_object_is_choice(ent->e_type))
    {
        reference->choice_type = ref_ent->e_type;
    }
    reference->param = param;
    cm_guid = qof_instance_get_guid(ref_ent);
    guid_to_string_buff(cm_guid, cm_sa);
    cm_string = g_strdup(cm_sa);
    if (TRUE == string_to_guid(cm_string, reference->ref_guid))
    {
        g_free(cm_string);
        return reference;
    }
    g_free(cm_string);
    return NULL;
}

QofInstanceReference*
qof_instance_get_reference_from(QofInstance *ent, const QofParam *param)
{
    g_return_val_if_fail(param, NULL);
    param = qof_class_get_parameter(ent->e_type, param->param_name);
    g_return_val_if_fail(0 != g_strcmp0(param->param_type, QOF_TYPE_COLLECT), NULL);
    return create_reference(ent, param);
}

void qof_book_set_references(QofBook *book)
{
    gboolean partial;

    partial =
        (gboolean)GPOINTER_TO_INT(qof_book_get_data(book, PARTIAL_QOFBOOK));
    g_return_if_fail(partial);
    qof_object_foreach_type(set_each_type, book);
}
