/********************************************************************\
 * qofquery-serialize.c -- Convert QofQuery to XML                  *
 * Copyright (C) 2001,2002,2004 Linas Vepstas <linas@linas.org>     *
 *                                                                  *
 * This program is free software; you can redistribute it and/or    *
 * modify it under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation; either version 2 of   *
 * the License, or (at your option) any later version.              *
 *                                                                  *
 * This program is distributed in the hope that it will be useful,  *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 * GNU General Public License for more details.                     *
 *                                                                  *
 * You should have received a copy of the GNU General Public License*
 * along with this program; if not, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
 *                                                                  *
\********************************************************************/

#include "config.h"

#include "qofquery-serialize.h"
#include "qofquery-p.h"
#include "qofquerycore-p.h"
#include "kvp_frame.h"

/* NOTE: Development of this idea has ceased and this file is
no longer included in the QOF library. It remains in CVS for now.*/

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

#define PUT_STR(TOK,VAL) {                           \
   xmlNodePtr node;                                  \
   const char * str = (VAL);                         \
   if (str && 0 != str[0])                           \
   {                                                 \
      node = xmlNewNode (NULL, TOK);                 \
      xmlNodeAddContent(node, str);                  \
      xmlAddChild (topnode, node);                   \
   }                                                 \
}

#define PUT_INT32(TOK,VAL) {                         \
   xmlNodePtr node;                                  \
   char buff[80];                                    \
   g_snprintf (buff, sizeof(buff), "%d", (VAL));     \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, buff);                    \
   xmlAddChild (topnode, node);                      \
}

#define PUT_INT64(TOK,VAL) {                         \
   xmlNodePtr node;                                  \
   char buff[80];                                    \
   g_snprintf (buff, sizeof(buff), "%" G_GINT64_FORMAT, (VAL));   \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, buff);                    \
   xmlAddChild (topnode, node);                      \
}

#define PUT_DBL(TOK,VAL) {                           \
   xmlNodePtr node;                                  \
   char buff[80];                                    \
   g_snprintf (buff, sizeof(buff), "%.18g", (VAL));  \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, buff);                    \
   xmlAddChild (topnode, node);                      \
}

#define PUT_GUID(TOK,VAL) {                          \
   xmlNodePtr node;                                  \
   char buff[80];                                    \
   guid_to_string_buff ((VAL), buff);                \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, buff);                    \
   xmlAddChild (topnode, node);                      \
}

#define PUT_DATE(TOK,VAL) {                          \
   xmlNodePtr node;                                  \
   char buff[80];                                    \
   gnc_timespec_to_iso8601_buff ((VAL), buff);       \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, buff);                    \
   xmlAddChild (topnode, node);                      \
}

#define PUT_NUMERIC(TOK,VAL) {                       \
   xmlNodePtr node;                                  \
   char *str;                                        \
   str = gnc_numeric_to_string (VAL);                \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, str);                     \
   g_free (str);                                     \
   xmlAddChild (topnode, node);                      \
}

#define PUT_BOOL(TOK,VAL) {                          \
   xmlNodePtr node;                                  \
   gboolean boll = (VAL);                            \
   node = xmlNewNode (NULL, TOK);                    \
   if (boll) {                                       \
      xmlNodeAddContent(node, "T");                  \
   } else {                                          \
      xmlNodeAddContent(node, "F");                  \
   }                                                 \
   xmlAddChild (topnode, node);                      \
}

#define PUT_HOW(TOK,VAL,A,B,C,D,E,F) {               \
   xmlNodePtr node;                                  \
   const char * str = "EQUAL";                       \
   switch (VAL)                                      \
   {                                                 \
      case QOF_COMPARE_##A: str = #A; break;         \
      case QOF_COMPARE_##B: str = #B; break;         \
      case QOF_COMPARE_##C: str = #C; break;         \
      case QOF_COMPARE_##D: str = #D; break;         \
      case QOF_COMPARE_##E: str = #E; break;         \
      case QOF_COMPARE_##F: str = #F; break;         \
   }                                                 \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, str);                     \
   xmlAddChild (topnode, node);                      \
}

#define PUT_MATCH2(TOK,VAL,PFX,A,B) {                \
   xmlNodePtr node;                                  \
   const char * str = #A;                            \
   switch (VAL)                                      \
   {                                                 \
      case QOF_##PFX##_##A: str = #A; break;         \
      case QOF_##PFX##_##B: str = #B; break;         \
   }                                                 \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, str);                     \
   xmlAddChild (topnode, node);                      \
}

#define PUT_MATCH3(TOK,VAL,PFX,A,B,C) {              \
   xmlNodePtr node;                                  \
   const char * str = #A;                            \
   switch (VAL)                                      \
   {                                                 \
      case QOF_##PFX##_##A: str = #A; break;         \
      case QOF_##PFX##_##B: str = #B; break;         \
      case QOF_##PFX##_##C: str = #C; break;         \
   }                                                 \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, str);                     \
   xmlAddChild (topnode, node);                      \
}

#define PUT_MATCH5(TOK,VAL,PFX,A,B,C,D,E) {          \
   xmlNodePtr node;                                  \
   const char * str = #A;                            \
   switch (VAL)                                      \
   {                                                 \
      case QOF_##PFX##_##A: str = #A; break;         \
      case QOF_##PFX##_##B: str = #B; break;         \
      case QOF_##PFX##_##C: str = #C; break;         \
      case QOF_##PFX##_##D: str = #D; break;         \
      case QOF_##PFX##_##E: str = #E; break;         \
   }                                                 \
   node = xmlNewNode (NULL, TOK);                    \
   xmlNodeAddContent(node, str);                     \
   xmlAddChild (topnode, node);                      \
}

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

static void
qof_kvp_value_to_xml (KvpValue *kval, xmlNodePtr topnode)
{
	KvpValueType kvt = kvp_value_get_type (kval);

	switch (kvt)
	{
		case KVP_TYPE_GINT64:
			PUT_INT64 ("qofquery:int64", kvp_value_get_gint64(kval));
			break;
		case KVP_TYPE_DOUBLE:
			PUT_DBL ("qofquery:double", kvp_value_get_double(kval));
			break;
		case KVP_TYPE_NUMERIC:
			PUT_NUMERIC ("qofquery:numeric", kvp_value_get_numeric(kval));
			break;
		case KVP_TYPE_GUID:
			PUT_GUID ("qofquery:guid", kvp_value_get_guid(kval));
			break;
		case KVP_TYPE_STRING:
			PUT_STR ("qofquery:string", kvp_value_get_string(kval));
			break;
		case KVP_TYPE_TIMESPEC:
			PUT_DATE ("qofquery:date", kvp_value_get_timespec(kval));
			break;
		case KVP_TYPE_BINARY:
		case KVP_TYPE_GLIST:
		case KVP_TYPE_FRAME:
			// XXX don't know how to support these.
			break;
	}
}

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

static xmlNodePtr
qof_query_pred_data_to_xml (QofQueryPredData *pd)
{
	GList *n;
	GSList *ns;
	xmlNodePtr topnode;
	query_guid_t pdata_g;
	query_string_t pdata_s;
	query_numeric_t pdata_n;
	query_kvp_t pdata_k;
	query_date_t pdata_d;
	query_int64_t pdata_i64;
	query_int32_t pdata_i32;
	query_double_t pdata_db;
	query_boolean_t pdata_bool;
	query_char_t pdata_c;
	
	if (!safe_strcmp (pd->type_name, QOF_TYPE_GUID))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-guid");
		/* GUID Predicate doesn't do a PUT_HOW */

		pdata_g = (query_guid_t) pd;
		PUT_MATCH5("qofquery:guid-match", pdata_g->options, 
		                GUID_MATCH, ANY, ALL, NONE, NULL, LIST_ANY);

		for (n = pdata_g->guids; n; n = n->next)
		{
			PUT_GUID ("qofquery:guid", n->data);
		}
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_STRING))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-string");
		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);

		pdata_s = (query_string_t) pd;
		PUT_MATCH2("qofquery:string-match", pdata_s->options,
                       STRING_MATCH, NORMAL, CASEINSENSITIVE);
		PUT_BOOL ("qofquery:is-regex", pdata_s->is_regex);
		PUT_STR ("qofquery:string", pdata_s->matchstring);
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_NUMERIC))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-numeric");
		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);

		pdata_n = (query_numeric_t) pd;
		PUT_MATCH3("qofquery:numeric-match", pdata_n->options,
		                 NUMERIC_MATCH, DEBIT, CREDIT, ANY);
		
		PUT_NUMERIC ("qofquery:numeric", pdata_n->amount);
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_KVP))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-kvp");
		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);

		pdata_k = (query_kvp_t) pd;
		for (ns=pdata_k->path; ns; ns=ns->next)
		{
			PUT_STR ("qofquery:kvp-path", ns->data);
		}
		qof_kvp_value_to_xml (pdata_k->value, topnode);
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_DATE))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-date");
		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);

		pdata_d = (query_date_t) pd;
		
		PUT_MATCH2("qofquery:date-match", pdata_d->options,
		                 DATE_MATCH, NORMAL, DAY);

		PUT_DATE ("qofquery:date", pdata_d->date);
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_INT64))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-int64");
		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);

		pdata_i64 = (query_int64_t) pd;
		PUT_INT64 ("qofquery:int64", pdata_i64->val);
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_INT32))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-int32");
		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);

		pdata_i32 = (query_int32_t) pd;
		
		PUT_INT32 ("qofquery:int32", pdata_i32->val);
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_DOUBLE))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-double");
		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);

		pdata_db = (query_double_t) pd;
		
		PUT_DBL ("qofquery:double", pdata_db->val);
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_BOOLEAN))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-boolean");
		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);

		pdata_bool = (query_boolean_t) pd;
		
		PUT_BOOL ("qofquery:boolean", pdata_bool->val);
		return topnode;
	}
	if (!safe_strcmp (pd->type_name, QOF_TYPE_CHAR))
	{
		topnode = xmlNewNode (NULL, "qofquery:pred-char");
		/* There is no PUT_HOW for char-match */
		pdata_c = (query_char_t) pd;
		
		PUT_MATCH2("qofquery:char-match", pdata_c->options,
		                 CHAR_MATCH, ANY, NONE);
		
		PUT_STR ("qofquery:char-list", pdata_c->char_list);
		return topnode;
	}
	return NULL;
}

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

static xmlNodePtr
qof_query_param_path_to_xml (GSList *param_path)
{
	xmlNodePtr topnode;
	GSList *n;
	QofIdTypeConst path;
	
	n = param_path;
	topnode = xmlNewNode (NULL, "qofquery:param-path");
	for ( ; n; n=n->next)
	{
		path = n->data;
		if (!path) continue;
		PUT_STR ("qofquery:param", path);
	}
	return topnode;
}

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

static xmlNodePtr
qof_query_one_term_to_xml (QofQueryTerm *qt)
{
	xmlNodePtr node;
	xmlNodePtr term;
	xmlNodePtr topnode;
	gboolean invert;
	GSList *path;
	QofQueryPredData *pd;

	invert = qof_query_term_is_inverted (qt);
	term = xmlNewNode (NULL, "qofquery:term");
	topnode = term;
	path = qof_query_term_get_param_path (qt);
	pd = qof_query_term_get_pred_data (qt);
	if (invert)
	{
		/* inverter becomes new top mode */
		topnode = xmlNewNode (NULL, "qofquery:invert");
		xmlAddChild (term, topnode);
	}

	node = qof_query_param_path_to_xml (path);
	if (node) xmlAddChild (topnode, node);

	node = qof_query_pred_data_to_xml (pd);
	if (node) xmlAddChild (topnode, node);

	return term;
}

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

static xmlNodePtr
qof_query_and_terms_to_xml (GList *and_terms)
{
	xmlNodePtr terms;
	GList *n;
	QofQueryTerm *qt;
	xmlNodePtr t;
	
	terms = xmlNewNode (NULL, "qofquery:and-terms");
	n = and_terms;
	for ( ; n; n=n->next)
	{
		qt = n->data;
		if (!qt) continue;

		t = qof_query_one_term_to_xml (n->data);
		if (t) xmlAddChild (terms, t);
	}
	return terms;
}

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

static xmlNodePtr
qof_query_terms_to_xml (QofQuery *q)
{
	xmlNodePtr terms;
	GList *n;
	xmlNodePtr andt;
	
	terms = NULL;
	n = qof_query_get_terms (q);
	if (!n) return NULL;
	terms = xmlNewNode (NULL, "qofquery:or-terms");

	for ( ; n; n=n->next)
	{
		andt = qof_query_and_terms_to_xml (n->data);
		if (andt) xmlAddChild (terms, andt);
	}
	return terms;
}

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

static xmlNodePtr
qof_query_sorts_to_xml (QofQuery *q)
{
	QofQuerySort *s[3];
	xmlNodePtr sortlist;
	GSList *plist;
	xmlNodePtr sort;
	xmlNodePtr topnode;
	gboolean increasing;
	gint opt;
	xmlNodePtr pl;
	int i;
	
	qof_query_get_sorts (q, &s[0], &s[1], &s[2]);

	if (NULL == s[0]) return NULL;

	sortlist = xmlNewNode (NULL, "qofquery:sort-list");
	for (i=0; i<3; i++)
	{
		if (NULL == s[i]) continue;

		plist = qof_query_sort_get_param_path (s[i]);
		if (!plist) continue;

		sort = xmlNewNode (NULL, "qofquery:sort");
		xmlAddChild (sortlist, sort);

		topnode = sort;

		increasing = qof_query_sort_get_increasing (s[i]);
		PUT_STR ("qofquery:order", increasing ? "DESCENDING" : "ASCENDING");

		opt = qof_query_sort_get_sort_options (s[i]);
		PUT_INT32 ("qofquery:options", opt);

		pl = qof_query_param_path_to_xml (plist);
		if (pl) xmlAddChild (sort, pl);
	}

	return sortlist;
}

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

static void
do_qof_query_to_xml (QofQuery *q, xmlNodePtr topnode)
{
	QofIdType search_for;
	xmlNodePtr terms;
	xmlNodePtr sorts;
	gint max_results;
	
	search_for = qof_query_get_search_for (q);
	PUT_STR ("qofquery:search-for", search_for);

	terms = qof_query_terms_to_xml(q);
	if (terms) xmlAddChild (topnode, terms);

	sorts = qof_query_sorts_to_xml (q);
	if (sorts) xmlAddChild (topnode, sorts);

	max_results = qof_query_get_max_results (q);
	PUT_INT32 ("qofquery:max-results", max_results);
}

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

xmlNodePtr
qof_query_to_xml (QofQuery *q)
{
	xmlNodePtr topnode;
	xmlNodePtr node;
	xmlNsPtr   ns;

	topnode = xmlNewNode(NULL, "qof:qofquery");
	xmlSetProp(topnode, "version", "1.0.1");

	// XXX path to DTD is wrong
	// ns = xmlNewNs (topnode, "file:" "/usr/share/lib" "/qofquery.dtd", "qof");

	do_qof_query_to_xml (q, topnode);

	return topnode;
}

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

#ifdef UNIT_TEST

#include <stdio.h>
#include <qof/qofsql.h>

int main (int argc, char * argv[])
{
	QofQuery *q;
	QofSqlQuery *sq;
	xmlDocPtr doc;
	xmlNodePtr topnode;
	xmlChar *xbuf;
	int bufsz;
	xmlOutputBufferPtr xbuf;
	
	qof_query_init();
	qof_object_initialize ();

	static QofParam params[] = {
		{ "adate",  QOF_TYPE_DATE, NULL, NULL},
		{ "aint",   QOF_TYPE_INT32, NULL, NULL},
		{ "aint64", QOF_TYPE_INT64, NULL, NULL},
		{ "astr",   QOF_TYPE_STRING, NULL, NULL},
		{ NULL },
	};

	qof_class_register ("GncABC", NULL, params);
	sq = qof_sql_query_new();

	qof_sql_query_parse (sq, 
	    "SELECT * from GncABC WHERE aint = 123 "
	    "or not astr=\'asdf\' "
	    "and aint64 = 9876123456789;");
	// qof_sql_query_parse (sq, "SELECT * from GncABC;");
	q = qof_sql_query_get_query (sq);

	qof_query_print (q);

	doc = doc = xmlNewDoc("1.0");
	topnode = qof_query_to_xml (q);
	xmlDocSetRootElement(doc,topnode);

	xmlDocDumpFormatMemory (doc, &xbuf, &bufsz, 1);

	printf ("%s\n", xbuf);
	xmlFree (xbuf);
	xmlFreeDoc(doc);

#if 0
printf ("duude\n");
	// xmlOutputBufferPtr xbuf = xmlAllocOutputBuffer (enc);
	xbuf = xmlOutputBufferCreateFile (stdout, NULL);
printf ("duude\n");

	xbuf = xmlOutputBufferCreateFd (1, NULL);
printf ("duude\n");
	xmlNodeDumpOutput (xbuf, NULL, topnode, 99, 99, "iso-8859-1");
	// xmlElemDump (stdout, NULL, topnode);
#endif

	return 0;
}

#endif /* UNIT_TEST */

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