/********************************************************************\
 * QueryCore.c -- API for providing core Query data types           *
 * Copyright (C) 2002 Derek Atkins <warlord@MIT.EDU>                *
 *                                                                  *
 * 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 "guid.hpp"
#include <config.h>

#include <glib.h>
#include <stdlib.h>

#include "qof.h"
#include "qofquerycore-p.h"

static QofLogModule log_module = QOF_MOD_QUERY;

/* A function to destroy a query predicate's pdata */
typedef void (*QueryPredDataFree) (QofQueryPredData *pdata);

/* A function to copy a query's predicate data */
typedef QofQueryPredData *(*QueryPredicateCopyFunc) (const QofQueryPredData *pdata);

/* A function to take the object, apply the getter->param_getfcn,
 * and return a printable string.  Note that this QofParam->getfnc
 * function should be returning a type equal to this core object type.
 *
 * Note that this string MUST be freed by the caller.
 */
typedef char * (*QueryToString) (gpointer object, QofParam *getter);

/* A function to test for equality of predicate data */
typedef gboolean (*QueryPredicateEqual) (const QofQueryPredData *p1,
        const QofQueryPredData *p2);

static QueryPredicateCopyFunc qof_query_copy_predicate (QofType type);
static QueryPredDataFree qof_query_predicate_free (QofType type);

/* Core Type Predicate helpers */
typedef const char * (*query_string_getter) (gpointer, QofParam *);
static const char * query_string_type = QOF_TYPE_STRING;

typedef time64 (*query_date_getter) (gpointer, QofParam *);
static const char * query_date_type = QOF_TYPE_DATE;

typedef gnc_numeric (*query_numeric_getter) (gpointer, QofParam *);
static const char * query_numeric_type = QOF_TYPE_NUMERIC;

typedef GList * (*query_glist_getter) (gpointer, QofParam *);
typedef const GncGUID * (*query_guid_getter) (gpointer, QofParam *);
static const char * query_guid_type = QOF_TYPE_GUID;

typedef gint32 (*query_int32_getter) (gpointer, QofParam *);
static const char * query_int32_type = QOF_TYPE_INT32;

typedef gint64 (*query_int64_getter) (gpointer, QofParam *);
static const char * query_int64_type = QOF_TYPE_INT64;

typedef double (*query_double_getter) (gpointer, QofParam *);
static const char * query_double_type = QOF_TYPE_DOUBLE;

typedef gboolean (*query_boolean_getter) (gpointer, QofParam *);
static const char * query_boolean_type = QOF_TYPE_BOOLEAN;

typedef char (*query_char_getter) (gpointer, QofParam *);
static const char * query_char_type = QOF_TYPE_CHAR;

typedef QofCollection * (*query_collect_getter) (gpointer, QofParam*);
static const char * query_collect_type = QOF_TYPE_COLLECT;

typedef const GncGUID * (*query_choice_getter) (gpointer, QofParam *);
static const char * query_choice_type = QOF_TYPE_CHOICE;

/* Tables for predicate storage and lookup */
static gboolean initialized = FALSE;
static GHashTable *predTable = NULL;
static GHashTable *cmpTable = NULL;
static GHashTable *copyTable = NULL;
static GHashTable *freeTable = NULL;
static GHashTable *toStringTable = NULL;
static GHashTable *predEqualTable = NULL;

#define COMPARE_ERROR -3
#define PREDICATE_ERROR -2

#define VERIFY_PDATA(str) { \
        g_return_if_fail (pd != NULL); \
        g_return_if_fail (pd->type_name == str || \
                        !g_strcmp0 (str, pd->type_name)); \
}
#define VERIFY_PDATA_R(str) { \
        g_return_val_if_fail (pd != NULL, NULL); \
        g_return_val_if_fail (pd->type_name == str || \
                                !g_strcmp0 (str, pd->type_name), \
                                NULL); \
}
#define VERIFY_PREDICATE(str) { \
        g_return_val_if_fail (getter != NULL, PREDICATE_ERROR); \
        g_return_val_if_fail (getter->param_getfcn != NULL, PREDICATE_ERROR); \
        g_return_val_if_fail (pd != NULL, PREDICATE_ERROR); \
        g_return_val_if_fail (pd->type_name == str || \
                                !g_strcmp0 (str, pd->type_name), \
                                PREDICATE_ERROR); \
}

/* *******************************************************************/
/* TYPE-HANDLING FUNCTIONS */

/* QOF_TYPE_STRING */

static int
string_match_predicate (gpointer object,
                        QofParam *getter,
                        QofQueryPredData *pd)
{
    query_string_t pdata = (query_string_t) pd;
    const char *s;
    int ret = 0;

    VERIFY_PREDICATE (query_string_type);

    s = ((query_string_getter)getter->param_getfcn) (object, getter);

    if (!s) s = "";

    if (pdata->is_regex)
    {
        regmatch_t match;
        if (!regexec (&pdata->compiled, s, 1, &match, 0))
            ret = 1;
    }
    else
    {
        if (pdata->options == QOF_STRING_MATCH_CASEINSENSITIVE)
        {
            if (pd->how == QOF_COMPARE_CONTAINS || pd->how == QOF_COMPARE_NCONTAINS)
            {
                if (qof_utf8_substr_nocase (s, pdata->matchstring)) //uses strstr
                    ret = 1;
            }
            else
            {
                 if (safe_strcasecmp (s, pdata->matchstring) == 0) //uses collate
                    ret = 1;
            }
        }
        else
        {
            if (pd->how == QOF_COMPARE_CONTAINS || pd->how == QOF_COMPARE_NCONTAINS)
            {
                if (strstr (s, pdata->matchstring))
                    ret = 1;
            }
            else
            {
                if (g_strcmp0 (s, pdata->matchstring) == 0)
                    ret = 1;
            }
        }
    }

    switch (pd->how)
    {
    case QOF_COMPARE_CONTAINS:
        return ret;
    case QOF_COMPARE_NCONTAINS:
        return !ret;
    case QOF_COMPARE_EQUAL:
        return ret;
    case QOF_COMPARE_NEQ:
        return !ret;
    default:
        PWARN ("bad match type: %d", pd->how);
        return 0;
    }
}

static int
string_compare_func (gpointer a, gpointer b, gint options,
                     QofParam *getter)
{
    const char *s1, *s2;
    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);

    s1 = ((query_string_getter)getter->param_getfcn) (a, getter);
    s2 = ((query_string_getter)getter->param_getfcn) (b, getter);

    if (options == QOF_STRING_MATCH_CASEINSENSITIVE)
        return safe_strcasecmp (s1, s2);

    return g_strcmp0 (s1, s2);
}

int
qof_string_number_compare_func (gpointer a, gpointer b, gint options,
                                QofParam *getter)
{
    const char *s1, *s2;
    char *sr1, *sr2;
    long i1, i2;
    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);

    s1 = ((query_string_getter)getter->param_getfcn) (a, getter);
    s2 = ((query_string_getter)getter->param_getfcn) (b, getter);

    // Deal with NULL strings
    if (s1 == s2)  return 0;
    if (!s1 && s2) return -1;
    if (s1 && !s2) return 1;

    // Convert to integers and test
    i1 = strtol(s1, &sr1, 10);
    i2 = strtol(s2, &sr2, 10);
    if (i1 < i2)  return -1;
    if (i1 > i2)  return 1;

    // If the integers match, then test the REST of the string as text.
    if (options == QOF_STRING_MATCH_CASEINSENSITIVE)
        return safe_strcasecmp (sr1, sr2);

    return g_strcmp0 (sr1, sr2);
}

static void
string_free_pdata (QofQueryPredData *pd)
{
    query_string_t pdata = (query_string_t) pd;

    VERIFY_PDATA (query_string_type);

    if (pdata->is_regex)
        regfree (&pdata->compiled);

    g_free (pdata->matchstring);
    g_free (pdata);
}

static QofQueryPredData *
string_copy_predicate (const QofQueryPredData *pd)
{
    const query_string_t pdata = (const query_string_t) pd;

    VERIFY_PDATA_R (query_string_type);

    return qof_query_string_predicate (pd->how, pdata->matchstring,
                                       pdata->options,
                                       pdata->is_regex);
}

static gboolean
string_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_string_t pd1 = (const query_string_t) p1;
    const query_string_t pd2 = (const query_string_t) p2;

    if (pd1->options != pd2->options) return FALSE;
    if (pd1->is_regex != pd2->is_regex) return FALSE;
    return (g_strcmp0 (pd1->matchstring, pd2->matchstring) == 0);
}

QofQueryPredData *
qof_query_string_predicate (QofQueryCompare how,
                            const char *str, QofStringMatch options,
                            gboolean is_regex)
{
    query_string_t pdata;

    g_return_val_if_fail (str, NULL);
//    g_return_val_if_fail (*str != '\0', NULL);
    g_return_val_if_fail (how == QOF_COMPARE_CONTAINS || how == QOF_COMPARE_NCONTAINS ||
                          how == QOF_COMPARE_EQUAL || how == QOF_COMPARE_NEQ, NULL);

    pdata = g_new0 (query_string_def, 1);
    pdata->pd.type_name = query_string_type;
    pdata->pd.how = how;
    pdata->options = options;
    pdata->matchstring = g_strdup (str);

    if (is_regex)
    {
        int rc;
        int flags = REG_EXTENDED;
        if (options == QOF_STRING_MATCH_CASEINSENSITIVE)
            flags |= REG_ICASE;

        rc = regcomp(&pdata->compiled, str, flags);
        if (rc)
        {
            g_free(pdata->matchstring);
            g_free(pdata);
            return NULL;
        }
        pdata->is_regex = TRUE;
    }

    return ((QofQueryPredData*)pdata);
}

static char *
string_to_string (gpointer object, QofParam *getter)
{
    const char *res;
    res = ((query_string_getter)getter->param_getfcn)(object, getter);
    if (res)
        return g_strdup (res);
    return NULL;
}

/* QOF_TYPE_DATE =================================================== */

static int
date_compare (time64 ta, time64 tb, QofDateMatch options)
{

    if (options == QOF_DATE_MATCH_DAY)
    {
        ta = time64CanonicalDayTime (ta);
        tb = time64CanonicalDayTime (tb);
    }

    if (ta < tb)
        return -1;
    if (ta > tb)
        return 1;

    return 0;
}

static int
date_match_predicate (gpointer object, QofParam *getter,
                      QofQueryPredData *pd)
{
    query_date_t pdata = (query_date_t)pd;
    time64 objtime;
    int compare;

    VERIFY_PREDICATE (query_date_type);

    objtime = ((query_date_getter)getter->param_getfcn) (object, getter);
    compare = date_compare (objtime, pdata->date, pdata->options);

    switch (pd->how)
    {
    case QOF_COMPARE_LT:
        return (compare < 0);
    case QOF_COMPARE_LTE:
        return (compare <= 0);
    case QOF_COMPARE_EQUAL:
        return (compare == 0);
    case QOF_COMPARE_GT:
        return (compare > 0);
    case QOF_COMPARE_GTE:
        return (compare >= 0);
    case QOF_COMPARE_NEQ:
        return (compare != 0);
    default:
        PWARN ("bad match type: %d", pd->how);
        return 0;
    }
}

static int
date_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
{
    time64 ta, tb;

    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);

    ta = ((query_date_getter)getter->param_getfcn) (a, getter);
    tb = ((query_date_getter)getter->param_getfcn) (b, getter);

    return date_compare (ta, tb, static_cast<QofDateMatch>(options));
}

static void
date_free_pdata (QofQueryPredData *pd)
{
    query_date_t pdata = (query_date_t)pd;

    VERIFY_PDATA (query_date_type);

    g_free (pdata);
}

static QofQueryPredData *
date_copy_predicate (const QofQueryPredData *pd)
{
    const query_date_t pdata = (const query_date_t)pd;

    VERIFY_PDATA_R (query_date_type);

    return qof_query_date_predicate (pd->how, pdata->options, pdata->date);
}

static gboolean
date_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_date_t pd1 = (const query_date_t) p1;
    const query_date_t pd2 = (const query_date_t) p2;

    if (pd1->options != pd2->options) return FALSE;
    return (pd1->date == pd2->date);
}

QofQueryPredData *
qof_query_date_predicate (QofQueryCompare how,
                          QofDateMatch options, time64 date)
{
    query_date_t pdata;

    pdata = g_new0 (query_date_def, 1);
    pdata->pd.type_name = query_date_type;
    pdata->pd.how = how;
    pdata->options = options;
    pdata->date = date;
    return ((QofQueryPredData*)pdata);
}

gboolean
qof_query_date_predicate_get_date (const QofQueryPredData *pd, time64 *date)
{
    const query_date_t pdata = (const query_date_t)pd;

    if (pdata->pd.type_name != query_date_type)
        return FALSE;
    *date = pdata->date;
    return TRUE;
}

static char *
date_to_string (gpointer object, QofParam *getter)
{
    time64 tt = ((query_date_getter)getter->param_getfcn)(object, getter);

    if (tt != INT64_MAX)
        return qof_print_date (tt);

    return NULL;
}

/* QOF_TYPE_NUMERIC ================================================= */

static int
numeric_match_predicate (gpointer object, QofParam *getter,
                         QofQueryPredData* pd)
{
    query_numeric_t pdata = (query_numeric_t)pd;
    gnc_numeric obj_val;
    int compare;

    VERIFY_PREDICATE (query_numeric_type);

    obj_val = ((query_numeric_getter)getter->param_getfcn) (object, getter);

    switch (pdata->options)
    {
    case QOF_NUMERIC_MATCH_CREDIT:
        if (gnc_numeric_positive_p (obj_val)) return 0;
        break;
    case QOF_NUMERIC_MATCH_DEBIT:
        if (gnc_numeric_negative_p (obj_val)) return 0;
        break;
    default:
        break;
    }

    /* Amounts are considered to be 'equal' if they match to
     * four decimal places. (epsilon=1/10000) */
    if (pd->how == QOF_COMPARE_EQUAL || pd->how == QOF_COMPARE_NEQ)
    {
        gnc_numeric cmp_val = gnc_numeric_create (1, 10000);
        compare =
            (gnc_numeric_compare (gnc_numeric_abs
                                  (gnc_numeric_sub (gnc_numeric_abs (obj_val),
                                          gnc_numeric_abs (pdata->amount),
                                          100000, GNC_HOW_RND_ROUND_HALF_UP)),
                                  cmp_val) < 0);
    }
    else
        compare = gnc_numeric_compare (gnc_numeric_abs (obj_val), pdata->amount);

    switch (pd->how)
    {
    case QOF_COMPARE_LT:
        return (compare < 0);
    case QOF_COMPARE_LTE:
        return (compare <= 0);
    case QOF_COMPARE_EQUAL:
        return compare;
    case QOF_COMPARE_GT:
        return (compare > 0);
    case QOF_COMPARE_GTE:
        return (compare >= 0);
    case QOF_COMPARE_NEQ:
        return !compare;
    default:
        PWARN ("bad match type: %d", pd->how);
        return 0;
    }
}

static int
numeric_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
{
    gnc_numeric va, vb;

    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);

    va = ((query_numeric_getter)getter->param_getfcn) (a, getter);
    vb = ((query_numeric_getter)getter->param_getfcn) (b, getter);

    return gnc_numeric_compare (va, vb);
}

static void
numeric_free_pdata (QofQueryPredData* pd)
{
    query_numeric_t pdata = (query_numeric_t)pd;
    VERIFY_PDATA (query_numeric_type);
    g_free (pdata);
}

static QofQueryPredData *
numeric_copy_predicate (const QofQueryPredData *pd)
{
    const query_numeric_t pdata = (const query_numeric_t)pd;
    VERIFY_PDATA_R (query_numeric_type);
    return qof_query_numeric_predicate (pd->how, pdata->options, pdata->amount);
}

static gboolean
numeric_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_numeric_t pd1 = (const query_numeric_t) p1;
    const query_numeric_t pd2 = (const query_numeric_t) p2;

    if (pd1->options != pd2->options) return FALSE;
    return gnc_numeric_equal (pd1->amount, pd2->amount);
}

QofQueryPredData *
qof_query_numeric_predicate (QofQueryCompare how,
                             QofNumericMatch options,
                             gnc_numeric value)
{
    query_numeric_t pdata;
    pdata = g_new0 (query_numeric_def, 1);
    pdata->pd.type_name = query_numeric_type;
    pdata->pd.how = how;
    pdata->options = options;
    pdata->amount = value;
    return ((QofQueryPredData*)pdata);
}

static char *
numeric_to_string (gpointer object, QofParam *getter)
{
    gnc_numeric num;
    num = ((query_numeric_getter)getter->param_getfcn)(object, getter);

    return gnc_numeric_to_string (num);
}

static char *
debcred_to_string (gpointer object, QofParam *getter)
{
    gnc_numeric num;
    num = ((query_numeric_getter)getter->param_getfcn)(object, getter);

    return gnc_numeric_to_string (num);
}

/* QOF_TYPE_GUID =================================================== */

static int
guid_match_predicate (gpointer object, QofParam *getter,
                      QofQueryPredData *pd)
{
    query_guid_t pdata = (query_guid_t)pd;
    GList *node, *o_list;
    const GncGUID *guid = NULL;

    VERIFY_PREDICATE (query_guid_type);

    switch (pdata->options)
    {

    case QOF_GUID_MATCH_ALL:
        /* object is a GList of objects; param_getfcn must be called on each one.
         * See if every guid in the predicate is accounted-for in the
         * object list
         */

        for (node = pdata->guids; node; node = node->next)
        {
            /* See if this GncGUID matches the object's guid */
            for (o_list = static_cast<GList*>(object); o_list;
		 o_list = static_cast<GList*>(o_list->next))
            {
                guid = ((query_guid_getter)getter->param_getfcn) (o_list->data, getter);
                if (guid_equal (static_cast<GncGUID*>(node->data), guid))
                    break;
            }

            /*
             * If o_list is NULL, we've walked the whole list without finding
             * a match.  Therefore break out now, the match has failed.
             */
            if (o_list == NULL)
                break;
        }

        /*
         * The match is complete.  If node == NULL then we've successfully
         * found a match for all the guids in the predicate.  Return
         * appropriately below.
         */

        break;

    case QOF_GUID_MATCH_LIST_ANY:
        /* object is a single object, getter returns a GList* of GncGUID*
         *
         * See if any GncGUID* in the returned list matches any guid in the
         * predicate match list.
         */

        o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);

        for (node = o_list; node; node = node->next)
        {
            GList *node2;

            /* Search the predicate data for a match */
            for (node2 = pdata->guids; node2; node2 = node2->next)
            {
                if (guid_equal (static_cast<GncGUID*>(node->data),
				static_cast<GncGUID*>(node2->data)))
                    break;
            }

            /* Check to see if we found a match.  If so, break now */
            if (node2 != NULL)
                break;
        }

        g_list_free(o_list);

        /* yea, node may point to an invalid location, but that's ok.
         * we're not _USING_ the value, just checking that it's non-NULL
         */

        break;

    default:
        /* object is a single object, getter returns a GncGUID*
         *
         * See if the guid is in the list
         */

        guid = ((query_guid_getter)getter->param_getfcn) (object, getter);
        for (node = pdata->guids; node; node = node->next)
        {
            if (guid_equal (static_cast<GncGUID*>(node->data), guid))
                break;
        }
    }

    switch (pdata->options)
    {
    case QOF_GUID_MATCH_ANY:
    case QOF_GUID_MATCH_LIST_ANY:
        return (node != NULL);
        break;
    case QOF_GUID_MATCH_NONE:
    case QOF_GUID_MATCH_ALL:
        return (node == NULL);
        break;
    case QOF_GUID_MATCH_NULL:
        return ((guid == NULL) || guid_equal(guid, guid_null()));
        break;
    default:
        PWARN ("bad match type");
        return 0;
    }
}

static void
guid_free_pdata (QofQueryPredData *pd)
{
    query_guid_t pdata = (query_guid_t)pd;
    GList *node;
    VERIFY_PDATA (query_guid_type);
    for (node = pdata->guids; node; node = node->next)
    {
        guid_free (static_cast<GncGUID*>(node->data));
    }
    g_list_free (pdata->guids);
    g_free (pdata);
}

static QofQueryPredData *
guid_copy_predicate (const QofQueryPredData *pd)
{
    const query_guid_t pdata = (const query_guid_t)pd;
    VERIFY_PDATA_R (query_guid_type);
    return qof_query_guid_predicate (pdata->options, pdata->guids);
}

static gboolean
guid_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_guid_t pd1 = (const query_guid_t) p1;
    const query_guid_t pd2 = (const query_guid_t) p2;
    GList *l1 = pd1->guids, *l2 = pd2->guids;

    if (pd1->options != pd2->options) return FALSE;
    if (g_list_length (l1) != g_list_length (l2)) return FALSE;
    for ( ; l1 ; l1 = l1->next, l2 = l2->next)
    {
        if (!guid_equal (static_cast<GncGUID*>(l1->data),
			 static_cast<GncGUID*>(l2->data)))
            return FALSE;
    }
    return TRUE;
}

QofQueryPredData *
qof_query_guid_predicate (QofGuidMatch options, GList *guid_list)
{
    query_guid_t pdata;
    GList *node;

    /* An empty list of guids is only valid when testing for a null GUID value */
    if (!guid_list)
        g_return_val_if_fail (options == QOF_GUID_MATCH_NULL, NULL);

    pdata = g_new0 (query_guid_def, 1);
    pdata->pd.how = QOF_COMPARE_EQUAL;
    pdata->pd.type_name = query_guid_type;
    pdata->options = options;

    pdata->guids = g_list_copy (guid_list);
    for (node = pdata->guids; node; node = node->next)
    {
        GncGUID *guid = guid_malloc ();
        *guid = *((GncGUID *)node->data);
        node->data = guid;
    }
    return ((QofQueryPredData*)pdata);
}

/* ================================================================ */
/* QOF_TYPE_INT32 */

static int
int32_match_predicate (gpointer object, QofParam *getter,
                       QofQueryPredData *pd)
{
    gint32 val;
    query_int32_t pdata = (query_int32_t)pd;

    VERIFY_PREDICATE (query_int32_type);

    val = ((query_int32_getter)getter->param_getfcn) (object, getter);

    switch (pd->how)
    {
    case QOF_COMPARE_LT:
        return (val < pdata->val);
    case QOF_COMPARE_LTE:
        return (val <= pdata->val);
    case QOF_COMPARE_EQUAL:
        return (val == pdata->val);
    case QOF_COMPARE_GT:
        return (val > pdata->val);
    case QOF_COMPARE_GTE:
        return (val >= pdata->val);
    case QOF_COMPARE_NEQ:
        return (val != pdata->val);
    default:
        PWARN ("bad match type: %d", pd->how);
        return 0;
    }
}

static int
int32_compare_func (gpointer a, gpointer b, gint options,
                    QofParam *getter)
{
    gint32 v1, v2;
    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);

    v1 = ((query_int32_getter)getter->param_getfcn)(a, getter);
    v2 = ((query_int32_getter)getter->param_getfcn)(b, getter);

    if (v1 < v2) return -1;
    if (v1 > v2) return 1;
    return 0;
}

static void
int32_free_pdata (QofQueryPredData *pd)
{
    query_int32_t pdata = (query_int32_t)pd;
    VERIFY_PDATA (query_int32_type);
    g_free (pdata);
}

static QofQueryPredData *
int32_copy_predicate (const QofQueryPredData *pd)
{
    const query_int32_t pdata = (const query_int32_t)pd;
    VERIFY_PDATA_R (query_int32_type);
    return qof_query_int32_predicate (pd->how, pdata->val);
}

static gboolean
int32_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_int32_t pd1 = (const query_int32_t) p1;
    const query_int32_t pd2 = (const query_int32_t) p2;

    return (pd1->val == pd2->val);
}

QofQueryPredData *
qof_query_int32_predicate (QofQueryCompare how, gint32 val)
{
    query_int32_t pdata = g_new0 (query_int32_def, 1);
    pdata->pd.type_name = query_int32_type;
    pdata->pd.how = how;
    pdata->val = val;
    return ((QofQueryPredData*)pdata);
}

static char *
int32_to_string (gpointer object, QofParam *getter)
{
    gint32 num = ((query_int32_getter)getter->param_getfcn)(object, getter);

    return g_strdup_printf ("%d", num);
}

/* ================================================================ */
/* QOF_TYPE_INT64 */

static int
int64_match_predicate (gpointer object, QofParam *getter,
                       QofQueryPredData *pd)
{
    gint64 val;
    query_int64_t pdata = (query_int64_t)pd;

    VERIFY_PREDICATE (query_int64_type);

    val = ((query_int64_getter)getter->param_getfcn) (object, getter);

    switch (pd->how)
    {
    case QOF_COMPARE_LT:
        return (val < pdata->val);
    case QOF_COMPARE_LTE:
        return (val <= pdata->val);
    case QOF_COMPARE_EQUAL:
        return (val == pdata->val);
    case QOF_COMPARE_GT:
        return (val > pdata->val);
    case QOF_COMPARE_GTE:
        return (val >= pdata->val);
    case QOF_COMPARE_NEQ:
        return (val != pdata->val);
    default:
        PWARN ("bad match type: %d", pd->how);
        return 0;
    }
}

static int
int64_compare_func (gpointer a, gpointer b, gint options,
                    QofParam *getter)
{
    gint64 v1, v2;
    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);

    v1 = ((query_int64_getter)getter->param_getfcn)(a, getter);
    v2 = ((query_int64_getter)getter->param_getfcn)(b, getter);

    if (v1 < v2) return -1;
    if (v1 > v2) return 1;
    return 0;
}

static void
int64_free_pdata (QofQueryPredData *pd)
{
    query_int64_t pdata = (query_int64_t)pd;
    VERIFY_PDATA (query_int64_type);
    g_free (pdata);
}

static QofQueryPredData *
int64_copy_predicate (const QofQueryPredData *pd)
{
    const query_int64_t pdata = (const query_int64_t)pd;
    VERIFY_PDATA_R (query_int64_type);
    return qof_query_int64_predicate (pd->how, pdata->val);
}

static gboolean
int64_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_int64_t pd1 = (const query_int64_t) p1;
    const query_int64_t pd2 = (const query_int64_t) p2;

    return (pd1->val == pd2->val);
}

QofQueryPredData *
qof_query_int64_predicate (QofQueryCompare how, gint64 val)
{
    query_int64_t pdata = g_new0 (query_int64_def, 1);
    pdata->pd.type_name = query_int64_type;
    pdata->pd.how = how;
    pdata->val = val;
    return ((QofQueryPredData*)pdata);
}

static char *
int64_to_string (gpointer object, QofParam *getter)
{
    gint64 num = ((query_int64_getter)getter->param_getfcn)(object, getter);

    return g_strdup_printf ("%" G_GINT64_FORMAT, num);
}

/* ================================================================ */
/* QOF_TYPE_DOUBLE */

static int
double_match_predicate (gpointer object, QofParam *getter,
                        QofQueryPredData *pd)
{
    double val;
    query_double_t pdata = (query_double_t)pd;

    VERIFY_PREDICATE (query_double_type);

    val = ((query_double_getter)getter->param_getfcn) (object, getter);

    switch (pd->how)
    {
    case QOF_COMPARE_LT:
        return (val < pdata->val);
    case QOF_COMPARE_LTE:
        return (val <= pdata->val);
    case QOF_COMPARE_EQUAL:
        return (val == pdata->val);
    case QOF_COMPARE_GT:
        return (val > pdata->val);
    case QOF_COMPARE_GTE:
        return (val >= pdata->val);
    case QOF_COMPARE_NEQ:
        return (val != pdata->val);
    default:
        PWARN ("bad match type: %d", pd->how);
        return 0;
    }
}

static int
double_compare_func (gpointer a, gpointer b, gint options,
                     QofParam *getter)
{
    double v1, v2;
    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);

    v1 = ((query_double_getter)getter->param_getfcn) (a, getter);
    v2 = ((query_double_getter)getter->param_getfcn) (b, getter);

    if (v1 < v2) return -1;
    if (v1 > v2) return 1;
    return 0;
}

static void
double_free_pdata (QofQueryPredData *pd)
{
    query_double_t pdata = (query_double_t)pd;
    VERIFY_PDATA (query_double_type);
    g_free (pdata);
}

static QofQueryPredData *
double_copy_predicate (const QofQueryPredData *pd)
{
    const query_double_t pdata = (const query_double_t)pd;
    VERIFY_PDATA_R (query_double_type);
    return qof_query_double_predicate (pd->how, pdata->val);
}

static gboolean
double_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_double_t pd1 = (const query_double_t) p1;
    const query_double_t pd2 = (const query_double_t) p2;

    return (pd1->val == pd2->val);
}

QofQueryPredData *
qof_query_double_predicate (QofQueryCompare how, double val)
{
    query_double_t pdata = g_new0 (query_double_def, 1);
    pdata->pd.type_name = query_double_type;
    pdata->pd.how = how;
    pdata->val = val;
    return ((QofQueryPredData*)pdata);
}

static char *
double_to_string (gpointer object, QofParam *getter)
{
    double num = ((query_double_getter)getter->param_getfcn)(object, getter);

    return g_strdup_printf ("%f", num);
}

/* QOF_TYPE_BOOLEAN =================================================== */

static int
boolean_match_predicate (gpointer object, QofParam *getter,
                         QofQueryPredData *pd)
{
    gboolean val;
    query_boolean_t pdata = (query_boolean_t)pd;

    VERIFY_PREDICATE (query_boolean_type);

    val = ((query_boolean_getter)getter->param_getfcn) (object, getter);

    switch (pd->how)
    {
    case QOF_COMPARE_EQUAL:
        return (val == pdata->val);
    case  QOF_COMPARE_NEQ:
        return (val != pdata->val);
    default:
        PWARN ("bad match type: %d", pd->how);
        return 0;
    }
}

static int
boolean_compare_func (gpointer a, gpointer b, gint options,
                      QofParam *getter)
{
    gboolean va, vb;
    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
    va = ((query_boolean_getter)getter->param_getfcn) (a, getter);
    vb = ((query_boolean_getter)getter->param_getfcn) (b, getter);
    if (!va && vb) return -1;
    if (va && !vb) return 1;
    return 0;
}

static void
boolean_free_pdata (QofQueryPredData *pd)
{
    query_boolean_t pdata = (query_boolean_t)pd;
    VERIFY_PDATA (query_boolean_type);
    g_free (pdata);
}

static QofQueryPredData *
boolean_copy_predicate (const QofQueryPredData *pd)
{
    const query_boolean_t pdata = (const query_boolean_t)pd;
    VERIFY_PDATA_R (query_boolean_type);
    return qof_query_boolean_predicate (pd->how, pdata->val);
}

static gboolean
boolean_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_boolean_t pd1 = (const query_boolean_t) p1;
    const query_boolean_t pd2 = (const query_boolean_t) p2;

    return (pd1->val == pd2->val);
}

QofQueryPredData *
qof_query_boolean_predicate (QofQueryCompare how, gboolean val)
{
    query_boolean_t pdata;
    g_return_val_if_fail (how == QOF_COMPARE_EQUAL || how == QOF_COMPARE_NEQ, NULL);

    pdata = g_new0 (query_boolean_def, 1);
    pdata->pd.type_name = query_boolean_type;
    pdata->pd.how = how;
    pdata->val = val;
    return ((QofQueryPredData*)pdata);
}

static char *
boolean_to_string (gpointer object, QofParam *getter)
{
    gboolean num = ((query_boolean_getter)getter->param_getfcn)(object, getter);

    return g_strdup_printf ("%s", (num ? "X" : ""));
}

/* QOF_TYPE_CHAR =================================================== */

static int
char_match_predicate (gpointer object, QofParam *getter,
                      QofQueryPredData *pd)
{
    char c;
    query_char_t pdata = (query_char_t)pd;

    VERIFY_PREDICATE (query_char_type);

    c = ((query_char_getter)getter->param_getfcn) (object, getter);

    switch (pdata->options)
    {
    case QOF_CHAR_MATCH_ANY:
        if (strchr (pdata->char_list, c)) return 1;
        return 0;
    case QOF_CHAR_MATCH_NONE:
        if (!strchr (pdata->char_list, c)) return 1;
        return 0;
    default:
        PWARN ("bad match type");
        return 0;
    }
}

static int
char_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
{
    char va, vb;
    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
    va = ((query_char_getter)getter->param_getfcn)(a, getter);
    vb = ((query_char_getter)getter->param_getfcn)(b, getter);
    return (va - vb);
}

static void
char_free_pdata (QofQueryPredData *pd)
{
    query_char_t pdata = (query_char_t)pd;
    VERIFY_PDATA (query_char_type);
    g_free (pdata->char_list);
    g_free (pdata);
}

static QofQueryPredData *
char_copy_predicate (const QofQueryPredData *pd)
{
    const query_char_t pdata = (const query_char_t)pd;
    VERIFY_PDATA_R (query_char_type);
    return qof_query_char_predicate (pdata->options, pdata->char_list);
}

static gboolean
char_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_char_t pd1 = (const query_char_t) p1;
    const query_char_t pd2 = (const query_char_t) p2;

    if (pd1->options != pd2->options) return FALSE;
    return (g_strcmp0 (pd1->char_list, pd2->char_list) == 0);
}

QofQueryPredData *
qof_query_char_predicate (QofCharMatch options, const char *chars)
{
    query_char_t pdata;
    g_return_val_if_fail (chars, NULL);
    pdata = g_new0 (query_char_def, 1);
    pdata->pd.type_name = query_char_type;
    pdata->pd.how = QOF_COMPARE_EQUAL;
    pdata->options = options;
    pdata->char_list = g_strdup (chars);
    return ((QofQueryPredData*)pdata);
}

static char *
char_to_string (gpointer object, QofParam *getter)
{
    char num = ((query_char_getter)getter->param_getfcn)(object, getter);

    return g_strdup_printf ("%c", num);
}

/* QOF_TYPE_COLLECT =============================================== */

static int
collect_match_predicate (gpointer object, QofParam *getter,
                         QofQueryPredData *pd)
{
    query_coll_t pdata;
    GList *node, *node2, *o_list;
    const GncGUID *guid;

    pdata = (query_coll_t)pd;
    VERIFY_PREDICATE (query_collect_type);
    guid = NULL;
    switch (pdata->options)
    {
    case QOF_GUID_MATCH_ALL :
    {
        for (node = pdata->guids; node; node = node->next)
        {
            for (o_list = static_cast<GList*>(object); o_list;
		 o_list = static_cast<GList*>(o_list->next))
            {
                guid = ((query_guid_getter)getter->param_getfcn)
                       (o_list->data, getter);
                if (guid_equal (static_cast<GncGUID*>(node->data), guid))
                {
                    break;
                }
            }
            if (o_list == NULL)
            {
                break;
            }
        }
        break;
    }
    case QOF_GUID_MATCH_LIST_ANY :
    {
        o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);
        for (node = o_list; node; node = node->next)
        {
            for (node2 = pdata->guids; node2; node2 = node2->next)
            {
                if (guid_equal (static_cast<GncGUID*>(node->data),
				static_cast<GncGUID*>(node2->data)))
                {
                    break;
                }
            }
            if (node2 != NULL)
            {
                break;
            }
        }
        g_list_free(o_list);
        break;
    }
    default :
    {
        guid = ((query_guid_getter)getter->param_getfcn) (object, getter);
        for (node = pdata->guids; node; node = node->next)
        {
            if (guid_equal (static_cast<GncGUID*>(node->data), guid))
            {
                break;
            }
        }
    }
    switch (pdata->options)
    {
    case QOF_GUID_MATCH_ANY :
    case QOF_GUID_MATCH_LIST_ANY :
    {
        return (node != NULL);
        break;
    }
    case QOF_GUID_MATCH_NONE :
    case QOF_GUID_MATCH_ALL :
    {
        return (node == NULL);
        break;
    }
    case QOF_GUID_MATCH_NULL :
    {
        return ((guid == NULL) || guid_equal(guid, guid_null()));
        break;
    }
    default :
    {
        PWARN ("bad match type");
        return 0;
    }
    }
    }
    return 0;
}

static int
collect_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
{
    gint result;
    QofCollection *c1, *c2;

    c1 = ((query_collect_getter)getter->param_getfcn) (a, getter);
    c2 = ((query_collect_getter)getter->param_getfcn) (b, getter);
    result = qof_collection_compare(c1, c2);
    return result;
}

static void
collect_free_pdata (QofQueryPredData *pd)
{
    query_coll_t pdata;
    GList *node;

    node = NULL;
    pdata = (query_coll_t) pd;
    VERIFY_PDATA (query_collect_type);
    for (node = pdata->guids; node; node = node->next)
    {
        guid_free (static_cast<GncGUID*>(node->data));
    }
    qof_collection_destroy(pdata->coll);
    g_list_free (pdata->guids);
    g_free (pdata);
}

static QofQueryPredData *
collect_copy_predicate (const QofQueryPredData *pd)
{
    const query_coll_t pdata = (const query_coll_t) pd;

    VERIFY_PDATA_R (query_collect_type);
    return qof_query_collect_predicate (pdata->options, pdata->coll);
}

static gboolean
collect_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_coll_t pd1 = (const query_coll_t) p1;
    const query_coll_t pd2 = (const query_coll_t) p2;
    gint result;

    result = qof_collection_compare(pd1->coll, pd2->coll);
    if (result == 0)
    {
        return TRUE;
    }
    return FALSE;
}

static void
query_collect_cb(QofInstance* ent, gpointer user_data)
{
    query_coll_t pdata;
    GncGUID *guid;

    guid = (GncGUID*)qof_entity_get_guid(ent);
    pdata = (query_coll_t)user_data;
    pdata->guids = g_list_append(pdata->guids, guid);
}

QofQueryPredData *
qof_query_collect_predicate (QofGuidMatch options, QofCollection *coll)
{
    query_coll_t pdata;

    g_return_val_if_fail (coll, NULL);
    pdata = g_new0 (query_coll_def, 1);
    pdata->pd.type_name = query_collect_type;
    pdata->options = options;
    qof_collection_foreach(coll, query_collect_cb, pdata);
    if (NULL == pdata->guids)
    {
        return NULL;
    }
    return ((QofQueryPredData*)pdata);
}

/* QOF_TYPE_CHOICE */

static int
choice_match_predicate (gpointer object, QofParam *getter,
                        QofQueryPredData *pd)
{
    query_choice_t pdata = (query_choice_t)pd;
    GList *node, *o_list;
    const GncGUID *guid = NULL;

    VERIFY_PREDICATE (query_choice_type);

    switch (pdata->options)
    {

    case QOF_GUID_MATCH_ALL:
        /* object is a GList of objects; param_getfcn must be called on each one.
         * See if every guid in the predicate is accounted-for in the
         * object list
         */

        for (node = pdata->guids; node; node = node->next)
        {
            /* See if this GncGUID matches the object's guid */
            for (o_list = static_cast<GList*>(object); o_list;
		 o_list = static_cast<GList*>(o_list->next))
            {
                guid = ((query_choice_getter)getter->param_getfcn) (o_list->data, getter);
                if (guid_equal (static_cast<GncGUID*>(node->data), guid))
                    break;
            }

            /*
             * If o_list is NULL, we've walked the whole list without finding
             * a match.  Therefore break out now, the match has failed.
             */
            if (o_list == NULL)
                break;
        }

        /*
         * The match is complete.  If node == NULL then we've successfully
         * found a match for all the guids in the predicate.  Return
         * appropriately below.
         */

        break;

    case QOF_GUID_MATCH_LIST_ANY:

        o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);

        for (node = o_list; node; node = node->next)
        {
            GList *node2;

            for (node2 = pdata->guids; node2; node2 = node2->next)
            {
                if (guid_equal (static_cast<GncGUID*>(node->data),
				static_cast<GncGUID*>(node2->data)))
                    break;
            }

            if (node2 != NULL)
                break;
        }

        g_list_free(o_list);

        break;

    default:
        /* object is a single object, getter returns a GncGUID*
         *
         * See if the guid is in the list
         */

        guid = ((query_choice_getter)getter->param_getfcn) (object, getter);
        for (node = pdata->guids; node; node = node->next)
        {
            if (guid_equal (static_cast<GncGUID*>(node->data), guid))
                break;
        }
    }

    switch (pdata->options)
    {
    case QOF_GUID_MATCH_ANY:
    case QOF_GUID_MATCH_LIST_ANY:
        return (node != NULL);
        break;
    case QOF_GUID_MATCH_NONE:
    case QOF_GUID_MATCH_ALL:
        return (node == NULL);
        break;
    case QOF_GUID_MATCH_NULL:
        return ((guid == NULL) || guid_equal(guid, guid_null()));
        break;
    default:
        PWARN ("bad match type");
        return 0;
    }
}

static void
choice_free_pdata (QofQueryPredData *pd)
{
    query_choice_t pdata = (query_choice_t)pd;
    GList *node;
    VERIFY_PDATA (query_choice_type);
    for (node = pdata->guids; node; node = node->next)
    {
        guid_free (static_cast<GncGUID*>(node->data));
    }
    g_list_free (pdata->guids);
    g_free (pdata);
}

static QofQueryPredData *
choice_copy_predicate (const QofQueryPredData *pd)
{
    const query_choice_t pdata = (const query_choice_t)pd;
    VERIFY_PDATA_R (query_choice_type);
    return qof_query_choice_predicate (pdata->options, pdata->guids);
}

static gboolean
choice_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    const query_choice_t pd1 = (const query_choice_t) p1;
    const query_choice_t pd2 = (const query_choice_t) p2;
    GList *l1 = pd1->guids, *l2 = pd2->guids;

    if (pd1->options != pd2->options) return FALSE;
    if (g_list_length (l1) != g_list_length (l2)) return FALSE;
    for ( ; l1 ; l1 = l1->next, l2 = l2->next)
    {
        if (!guid_equal (static_cast<GncGUID*>(l1->data),
			 static_cast<GncGUID*>(l2->data)))
            return FALSE;
    }
    return TRUE;
}

QofQueryPredData *
qof_query_choice_predicate (QofGuidMatch options, GList *guid_list)
{
    query_choice_t pdata;
    GList *node;

    if (NULL == guid_list) return NULL;

    pdata = g_new0 (query_choice_def, 1);
    pdata->pd.how = QOF_COMPARE_EQUAL;
    pdata->pd.type_name = query_choice_type;
    pdata->options = options;

    pdata->guids = g_list_copy (guid_list);
    for (node = pdata->guids; node; node = node->next)
    {
        GncGUID *guid = guid_malloc ();
        *guid = *((GncGUID *)node->data);
        node->data = guid;
    }
    return ((QofQueryPredData*)pdata);
}


/* initialization ================================================== */
/** This function registers a new Core Object with the QofQuery
 * subsystem.  It maps the "core_name" object to the given
 * query_predicate, predicate_copy, and predicate_data_free functions.
 *
 * An example:
 * qof_query_register_core_object (QOF_TYPE_STRING, string_match_predicate,
 *                               string_compare_fcn, string_free_pdata,
 *                               string_print_fcn, pred_equal_fcn);
 */


static void
qof_query_register_core_object (QofType core_name,
                                QofQueryPredicateFunc pred,
                                QofCompareFunc comp,
                                QueryPredicateCopyFunc copy,
                                QueryPredDataFree pd_free,
                                QueryToString toString,
                                QueryPredicateEqual pred_equal)
{
    g_return_if_fail (core_name);
    g_return_if_fail (*core_name != '\0');

    if (pred)
        g_hash_table_insert (predTable, (char *)core_name,
			     reinterpret_cast<void*>(pred));

    if (comp)
        g_hash_table_insert (cmpTable, (char *)core_name,
			     reinterpret_cast<void*>(comp));

    if (copy)
        g_hash_table_insert (copyTable, (char *)core_name,
			     reinterpret_cast<void*>(copy));

    if (pd_free)
        g_hash_table_insert (freeTable, (char *)core_name,
			     reinterpret_cast<void*>(pd_free));

    if (toString)
        g_hash_table_insert (toStringTable, (char *)core_name,
			     reinterpret_cast<void*>(toString));

    if (pred_equal)
        g_hash_table_insert (predEqualTable, (char *)core_name,
			     reinterpret_cast<void*>(pred_equal));
}

static void init_tables (void)
{
    unsigned int i;
    struct
    {
        QofType                name;
        QofQueryPredicateFunc  pred;
        QofCompareFunc         comp;
        QueryPredicateCopyFunc copy;
        QueryPredDataFree      pd_free;
        QueryToString          toString;
        QueryPredicateEqual    pred_equal;
    } knownTypes[] =
    {
        {
            QOF_TYPE_STRING, string_match_predicate, string_compare_func,
            string_copy_predicate, string_free_pdata, string_to_string,
            string_predicate_equal
        },
        {
            QOF_TYPE_DATE, date_match_predicate, date_compare_func,
            date_copy_predicate, date_free_pdata, date_to_string,
            date_predicate_equal
        },
        {
            QOF_TYPE_DEBCRED, numeric_match_predicate, numeric_compare_func,
            numeric_copy_predicate, numeric_free_pdata, debcred_to_string,
            numeric_predicate_equal
        },
        {
            QOF_TYPE_NUMERIC, numeric_match_predicate, numeric_compare_func,
            numeric_copy_predicate, numeric_free_pdata, numeric_to_string,
            numeric_predicate_equal
        },
        {
            QOF_TYPE_GUID, guid_match_predicate, NULL,
            guid_copy_predicate, guid_free_pdata, NULL,
            guid_predicate_equal
        },
        {
            QOF_TYPE_INT32, int32_match_predicate, int32_compare_func,
            int32_copy_predicate, int32_free_pdata, int32_to_string,
            int32_predicate_equal
        },
        {
            QOF_TYPE_INT64, int64_match_predicate, int64_compare_func,
            int64_copy_predicate, int64_free_pdata, int64_to_string,
            int64_predicate_equal
        },
        {
            QOF_TYPE_DOUBLE, double_match_predicate, double_compare_func,
            double_copy_predicate, double_free_pdata, double_to_string,
            double_predicate_equal
        },
        {
            QOF_TYPE_BOOLEAN, boolean_match_predicate, boolean_compare_func,
            boolean_copy_predicate, boolean_free_pdata, boolean_to_string,
            boolean_predicate_equal
        },
        {
            QOF_TYPE_CHAR, char_match_predicate, char_compare_func,
            char_copy_predicate, char_free_pdata, char_to_string,
            char_predicate_equal
        },
        {
            QOF_TYPE_COLLECT, collect_match_predicate, collect_compare_func,
            collect_copy_predicate, collect_free_pdata, NULL,
            collect_predicate_equal
        },
        {
            QOF_TYPE_CHOICE, choice_match_predicate, NULL,
            choice_copy_predicate, choice_free_pdata, NULL, choice_predicate_equal
        },
    };

    /* Register the known data types */
    for (i = 0; i < (sizeof(knownTypes) / sizeof(*knownTypes)); i++)
    {
        qof_query_register_core_object (knownTypes[i].name,
                                        knownTypes[i].pred,
                                        knownTypes[i].comp,
                                        knownTypes[i].copy,
                                        knownTypes[i].pd_free,
                                        knownTypes[i].toString,
                                        knownTypes[i].pred_equal);
    }
}

static QueryPredicateCopyFunc
qof_query_copy_predicate (QofType type)
{
    QueryPredicateCopyFunc rc;
    g_return_val_if_fail (type, NULL);
    rc = reinterpret_cast<QueryPredicateCopyFunc>(g_hash_table_lookup (copyTable, type));
    return rc;
}

static QueryPredDataFree
qof_query_predicate_free (QofType type)
{
    g_return_val_if_fail (type, NULL);
    return reinterpret_cast<QueryPredDataFree>(g_hash_table_lookup (freeTable, type));
}

/********************************************************************/
/* PUBLISHED API FUNCTIONS */

void qof_query_core_init (void)
{
    /* Only let us initialize once */
    if (initialized) return;
    initialized = TRUE;

    /* Create the tables */
    predTable = g_hash_table_new (g_str_hash, g_str_equal);
    cmpTable = g_hash_table_new (g_str_hash, g_str_equal);
    copyTable = g_hash_table_new (g_str_hash, g_str_equal);
    freeTable = g_hash_table_new (g_str_hash, g_str_equal);
    toStringTable = g_hash_table_new (g_str_hash, g_str_equal);
    predEqualTable = g_hash_table_new (g_str_hash, g_str_equal);

    init_tables ();
}

void qof_query_core_shutdown (void)
{
    if (!initialized) return;
    initialized = FALSE;

    g_hash_table_destroy (predTable);
    g_hash_table_destroy (cmpTable);
    g_hash_table_destroy (copyTable);
    g_hash_table_destroy (freeTable);
    g_hash_table_destroy (toStringTable);
    g_hash_table_destroy (predEqualTable);
}

QofQueryPredicateFunc
qof_query_core_get_predicate (QofType type)
{
    g_return_val_if_fail (type, NULL);
    return reinterpret_cast<QofQueryPredicateFunc>(g_hash_table_lookup (predTable, type));
}

QofCompareFunc
qof_query_core_get_compare (QofType type)
{
    g_return_val_if_fail (type, NULL);
    return reinterpret_cast<QofCompareFunc>(g_hash_table_lookup (cmpTable, type));
}

void
qof_query_core_predicate_free (QofQueryPredData *pdata)
{
    QueryPredDataFree free_fcn;

    g_return_if_fail (pdata);
    g_return_if_fail (pdata->type_name);

    free_fcn = qof_query_predicate_free (pdata->type_name);
    free_fcn (pdata);
}

QofQueryPredData *
qof_query_core_predicate_copy (const QofQueryPredData *pdata)
{
    QueryPredicateCopyFunc copy;

    g_return_val_if_fail (pdata, NULL);
    g_return_val_if_fail (pdata->type_name, NULL);

    copy = qof_query_copy_predicate (pdata->type_name);
    return (copy (pdata));
}

char *
qof_query_core_to_string (QofType type, gpointer object,
                          QofParam *getter)
{
    QueryToString toString;

    g_return_val_if_fail (type, NULL);
    g_return_val_if_fail (object, NULL);
    g_return_val_if_fail (getter, NULL);

    toString = reinterpret_cast<QueryToString>(g_hash_table_lookup (toStringTable, type));
    g_return_val_if_fail (toString, NULL);

    return toString (object, getter);
}

gboolean
qof_query_core_predicate_equal (const QofQueryPredData *p1, const QofQueryPredData *p2)
{
    QueryPredicateEqual pred_equal;

    if (p1 == p2) return TRUE;
    if (!p1 || !p2) return FALSE;

    if (p1->how != p2->how) return FALSE;
    if (g_strcmp0 (p1->type_name, p2->type_name)) return FALSE;

    pred_equal = reinterpret_cast<QueryPredicateEqual>(g_hash_table_lookup (predEqualTable, p1->type_name));
    g_return_val_if_fail (pred_equal, FALSE);

    return pred_equal (p1, p2);
}
