/********************************************************************\
 * qofinstance.c -- handler for fields common to all objects        *
 *                                                                  *
 * 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                   *
 *                                                                  *
\********************************************************************/

/*
 * Object instance holds many common fields that most
 * gnucash objects use.
 *
 * Copyright (C) 2003 Linas Vepstas <linas@linas.org>
 * Copyright (c) 2007 David Hampton <hampton@employees.org>
 * Copyright 2017 Aaron Laws <dartme18@gmail.com>
 */

#include "guid.hpp"
extern "C"
{
#include <config.h>
#include <glib.h>
}

#include <utility>
#include "qof.h"
#include "qofbook-p.h"
#include "qofid-p.h"
#include "kvp-frame.hpp"
#include "qofinstance-p.h"
#include "qof-backend.hpp"

static QofLogModule log_module = QOF_MOD_ENGINE;

/* ========================================================== */

enum
{
    LAST_SIGNAL
};

enum
{
    PROP_0,
    PROP_TYPE,
    PROP_GUID,
    PROP_COLLECTION,
    PROP_BOOK,
    PROP_LAST_UPDATE,
    PROP_EDITLEVEL,
    PROP_DESTROYING,
    PROP_DIRTY,
    PROP_INFANT,

    PROP_VERSION,
    PROP_VERSION_CHECK,
    PROP_IDATA,
};

typedef struct QofInstancePrivate
{
//    QofIdType        e_type;    /**<	Entity type */
    GncGUID guid;                  /**< GncGUID for the entity */
    QofCollection  *collection; /**< Entity collection */

    /* The entity_table in which this instance is stored */
    QofBook * book;

    /*  Timestamp used to track the last modification to this
     *  instance.  Typically used to compare two versions of the
     *  same object, to see which is newer.  When used with the
     *  SQL backend, this field is reserved for SQL use, to compare
     *  the version in local memory to the remote, server version.
     */
    Timespec last_update;

    /*  Keep track of nesting level of begin/end edit calls */
    int editlevel;

    /*  In process of being destroyed */
    gboolean do_free;

    /*  dirty/clean flag. If dirty, then this instance has been modified,
     *  but has not yet been written out to storage (file/database)
     */
    gboolean dirty;

    /* True iff this instance has never been committed. */
    gboolean infant;

    /* version number, used for tracking multiuser updates */
    gint32 version;
    guint32 version_check;  /* data aging timestamp */

    /* -------------------------------------------------------------- */
    /* Backend private expansion data */
    guint32  idata;   /* used by the sql backend for kvp management */
}  QofInstancePrivate;

#define GET_PRIVATE(o)  \
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), QOF_TYPE_INSTANCE,  QofInstancePrivate))

QOF_GOBJECT_GET_TYPE(QofInstance, qof_instance, G_TYPE_OBJECT, {});
QOF_GOBJECT_FINALIZE(qof_instance);
#undef G_PARAM_READWRITE
#define G_PARAM_READWRITE static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_WRITABLE)

static void qof_instance_get_property (GObject         *object,
                                       guint            prop_id,
                                       GValue          *value,
                                       GParamSpec      *pspec);
static void qof_instance_set_property (GObject         *object,
                                       guint            prop_id,
                                       const GValue    *value,
                                       GParamSpec      *pspec);
static void qof_instance_dispose(GObject*);
static void qof_instance_class_init(QofInstanceClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);
    object_class->finalize = qof_instance_finalize;
    object_class->dispose = qof_instance_dispose;
    object_class->set_property = qof_instance_set_property;
    object_class->get_property = qof_instance_get_property;

    g_type_class_add_private(klass, sizeof(QofInstancePrivate));

    klass->get_display_name = NULL;
    klass->refers_to_object = NULL;
    klass->get_typed_referring_object_list = NULL;

    g_object_class_install_property
    (object_class,
     PROP_GUID,
     g_param_spec_boxed ("guid",
                         "Object GncGUID",
                         "The object Globally Unique ID.",
                         GNC_TYPE_GUID,
                         G_PARAM_READWRITE));

    g_object_class_install_property
    (object_class,
     PROP_COLLECTION,
     g_param_spec_pointer ("collection",
                           "Object Collection",
                           "A collection of like objects of which this "
                           "particular object is amember.  E.g.. A "
                           "collection of accounts, or a collection of "
                           "splits.",
                           G_PARAM_READWRITE));

    g_object_class_install_property
    (object_class,
     PROP_BOOK,
     g_param_spec_object ("book",
                          "Object Book",
                          "The book that contains this object.",
                          QOF_TYPE_BOOK,
                          G_PARAM_READWRITE));

    g_object_class_install_property
    (object_class,
     PROP_LAST_UPDATE,
     g_param_spec_pointer ("last-update",
                           "Object Last Update",
                           "A pointer to the last time this object was "
                           "updated.  This value is present for use by "
                           "backends and shouldnot be written by other "
                           "code.",
                           G_PARAM_READWRITE));

    g_object_class_install_property
    (object_class,
     PROP_EDITLEVEL,
     g_param_spec_int ("editlevel",
                       "Object Edit Level",
                       "The object edit level.",
                       0, G_MAXINT32, 0,
                       G_PARAM_READABLE));

    g_object_class_install_property
    (object_class,
     PROP_DESTROYING,
     g_param_spec_boolean ("destroying",
                           "Object Destroying",
                           "This flag is set to TRUE if the object is "
                           "about to be destroyed.",
                           FALSE,
                           G_PARAM_READWRITE));

    g_object_class_install_property
    (object_class,
     PROP_DIRTY,
     g_param_spec_boolean ("dirty",
                           "Object Dirty",
                           "This flag is set to TRUE if the object has "
                           "unsaved changes.",
                           FALSE,
                           G_PARAM_READWRITE));

    g_object_class_install_property
    (object_class,
     PROP_INFANT,
     g_param_spec_boolean ("infant",
                           "Object Infant",
                           "This flag is set to TRUE if the object has "
                           "never been added to a book.  This implies "
                           "that its destruction does not affect the "
                           "state of the book, and therefore the saved "
                           "state of the data file.",
                           FALSE,
                           G_PARAM_READABLE));

    g_object_class_install_property
    (object_class,
     PROP_VERSION,
     g_param_spec_int ("version",
                       "Version",
                       "The version number of the current instance state.",
                       0,
                       G_MAXINT32,
                       0,
                       G_PARAM_READWRITE));

    g_object_class_install_property
    (object_class,
     PROP_VERSION_CHECK,
     g_param_spec_uint ("version-check",
                        "Version Check",
                        "The version check number of the current instance state.",
                        0,
                        G_MAXUINT32,
                        0,
                        G_PARAM_READWRITE));

    g_object_class_install_property
    (object_class,
     PROP_EDITLEVEL,
     g_param_spec_uint ("idata",
                        "Object IData",
                        "Per instance backend private data.",
                        0, G_MAXUINT32, 0,
                        G_PARAM_READWRITE));
}

static void
qof_instance_init (QofInstance *inst)
{
    QofInstancePrivate *priv;

    priv = GET_PRIVATE(inst);
    priv->book = NULL;
    inst->kvp_data = new KvpFrame;
    priv->last_update.tv_sec = 0;
    priv->last_update.tv_nsec = -1;
    priv->editlevel = 0;
    priv->do_free = FALSE;
    priv->dirty = FALSE;
    priv->infant = TRUE;
}

void
qof_instance_init_data (QofInstance *inst, QofIdType type, QofBook *book)
{
    QofInstancePrivate *priv;
    QofCollection *col;
    QofIdType col_type;

    g_return_if_fail(QOF_IS_INSTANCE(inst));
    priv = GET_PRIVATE(inst);
    g_return_if_fail(!priv->book);

    priv->book = book;
    col = qof_book_get_collection (book, type);
    g_return_if_fail(col != NULL);

    /* XXX We passed redundant info to this routine ... but I think that's
     * OK, it might eliminate programming errors. */

    col_type = qof_collection_get_type(col);
    if (g_strcmp0(col_type, type))
    {
        PERR ("attempt to insert \"%s\" into \"%s\"", type, col_type);
        return;
    }
    priv = GET_PRIVATE(inst);
    inst->e_type = static_cast<QofIdType>(CACHE_INSERT (type));

    do
    {
        guid_replace(&priv->guid);

        if (NULL == qof_collection_lookup_entity (col, &priv->guid))
            break;

        PWARN("duplicate id created, trying again");
    }
    while (1);

    priv->collection = col;

    qof_collection_insert_entity (col, inst);
}

static void
qof_instance_dispose (GObject *instp)
{
    QofInstancePrivate *priv;
    QofInstance* inst = QOF_INSTANCE(instp);

    priv = GET_PRIVATE(instp);
    if (!priv->collection)
        return;
    qof_collection_remove_entity(inst);

    CACHE_REMOVE(inst->e_type);
    inst->e_type = NULL;

    G_OBJECT_CLASS(qof_instance_parent_class)->dispose(instp);
}

static void
qof_instance_finalize_real (GObject *instp)
{
    QofInstancePrivate *priv;
    QofInstance* inst = QOF_INSTANCE(instp);

    delete inst->kvp_data;
    inst->kvp_data = nullptr;

    priv = GET_PRIVATE(inst);
    priv->editlevel = 0;
    priv->do_free = FALSE;
    priv->dirty = FALSE;
}

/* Note that g_value_set_object() refs the object, as does
 * g_object_get(). But g_object_get() only unrefs once when it disgorges
 * the object, leaving an unbalanced ref, which leaks. So instead of
 * using g_value_set_object(), use g_value_take_object() which doesn't
 * ref the object when used in get_property().
 */
static void
qof_instance_get_property (GObject         *object,
                           guint            prop_id,
                           GValue          *value,
                           GParamSpec      *pspec)
{
    QofInstance *inst;
    QofInstancePrivate *priv;

    g_return_if_fail(QOF_IS_INSTANCE(object));

    inst = QOF_INSTANCE(object);
    priv = GET_PRIVATE(inst);

    switch (prop_id)
    {
    case PROP_GUID:
        g_value_set_boxed(value, &priv->guid);
        break;
    case PROP_COLLECTION:
        g_value_set_pointer(value, priv->collection);
        break;
    case PROP_BOOK:
        g_value_take_object(value, priv->book);
        break;
    case PROP_LAST_UPDATE:
        g_value_set_pointer(value, &priv->last_update);
        break;
    case PROP_EDITLEVEL:
        g_value_set_int(value, priv->editlevel);
        break;
    case PROP_DESTROYING:
        g_value_set_boolean(value, priv->do_free);
        break;
    case PROP_DIRTY:
        g_value_set_boolean(value, qof_instance_get_dirty(inst));
        break;
    case PROP_INFANT:
        g_value_set_boolean(value, priv->infant);
        break;
    case PROP_VERSION:
        g_value_set_int(value, priv->version);
        break;
    case PROP_VERSION_CHECK:
        g_value_set_uint(value, priv->version_check);
        break;
    case PROP_IDATA:
        g_value_set_uint(value, priv->idata);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

static void
qof_instance_set_property (GObject         *object,
                           guint            prop_id,
                           const GValue    *value,
                           GParamSpec      *pspec)
{
    QofInstance *inst;
    Timespec *ts;

    g_return_if_fail(QOF_IS_INSTANCE(object));

    inst = QOF_INSTANCE(object);

    switch (prop_id)
    {
    case PROP_GUID:
        qof_instance_set_guid(inst,
			      static_cast<GncGUID*>(g_value_get_boxed(value)));
        break;
    case PROP_COLLECTION:
        qof_instance_set_collection(inst, static_cast<QofCollection*>(g_value_get_pointer(value)));
        break;
    case PROP_BOOK:
        qof_instance_set_book(inst,
			      static_cast<QofBook*>(g_value_get_object(value)));
        break;
    case PROP_LAST_UPDATE:
        ts = static_cast<Timespec*>(g_value_get_pointer(value));
        qof_instance_set_last_update(inst, *ts);
        break;
    case PROP_DESTROYING:
        qof_instance_set_destroying(inst, g_value_get_boolean(value));
        break;
    case PROP_DIRTY:
        qof_instance_set_dirty(inst);
        break;
    case PROP_VERSION:
        qof_instance_set_version(inst, g_value_get_int(value));
        break;
    case PROP_VERSION_CHECK:
        qof_instance_set_version_check(inst, g_value_get_uint(value));
        break;
    case PROP_IDATA:
        qof_instance_set_idata(inst, g_value_get_uint(value));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

const GncGUID *
qof_instance_get_guid (gconstpointer inst)
{
    QofInstancePrivate *priv;

    if (!inst) return NULL;
    g_return_val_if_fail(QOF_IS_INSTANCE(inst), guid_null());
    priv = GET_PRIVATE(inst);
    return &(priv->guid);
}

const GncGUID *
qof_entity_get_guid (gconstpointer ent)
{
    return ent ? qof_instance_get_guid(ent) : guid_null();
}

void
qof_instance_set_guid (gpointer ptr, const GncGUID *guid)
{
    QofInstancePrivate *priv;
    QofInstance *inst;
    QofCollection *col;

    g_return_if_fail(QOF_IS_INSTANCE(ptr));

    inst = QOF_INSTANCE(ptr);
    priv = GET_PRIVATE(inst);
    if (guid_equal (guid, &priv->guid))
        return;

    col = priv->collection;
    qof_collection_remove_entity(inst);
    priv->guid = *guid;
    qof_collection_insert_entity(col, inst);
}

void
qof_instance_copy_guid (gpointer to, gconstpointer from)
{
    g_return_if_fail(QOF_IS_INSTANCE(to));
    g_return_if_fail(QOF_IS_INSTANCE(from));

    GET_PRIVATE(to)->guid = GET_PRIVATE(from)->guid;
}

gint
qof_instance_guid_compare(gconstpointer ptr1, gconstpointer ptr2)
{
    const QofInstancePrivate *priv1, *priv2;

    g_return_val_if_fail(QOF_IS_INSTANCE(ptr1), -1);
    g_return_val_if_fail(QOF_IS_INSTANCE(ptr2),  1);

    priv1 = GET_PRIVATE(ptr1);
    priv2 = GET_PRIVATE(ptr2);

    return guid_compare(&priv1->guid, &priv2->guid);
}

QofCollection *
qof_instance_get_collection (gconstpointer ptr)
{

    g_return_val_if_fail(QOF_IS_INSTANCE(ptr), NULL);
    return GET_PRIVATE(ptr)->collection;
}

void
qof_instance_set_collection (gconstpointer ptr, QofCollection *col)
{
    g_return_if_fail(QOF_IS_INSTANCE(ptr));
    GET_PRIVATE(ptr)->collection = col;
}

QofBook *
qof_instance_get_book (gconstpointer inst)
{
    if (!inst) return NULL;
    g_return_val_if_fail(QOF_IS_INSTANCE(inst), NULL);
    return GET_PRIVATE(inst)->book;
}

void
qof_instance_set_book (gconstpointer inst, QofBook *book)
{
    g_return_if_fail(QOF_IS_INSTANCE(inst));
    GET_PRIVATE(inst)->book = book;
}

void
qof_instance_copy_book (gpointer ptr1, gconstpointer ptr2)
{
    g_return_if_fail(QOF_IS_INSTANCE(ptr1));
    g_return_if_fail(QOF_IS_INSTANCE(ptr2));

    GET_PRIVATE(ptr1)->book = GET_PRIVATE(ptr2)->book;
}

gboolean
qof_instance_books_equal (gconstpointer ptr1, gconstpointer ptr2)
{
    const QofInstancePrivate *priv1, *priv2;

    g_return_val_if_fail(QOF_IS_INSTANCE(ptr1), FALSE);
    g_return_val_if_fail(QOF_IS_INSTANCE(ptr2), FALSE);

    priv1 = GET_PRIVATE(ptr1);
    priv2 = GET_PRIVATE(ptr2);

    return (priv1->book == priv2->book);
}

/* Watch out: This function is still used (as a "friend") in src/import-export/aqb/gnc-ab-kvp.c */
KvpFrame*
qof_instance_get_slots (const QofInstance *inst)
{
    if (!inst) return NULL;
    return inst->kvp_data;
}

void
qof_instance_set_slots (QofInstance *inst, KvpFrame *frm)
{
    QofInstancePrivate *priv;

    if (!inst) return;

    priv = GET_PRIVATE(inst);
    if (inst->kvp_data && (inst->kvp_data != frm))
    {
        delete inst->kvp_data;
    }

    priv->dirty = TRUE;
    inst->kvp_data = frm;
}

void
qof_instance_set_last_update (QofInstance *inst, Timespec ts)
{
    if (!inst) return;
    GET_PRIVATE(inst)->last_update = ts;
}

gint
qof_instance_get_editlevel (gconstpointer ptr)
{
    g_return_val_if_fail(QOF_IS_INSTANCE(ptr), 0);
    return GET_PRIVATE(ptr)->editlevel;
}

void qof_instance_increase_editlevel (gpointer ptr)
{
    g_return_if_fail(QOF_IS_INSTANCE(ptr));
    GET_PRIVATE(ptr)->editlevel++;
}

void qof_instance_decrease_editlevel (gpointer ptr)
{
    g_return_if_fail(QOF_IS_INSTANCE(ptr));
    GET_PRIVATE(ptr)->editlevel--;
}

void qof_instance_reset_editlevel (gpointer ptr)
{
    g_return_if_fail(QOF_IS_INSTANCE(ptr));
    GET_PRIVATE(ptr)->editlevel = 0;
}

int
qof_instance_version_cmp (const QofInstance *left, const QofInstance *right)
{
    QofInstancePrivate *lpriv, *rpriv;

    if (!left && !right) return 0;
    if (!left) return -1;
    if (!right) return +1;

    lpriv = GET_PRIVATE(left);
    rpriv = GET_PRIVATE(right);
    if (lpriv->last_update.tv_sec  < rpriv->last_update.tv_sec) return -1;
    if (lpriv->last_update.tv_sec  > rpriv->last_update.tv_sec) return +1;
    if (lpriv->last_update.tv_nsec < rpriv->last_update.tv_nsec) return -1;
    if (lpriv->last_update.tv_nsec > rpriv->last_update.tv_nsec) return +1;
    return 0;
}

gboolean
qof_instance_get_destroying (gconstpointer ptr)
{
    g_return_val_if_fail(QOF_IS_INSTANCE(ptr), FALSE);
    return GET_PRIVATE(ptr)->do_free;
}

void
qof_instance_set_destroying (gpointer ptr, gboolean value)
{
    g_return_if_fail(QOF_IS_INSTANCE(ptr));
    GET_PRIVATE(ptr)->do_free = value;
}

gboolean
qof_instance_get_dirty_flag (gconstpointer ptr)
{
    g_return_val_if_fail(QOF_IS_INSTANCE(ptr), FALSE);
    return GET_PRIVATE(ptr)->dirty;
}

void
qof_instance_set_dirty_flag (gconstpointer inst, gboolean flag)
{
    g_return_if_fail(QOF_IS_INSTANCE(inst));
    GET_PRIVATE(inst)->dirty = flag;
}

void
qof_instance_mark_clean (QofInstance *inst)
{
    if (!inst) return;
    GET_PRIVATE(inst)->dirty = FALSE;
}

void
qof_instance_print_dirty (const QofInstance *inst, gpointer dummy)
{
    QofInstancePrivate *priv;

    priv = GET_PRIVATE(inst);
    if (priv->dirty)
    {
        gchar guidstr[GUID_ENCODING_LENGTH+1];
        guid_to_string_buff(&priv->guid, guidstr);
        printf("%s instance %s is dirty.\n", inst->e_type, guidstr);
    }
}

gboolean
qof_instance_get_dirty (QofInstance *inst)
{
    QofInstancePrivate *priv;
    QofCollection *coll;

    if (!inst)
    {
        return FALSE;
    }

    priv = GET_PRIVATE(inst);
    return priv->dirty;
}

void
qof_instance_set_dirty(QofInstance* inst)
{
    QofInstancePrivate *priv;
    QofCollection *coll;

    priv = GET_PRIVATE(inst);
    priv->dirty = TRUE;
}

gboolean
qof_instance_get_infant(const QofInstance *inst)
{
    g_return_val_if_fail(QOF_IS_INSTANCE(inst), FALSE);
    return GET_PRIVATE(inst)->infant;
}

gint32
qof_instance_get_version (gconstpointer inst)
{
    g_return_val_if_fail(QOF_IS_INSTANCE(inst), 0);
    return GET_PRIVATE(inst)->version;
}

void
qof_instance_set_version (gpointer inst, gint32 vers)
{
    g_return_if_fail(QOF_IS_INSTANCE(inst));
    GET_PRIVATE(inst)->version = vers;
}

void
qof_instance_copy_version (gpointer to, gconstpointer from)
{
    g_return_if_fail(QOF_IS_INSTANCE(to));
    g_return_if_fail(QOF_IS_INSTANCE(from));
    GET_PRIVATE(to)->version = GET_PRIVATE(from)->version;
}

guint32
qof_instance_get_version_check (gconstpointer inst)
{
    g_return_val_if_fail(QOF_IS_INSTANCE(inst), 0);
    return GET_PRIVATE(inst)->version_check;
}

void
qof_instance_set_version_check (gpointer inst, guint32 value)
{
    g_return_if_fail(QOF_IS_INSTANCE(inst));
    GET_PRIVATE(inst)->version_check = value;
}

void
qof_instance_copy_version_check (gpointer to, gconstpointer from)
{
    g_return_if_fail(QOF_IS_INSTANCE(to));
    g_return_if_fail(QOF_IS_INSTANCE(from));
    GET_PRIVATE(to)->version_check = GET_PRIVATE(from)->version_check;
}

guint32 qof_instance_get_idata (gconstpointer inst)
{
    if (!inst)
    {
        return 0;
    }
    g_return_val_if_fail(QOF_IS_INSTANCE(inst), 0);
    return GET_PRIVATE(inst)->idata;
}

void qof_instance_set_idata(gpointer inst, guint32 idata)
{
    if (!inst)
    {
        return;
    }
    g_return_if_fail(QOF_IS_INSTANCE(inst));
    GET_PRIVATE(inst)->idata = idata;
}

/* ========================================================== */

/* Returns a displayable name to represent this object */
gchar* qof_instance_get_display_name(const QofInstance* inst)
{
    g_return_val_if_fail( inst != NULL, NULL );

    if ( QOF_INSTANCE_GET_CLASS(inst)->get_display_name != NULL )
    {
        return QOF_INSTANCE_GET_CLASS(inst)->get_display_name(inst);
    }
    else
    {
        /* Not implemented - return default string */
        return g_strdup_printf("Object %s %p",
                               qof_collection_get_type(qof_instance_get_collection(inst)),
                               inst);
    }
}

typedef struct
{
    const QofInstance* inst;
    GList* list;
} GetReferringObjectHelperData;

static void
get_referring_object_instance_helper(QofInstance* inst, gpointer user_data)
{
    QofInstance** pInst = (QofInstance**)user_data;

    if (*pInst == NULL)
    {
        *pInst = inst;
    }
}

static void
get_referring_object_helper(QofCollection* coll, gpointer user_data)
{
    QofInstance* first_instance = NULL;
    GetReferringObjectHelperData* data = (GetReferringObjectHelperData*)user_data;

    qof_collection_foreach(coll, get_referring_object_instance_helper, &first_instance);

    if (first_instance != NULL)
    {
        GList* new_list = qof_instance_get_typed_referring_object_list(first_instance, data->inst);
        data->list = g_list_concat(data->list, new_list);
    }
}

/* Returns a list of objects referring to this object */
GList* qof_instance_get_referring_object_list(const QofInstance* inst)
{
    GetReferringObjectHelperData data;

    g_return_val_if_fail( inst != NULL, NULL );

    /* scan all collections */
    data.inst = inst;
    data.list = NULL;

    qof_book_foreach_collection(qof_instance_get_book(inst),
                                get_referring_object_helper,
                                &data);
    return data.list;
}

static void
get_typed_referring_object_instance_helper(QofInstance* inst, gpointer user_data)
{
    GetReferringObjectHelperData* data = (GetReferringObjectHelperData*)user_data;

    if (qof_instance_refers_to_object(inst, data->inst))
    {
        data->list = g_list_prepend(data->list, inst);
    }
}

GList*
qof_instance_get_referring_object_list_from_collection(const QofCollection* coll, const QofInstance* ref)
{
    GetReferringObjectHelperData data;

    g_return_val_if_fail( coll != NULL, NULL );
    g_return_val_if_fail( ref != NULL, NULL );

    data.inst = ref;
    data.list = NULL;

    qof_collection_foreach(coll, get_typed_referring_object_instance_helper, &data);
    return data.list;
}

GList*
qof_instance_get_typed_referring_object_list(const QofInstance* inst, const QofInstance* ref)
{
    g_return_val_if_fail( inst != NULL, NULL );
    g_return_val_if_fail( ref != NULL, NULL );

    if ( QOF_INSTANCE_GET_CLASS(inst)->get_typed_referring_object_list != NULL )
    {
        return QOF_INSTANCE_GET_CLASS(inst)->get_typed_referring_object_list(inst, ref);
    }
    else
    {
        /* Not implemented - by default, loop through all objects of this object's type and check
           them individually. */
        QofCollection* coll;

        coll = qof_instance_get_collection(inst);
        return qof_instance_get_referring_object_list_from_collection(coll, ref);
    }
}

/* Check if this object refers to a specific object */
gboolean qof_instance_refers_to_object(const QofInstance* inst, const QofInstance* ref)
{
    g_return_val_if_fail( inst != NULL, FALSE );
    g_return_val_if_fail( ref != NULL, FALSE );

    if ( QOF_INSTANCE_GET_CLASS(inst)->refers_to_object != NULL )
    {
        return QOF_INSTANCE_GET_CLASS(inst)->refers_to_object(inst, ref);
    }
    else
    {
        /* Not implemented - default = NO */
        return FALSE;
    }
}

/* g_object_set/get wrappers */
void
qof_instance_get (const QofInstance *inst, const gchar *first_prop, ...)
{
    va_list ap;
    g_return_if_fail (QOF_IS_INSTANCE (inst));

    va_start (ap, first_prop);
    g_object_get_valist (G_OBJECT (inst), first_prop, ap);
    va_end (ap);
}

void
qof_instance_set (QofInstance *inst, const gchar *first_prop, ...)
{
    va_list ap;
    QofInstancePrivate *priv = GET_PRIVATE(inst);
    g_return_if_fail (QOF_IS_INSTANCE (inst));

    qof_instance_set_dirty (inst);
    va_start (ap, first_prop);
    g_object_set_valist (G_OBJECT (inst), first_prop, ap);
    va_end (ap);
}


/* =================================================================== */
/* Entity edit and commit utilities */
/* =================================================================== */

gboolean
qof_begin_edit (QofInstance *inst)
{
    QofInstancePrivate *priv;

    if (!inst) return FALSE;

    priv = GET_PRIVATE(inst);
    priv->editlevel++;
    if (1 < priv->editlevel) return FALSE;
    if (0 >= priv->editlevel)
        priv->editlevel = 1;

    auto be = qof_book_get_backend(priv->book);
    if (be)
        be->begin(inst);
    else
        priv->dirty = TRUE;

    return TRUE;
}

gboolean qof_commit_edit (QofInstance *inst)
{
    QofInstancePrivate *priv;

    if (!inst) return FALSE;

    priv = GET_PRIVATE(inst);
    priv->editlevel--;
    if (0 < priv->editlevel) return FALSE;

    if (0 > priv->editlevel)
    {
        PERR ("unbalanced call - resetting (was %d)", priv->editlevel);
        priv->editlevel = 0;
    }
    return TRUE;
}

gboolean
qof_commit_edit_part2(QofInstance *inst,
                      void (*on_error)(QofInstance *, QofBackendError),
                      void (*on_done)(QofInstance *),
                      void (*on_free)(QofInstance *))
{
    QofInstancePrivate *priv;

    priv = GET_PRIVATE(inst);

    if (priv->dirty &&
        !(priv->infant && priv->do_free)) {
      qof_collection_mark_dirty(priv->collection);
      qof_book_mark_session_dirty(priv->book);
    }

    /* See if there's a backend.  If there is, invoke it. */
    auto be = qof_book_get_backend(priv->book);
    if (be)
    {
        QofBackendError errcode;

        /* clear errors */
        do
        {
            errcode = be->get_error();
        }
        while (errcode != ERR_BACKEND_NO_ERR);

        be->commit(inst);
        errcode = be->get_error();
        if (errcode != ERR_BACKEND_NO_ERR)
        {
            /* XXX Should perform a rollback here */
            priv->do_free = FALSE;

            /* Push error back onto the stack */
            be->set_error (errcode);
            if (on_error)
                on_error(inst, errcode);
            return FALSE;
        }
        /* XXX the backend commit code should clear dirty!! */
        priv->dirty = FALSE;
    }
    priv->infant = FALSE;

    if (priv->do_free)
    {
        if (on_free)
            on_free(inst);
        return TRUE;
    }

    if (on_done)
        on_done(inst);
    return TRUE;
}

gboolean
qof_instance_has_kvp (QofInstance *inst)
{
    return (inst->kvp_data != NULL && !inst->kvp_data->empty());
}

void qof_instance_set_path_kvp (QofInstance * inst, GValue const * value, std::vector<std::string> const & path)
{
    delete inst->kvp_data->set_path (path, kvp_value_from_gvalue (value));
}

void
qof_instance_set_kvp (QofInstance * inst, GValue const * value, unsigned count, ...)
{
    std::vector<std::string> path;
    va_list args;
    va_start (args, count);
    for (unsigned i{0}; i < count; ++i)
        path.push_back (va_arg (args, char const *));
    va_end (args);
    delete inst->kvp_data->set_path (path, kvp_value_from_gvalue (value));
}

void qof_instance_get_path_kvp (QofInstance * inst, GValue * value, std::vector<std::string> const & path)
{
    auto temp = gvalue_from_kvp_value (inst->kvp_data->get_slot (path));
    if (G_IS_VALUE (temp))
    {
        if (G_IS_VALUE (value))
            g_value_unset (value);
        g_value_init (value, G_VALUE_TYPE (temp));
        g_value_copy (temp, value);
        gnc_gvalue_free (temp);
    }
}

void
qof_instance_get_kvp (QofInstance * inst, GValue * value, unsigned count, ...)
{
    std::vector<std::string> path;
    va_list args;
    va_start (args, count);
    for (unsigned i{0}; i < count; ++i)
        path.push_back (va_arg (args, char const *));
    va_end (args);
    auto temp = gvalue_from_kvp_value (inst->kvp_data->get_slot (path));
    if (G_IS_VALUE (temp))
    {
        if (G_IS_VALUE (value))
            g_value_unset (value);
        g_value_init (value, G_VALUE_TYPE (temp));
        g_value_copy (temp, value);
        gnc_gvalue_free (temp);
    }
}

void
qof_instance_copy_kvp (QofInstance *to, const QofInstance *from)
{
    delete to->kvp_data;
    to->kvp_data = new KvpFrame(*from->kvp_data);
}

void
qof_instance_swap_kvp (QofInstance *a, QofInstance *b)
{
    std::swap(a->kvp_data, b->kvp_data);
}

int
qof_instance_compare_kvp (const QofInstance *a, const QofInstance *b)
{
    return compare(a->kvp_data, b->kvp_data);
}

char*
qof_instance_kvp_as_string (const QofInstance *inst)
{
    //The std::string is a local temporary and doesn't survive this function.
    return g_strdup(inst->kvp_data->to_string().c_str());
}

void
qof_instance_kvp_add_guid (const QofInstance *inst, const char* path,
                           const Timespec time, const char *key,
                           const GncGUID *guid)
{
    g_return_if_fail (inst->kvp_data != NULL);

    auto container = new KvpFrame;
    container->set({key}, new KvpValue(const_cast<GncGUID*>(guid)));
    container->set({"date"}, new KvpValue(time));
    delete inst->kvp_data->set_path({path}, new KvpValue(container));
}

inline static gboolean
kvp_match_guid (KvpValue *v, std::vector<std::string> const & path, const GncGUID *guid)
{
    if (v->get_type() != KvpValue::Type::FRAME)
        return FALSE;
    auto frame = v->get<KvpFrame*>();
    auto val = frame->get_slot(path);
    if (val == nullptr || val->get_type() != KvpValue::Type::GUID)
        return FALSE;
    auto this_guid = val->get<GncGUID*>();

    return guid_equal (this_guid, guid);
}

gboolean
qof_instance_kvp_has_guid (const QofInstance *inst, const char *path,
                           const char* key, const GncGUID *guid)
{
    g_return_val_if_fail (inst->kvp_data != NULL, FALSE);
    g_return_val_if_fail (guid != NULL, FALSE);

    auto v = inst->kvp_data->get_slot({path});
    if (v == nullptr) return FALSE;

    switch (v->get_type())
    {
    case KvpValue::Type::FRAME:
        return kvp_match_guid (v, {key}, guid);
        break;
    case KvpValue::Type::GLIST:
    {
        auto list = v->get<GList*>();
        for (auto node = list; node != NULL; node = node->next)
        {
            auto val = static_cast<KvpValue*>(node->data);
            if (kvp_match_guid (val, {key}, guid))
            {
                return TRUE;
            }
        }
        break;
    }
    default:
        PWARN ("Instance KVP on path %s contains the wrong type.", path);
        break;
    }
    return FALSE;
}

void
qof_instance_kvp_remove_guid (const QofInstance *inst, const char *path,
                          const char *key, const GncGUID *guid)
{
    g_return_if_fail (inst->kvp_data != NULL);
    g_return_if_fail (guid != NULL);

    auto v = inst->kvp_data->get_slot({path});
    if (v == NULL) return;

    switch (v->get_type())
    {
    case KvpValue::Type::FRAME:
        if (kvp_match_guid (v, {key}, guid))
        {
            delete inst->kvp_data->set_path({path}, nullptr);
            delete v;
        }
        break;
    case KvpValue::Type::GLIST:
    {
        auto list = v->get<GList*>();
        for (auto node = list; node != nullptr; node = node->next)
        {
            auto val = static_cast<KvpValue*>(node->data);
            if (kvp_match_guid (val, {key}, guid))
            {
                list = g_list_delete_link (list, node);
                v->set(list);
                delete val;
                break;
            }
        }
        break;
    }
    default:
        PWARN ("Instance KVP on path %s contains the wrong type.", path);
        break;
    }
    return;
}

void
qof_instance_kvp_merge_guids (const QofInstance *target,
                              const QofInstance *donor, const char *path)
{
    g_return_if_fail (target != NULL);
    g_return_if_fail (donor != NULL);

    if (! qof_instance_has_slot (donor, path)) return;
    auto v = donor->kvp_data->get_slot({path});
    if (v == NULL) return;

    auto target_val = target->kvp_data->get_slot({path});
    switch (v->get_type())
    {
    case KvpValue::Type::FRAME:
        if (target_val)
            target_val->add(v);
        else
            target->kvp_data->set_path({path}, v);
        donor->kvp_data->set({path}, nullptr); //Contents moved, Don't delete!
        break;
    case KvpValue::Type::GLIST:
        if (target_val)
        {
            auto list = target_val->get<GList*>();
            list = g_list_concat(list, v->get<GList*>());
            target_val->set(list);
        }
        else
            target->kvp_data->set({path}, v);
        donor->kvp_data->set({path}, nullptr); //Contents moved, Don't delete!
        break;
    default:
        PWARN ("Instance KVP on path %s contains the wrong type.", path);
        break;
    }
}

bool qof_instance_has_path_slot (QofInstance const * inst, std::vector<std::string> const & path)
{
    return inst->kvp_data->get_slot (path) != nullptr;
}

gboolean
qof_instance_has_slot (const QofInstance *inst, const char *path)
{
    return inst->kvp_data->get_slot({path}) != NULL;
}

void qof_instance_slot_path_delete (QofInstance const * inst, std::vector<std::string> const & path)
{
    delete inst->kvp_data->set (path, nullptr);
}

void
qof_instance_slot_delete (QofInstance const *inst, char const * path)
{
    delete inst->kvp_data->set ({path}, nullptr);
}

void qof_instance_slot_path_delete_if_empty (QofInstance const * inst, std::vector<std::string> const & path)
{
    auto slot = inst->kvp_data->get_slot (path);
    if (slot)
    {
        auto frame = slot->get <KvpFrame*> ();
        if (frame && frame->empty())
            delete inst->kvp_data->set (path, nullptr);
    }
}

void
qof_instance_slot_delete_if_empty (QofInstance const *inst, char const * path)
{
    auto slot = inst->kvp_data->get_slot ({path});
    if (slot)
    {
        auto frame = slot->get <KvpFrame*> ();
        if (frame && frame->empty ())
            delete inst->kvp_data->set ({path}, nullptr);
    }
}

std::vector <std::pair <std::string, KvpValue*>>
qof_instance_get_slots_prefix (QofInstance const * inst, std::string const & prefix)
{
    std::vector <std::pair <std::string, KvpValue*>> ret;
    inst->kvp_data->for_each_slot_temp ([&prefix, &ret] (std::string const & key, KvpValue * val) {
        if (key.find (prefix) == 0)
            ret.emplace_back (key, val);
    });
    return ret;
}

namespace {
struct wrap_param
{
    void (*proc)(const char*, const GValue*, void*);
    void *user_data;
};
}

static void
wrap_gvalue_function (const char* key, KvpValue *val, wrap_param & param)
{
    GValue *gv;
    if (val->get_type() != KvpValue::Type::FRAME)
        gv = gvalue_from_kvp_value(val);
    else
    {
        gv = g_slice_new0 (GValue);
        g_value_init (gv, G_TYPE_STRING);
        g_value_set_string (gv, nullptr);
    }
    param.proc(key, gv, param.user_data);
    g_slice_free (GValue, gv);
}

void
qof_instance_foreach_slot (const QofInstance *inst, const char* head, const char* category,
                           void (*proc)(const char*, const GValue*, void*), void* data)
{
    std::vector<std::string> path {head};
    if (category)
        path.emplace_back (category);

    auto slot = inst->kvp_data->get_slot(path);
    if (slot == nullptr || slot->get_type() != KvpValue::Type::FRAME)
        return;
    auto frame = slot->get<KvpFrame*>();
    wrap_param new_data {proc, data};
    frame->for_each_slot_temp(&wrap_gvalue_function, new_data);
}

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

