/**
 * This is part of an XML patch/diff library.
 *
 * Copyright (C) 2005 Nokia Corporation.
 *
 * Contact: Jari Urpalainen <jari.urpalainen@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * $Date:$
 */

#include "config.h"

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <signal.h>

#include <ctype.h>
#include <unistd.h>

#include <glib.h>

#include <libxml/tree.h>
#include <libxml/xpath.h>

#include "xml_diff.h"

#define STRCMP(x,y) \
  strcmp ((char*)x, (char*)y)

#define STRNCMP(x,y,z) \
  strncmp ((char*)x, (char*)y, z)

typedef enum
{
    ADD  = 0,
    REPLACE,
    DELETE,
    SKIP
} method_e;

typedef enum
{
    INIT    = 0,
    BEFORE  = 1 << 1,
    AFTER   = 1 << 2,
    PREPEND = 1 << 3
} ws_e;

typedef struct _diff_request_s
{
    struct _diff_request_s *nextAdd;

    method_e method;         /* add, remove, replace */
    xmlNodePtr pNodeLocPath, /* the node to be located */
	       pNode,        /* added element or attribute */
	       pNodeDeleted; /* replaced or deleted element */

    xmlElementType type;
    ws_e ws, addPos;
    int index;
    gchar *pch;
} diff_request_t;

typedef struct _diff_level_s
{
    int *pc;
    int cAlloc;
} diff_level_t;

typedef enum
{
    NONE   = 0,
    NAME   = 1 << 1,
    VALUE  = 1 << 2,
    NS     = 1 << 3,
    ATTR   = 1 << 4,
    CHILD  = 1 << 5,
    NS_DEC = 1 << 6,
    NS_DEF = 1 << 7,
    TYPE   = 1 << 8,
    MVALUE = 1 << 9
} diff_e;

/** add a new patch request to the glib slist */
static diff_request_t*
diff_add_request (method_e method,
		  xmlNodePtr pNodeLocPath,
		  ws_e add,
		  int index,
		  xmlNodePtr pNode,
		  xmlNodePtr pNodeDeleted,
		  const char *pch,
		  GSList **ppReq)
{
    diff_request_t *p;
    GSList *last;

    if (ppReq == NULL)
	return NULL;

    p = g_new0 (diff_request_t, 1);

    /* if add method and adjacent sibling exists add a reference to prev patch */
    if (method == ADD) {
	last = g_slist_last (*ppReq);
	if (last) {
	    diff_request_t *pLast = last->data;

	    if (pLast->method == ADD && pNode->prev == pLast->pNode)
		pLast->nextAdd = p;
	}
    }
    p->index        = index;
    p->ws           = INIT;
    p->addPos       = add;
    p->nextAdd      = NULL;
    p->method       = method;
    p->pNodeLocPath = pNodeLocPath;
    p->pNode        = pNode;
    p->type         = pNode->type;
    p->pNodeDeleted = pNodeDeleted;
    p->pch          = (gchar*)pch;

    *ppReq = g_slist_append (*ppReq, p);
    return p;
}

/** store elem index at some tree level */
static void
add_level (diff_level_t *p, int index, int cElems)
{
    if (p->cAlloc <= index) {
	p->cAlloc = index + 10;
	p->pc = realloc (p->pc,  p->cAlloc * sizeof(int));
    }
    p->pc[index] = cElems;
}

/** free level */
static void
free_levels (diff_level_t *p)
{
    free (p->pc);
    p->pc = NULL;
}

/** get next sibling, skip combinations of text & cdata nodes */
static xmlNodePtr
get_next (xmlNodePtr a)
{
    if (!a)
	return a;

    if (a->type == XML_TEXT_NODE || a->type == XML_CDATA_SECTION_NODE) {
	for (a = a->next; a; a = a->next) {
	    if (a->type != XML_TEXT_NODE && a->type != XML_CDATA_SECTION_NODE)
		break;
	}
	return a;
    }
    else {
	return a->next;
    }
}

#if 0
/** get prev sibling, skip combinations of text & cdata nodes*/
static xmlNodePtr
get_prev (xmlNodePtr a)
{
    if (!a)
	return a;

    if (a->type == XML_TEXT_NODE || a->type == XML_CDATA_SECTION_NODE) {
	for (a = a->prev; a; a = a->prev) {
	    if (a->type != XML_TEXT_NODE && a->type != XML_CDATA_SECTION_NODE)
		break;
	}
	return a;
    }
    else {
	return a->prev;
    }
}
#endif

/** last node of this type ? */
static int
last_of_this_type (xmlNodePtr n)
{
    xmlNodePtr next = NULL;

    for (next = get_next (n); next; next = next->next) {
	if (next->type == n->type ||
	    (n->type == XML_TEXT_NODE && next->type == XML_CDATA_SECTION_NODE) ||
	    (next->type == XML_TEXT_NODE && n->type == XML_CDATA_SECTION_NODE)) {
	    break;
	}
    }
    return next ? FALSE : TRUE;
}

/** get positional selector value */
static gchar *
get_sel (xmlNodePtr parent, diff_level_t *p, int index, xmlNodePtr n, int last)
{
    int i = 0, *pc = p->pc ? &p->pc[1] : NULL, j, cNode = index;

    xmlNodePtr *aNode = g_malloc (cNode * sizeof (*aNode)), node = parent;
    gchar *t = NULL, *pch = NULL;

    for (i = 0; node && node->type != XML_DOCUMENT_NODE; node = node->parent) {
	if (i >= cNode) {
	    cNode += 10;
	    aNode = g_realloc (aNode, cNode * sizeof (*aNode));
	}
	aNode[i++] = node;
    }
    if (i != index)
	return NULL;

    for (j = i - 1, i = 0; i < index; i++, j--) {
	if (pc[i] == 1 && last_of_this_type (aNode[j]))
	    pc[i] = 0;

	t = pch;
	pch = pc[i] ? g_strdup_printf ("%s/*[%d]", t ? t : "", pc[i]) :
		      g_strdup_printf ("%s%s*", t ? t : "", t ? "/" : "");
	g_free (t);
    }
    if (n && last != 0) {
	t = pch;

	if (last == 1 && last_of_this_type (n))
	    last = 0;

	if (n->type == XML_ELEMENT_NODE)
	    pch = last ? g_strdup_printf ("%s/*[%d]", t ? t : "", last) :
			 g_strdup_printf ("%s%s*", t ? t : "", t ? "/" : "");
	else if (n->type == XML_TEXT_NODE || n->type == XML_CDATA_SECTION_NODE)
	    pch = last ? g_strdup_printf ("%s/text()[%d]", t ? t : "", last) :
			 g_strdup_printf ("%s/text()", t ? t : "");
	else if (n->type == XML_COMMENT_NODE)
	    pch = last ? g_strdup_printf ("%s/comment()[%d]", t ? t : "", last) :
			 g_strdup_printf ("%s/comment()", t ? t : "");
	else if (n->type == XML_PI_NODE)
	    pch = last ? g_strdup_printf ("%s/processing-instruction('%s')[%d]", t ? t : "", n->name, last) :
			 g_strdup_printf ("%s/processing-instruction('%s')", t ? t : "", n->name);
	else
	    t = NULL;
	g_free (t);
    }
    g_free (aNode);

    return pch;
}

#define CMP_STR(a, b)			\
    if (!a && !b)			\
	return 0;			\
    else if (a && !b)			\
	return -1;			\
    else if (!a && b)			\
	return 1;			\
    else {				\
	int rc;				\
	if ((rc = STRCMP (a, b)))	\
	    return rc;			\
    }

static int diff_cmp_nodes (xmlNodePtr a, xmlNodePtr b, diff_e *diff);

/** compare attributes & values, unordered comparison */
static int
diff_cmp_attr (xmlAttrPtr attr_a, xmlAttrPtr attr_b, xmlNodePtr nodeLocPath, int c,
	       int indLevel,
	       diff_level_t *pLevel, GSList **ppReq)
{
    xmlAttrPtr a, b;
    int rc = 0, rc_single;
    GSList *list = NULL;

    for (b = attr_b; b; b = b->next ) {
	diff_e d;

	for (a = attr_a; a; a = a->next) {
	    rc_single = diff_cmp_nodes ((xmlNodePtr)a, (xmlNodePtr)b, &d);

	    if (!rc_single) {
		list = g_slist_append (list, a);
		break;
	    }
	    else if (d == VALUE) {
		list = g_slist_append (list, a);

		if (nodeLocPath)
		    diff_add_request (REPLACE, nodeLocPath, INIT, c, (xmlNodePtr)b, (xmlNodePtr)a,
				      get_sel (nodeLocPath, pLevel, indLevel, NULL, 0),
				      ppReq);
		rc++;
		break;
	    }
	}
	if (!a) {
	    rc++;

	    if (nodeLocPath)
		diff_add_request (ADD, nodeLocPath, INIT, c, (xmlNodePtr)b, NULL,
				  get_sel (nodeLocPath, pLevel, indLevel, NULL, 0),
				  ppReq);
	}
    }
    for (a = attr_a; a; a = a->next) {
	if (!g_slist_find (list, a)) {
	    rc++;

	    if (nodeLocPath)
		// DELETE attr_a
		diff_add_request (DELETE, nodeLocPath, INIT, c, (xmlNodePtr)a, (xmlNodePtr)a,
				  get_sel (nodeLocPath, pLevel, indLevel, NULL, 0),
				  ppReq);
	}
    }
    g_slist_free (list);
    return rc;
}

/** ns declarations, unordered collection */
static int
diff_cmp_ns (xmlNsPtr ns_a, xmlNsPtr ns_b, xmlNodePtr nodeLocPath, int c,
	     int indLevel, diff_level_t *pLevel, GSList **ppReq)
{
    xmlNsPtr a, b;
    int rc = 0;
    GSList *list = NULL;

    for (b = ns_b; b; b = b->next ) {
	for (a = ns_a; a; a = a->next) {
	    if ((!a->prefix && !b->prefix) ||
		(a->prefix && b->prefix && !STRCMP(a->prefix, b->prefix))) {
		if (a->href && b->href && !STRCMP(a->href, b->href)) {
		    list = g_slist_append (list, a);
		    break;
		}
		else {
		    list = g_slist_append(list, a);

		    if (nodeLocPath)
			diff_add_request (REPLACE, nodeLocPath, INIT, c, (xmlNodePtr)b, (xmlNodePtr)a,
					  get_sel (nodeLocPath, pLevel, indLevel, NULL, 0),
					  ppReq);
		    rc++;
		    break;
		}
	    }
	}
	if (!a) {
	    rc++;

	    if (nodeLocPath)
		diff_add_request (ADD, nodeLocPath, INIT, c, (xmlNodePtr)b, NULL,
				  get_sel (nodeLocPath, pLevel, indLevel, NULL, 0),
				  ppReq);
	}
    }
    for (a = ns_a; a; a = a->next) {
	if (!g_slist_find (list, a)) {
	    rc++;

	    if (nodeLocPath)
		// DELETE attr_a
		diff_add_request (DELETE, nodeLocPath, INIT, c, (xmlNodePtr)a, (xmlNodePtr)a,
				  get_sel (nodeLocPath, pLevel, indLevel, NULL, 0),
				  ppReq);
	}
    }
    g_slist_free (list);
    return rc;
}

/** simple node comparison */
static int
diff_cmp_nodes (xmlNodePtr a, xmlNodePtr b, diff_e *diff)
{
    int rc, rcNs;
    const xmlChar *pa, *pb;

    *diff = NONE;

    if (a && !b)
	return -1;
    else if (!a && b)
	return 1;
    else if (!a && !b)
	return 0;

    if (a->type != b->type) {
	*diff = TYPE;
	return 1;
    }

#define CMP(x, y, d)			\
    if (!x && !y) 			\
	;				\
    else if (x && !y) {			\
	*diff = d;			\
	return -1;			\
    }					\
    else if (!x && y) {			\
	*diff = d;			\
	return 1;			\
    }					\
    else if ((rc = STRCMP (x, y))) {	\
	*diff = d;			\
	return rc;			\
    }

    CMP (a->name, b->name, NAME);
    pa = a->ns ? a->ns->prefix : NULL;
    pb = b->ns ? b->ns->prefix : NULL;
    CMP (pa, pb, NS);

    if (a->type == XML_ATTRIBUTE_NODE) {
	const unsigned char *ca = a->children ? a->children->content : NULL;
	const unsigned char *cb = b->children ? b->children->content : NULL;

	CMP (ca, cb, VALUE);
	return 0;
    }
    else if (a->type == XML_TEXT_NODE ||
             a->type == XML_CDATA_SECTION_NODE) {
	xmlNodePtr aNext, bNext;

	CMP (a->content, b->content, VALUE);

	for (aNext = a->next, bNext = b->next; aNext || bNext;
	     aNext = aNext->next, bNext = bNext->next) {

	     if ((!aNext || (aNext->type != XML_CDATA_SECTION_NODE && aNext->type != XML_TEXT_NODE)) &&
		 (!bNext || (bNext->type != XML_CDATA_SECTION_NODE && bNext->type != XML_TEXT_NODE)))
		return 0;

	     if (!aNext || !bNext || aNext->type != bNext->type) {
		*diff = MVALUE;
		return 1;
	     }
	     CMP (aNext->content, bNext->content, MVALUE);
	}
	return 0;
    }
    else if (a->type == XML_COMMENT_NODE ||
	     a->type == XML_PI_NODE) {
	CMP (a->content, b->content, VALUE);
	return 0;
    }
    rc = diff_cmp_attr (a->properties, b->properties, 0, 0, 0, NULL, NULL);
    if (rc)
	*diff |= ATTR;

    rcNs = diff_cmp_ns (a->nsDef, b->nsDef, 0, 0, 0, NULL, NULL);
    if (rcNs)
	*diff |= NS_DEC;
    return rc + rcNs;
}
#undef CMP


/** remove list entries */
static void
remove_list_end (GSList *last, GSList **ppReq)
{
    GSList *l = last ? last->next : *ppReq;

    for ( ; l; l = l->next) {
	diff_request_t *p = l->data;

	g_free (p->pch);
	g_free (p);
    }
    if (last) {
	g_slist_free (last->next);
	last->next = NULL;
    }
    else {
	g_slist_free (*ppReq);
	*ppReq = NULL;
    }
}

#define COUNT(a)								\
    a->type == XML_ELEMENT_NODE ? cElem :					\
    (a->type == XML_TEXT_NODE || a->type == XML_CDATA_SECTION_NODE) ? cText :	\
    a->type == XML_COMMENT_NODE ? cComm :					\
    a->type == XML_PI_NODE ? cPI : 0

#define UPDATE(b)			\
    fTextNode = FALSE;			\
    if (b) {				\
    switch (b->type) {			\
    case XML_ELEMENT_NODE:		\
	cElem++;			\
	break;				\
					\
    case XML_TEXT_NODE:			\
    case XML_CDATA_SECTION_NODE:	\
	fTextNode = TRUE;		\
	cText++;			\
	break;				\
					\
    case XML_PI_NODE:			\
	cPI++;				\
	break;				\
					\
    case XML_COMMENT_NODE:		\
	cComm++;			\
	break;				\
					\
    default:				\
	break;				\
    }					\
    }

/** very simple and stupid comparison logic of the tree */
static int
diff_cmp_steps_children (int fOptimize,
			 xmlNodePtr nodeParentFrom,
			 xmlNodePtr nodeParentTo,
			 diff_e *diff,
			 int indLevel,
			 diff_level_t *pLevel,
			 GSList **ppReq,
			 int *pcChanges)
{
    int rc;
    xmlNodePtr a, b;
    int cElem, cPI, cComm, cText, fTextNode = FALSE;
    GSList *last = fOptimize == XML_DIFF_SIZE_OPTIMIZE ? g_slist_last (*ppReq) : NULL;
    int cInit = fOptimize == XML_DIFF_SIZE_OPTIMIZE ? g_slist_length (*ppReq) : 0;

    cElem = cPI = cComm = cText = 1;

    add_level (pLevel, ++indLevel, cElem);

    for (a = nodeParentFrom->children,
	 b = nodeParentTo->children; a || b; ) {
	diff_e d = { 0 };

	rc = diff_cmp_nodes (a, b, &d);

	if (rc) {
	    *diff |= CHILD;

	    if (a && b && d == VALUE) {
		gchar *pch = get_sel (a->parent, pLevel, indLevel - 1, a, COUNT (a));

		/* never an element type */
		if (a->type == XML_TEXT_NODE && b->type == XML_TEXT_NODE &&
		    !STRNCMP (a->content, b->content, strlen ((char*)a->content))) {
		    diff_add_request (ADD, a, AFTER, COUNT (a), b, a, pch, ppReq);
		}
		else {
		    /* replace node_a content with node_b content */
		    diff_add_request (REPLACE, a, INIT, COUNT (a), b, a, pch, ppReq);
		}
		UPDATE (b)
	    }
	    else if (a && b && d == MVALUE) {
		fTextNode = TRUE;
		/* never an element type
		 * replace node_a content with node_b content */
		diff_add_request (REPLACE, a, INIT, cText, b, a,
				  get_sel (a->parent, pLevel, indLevel - 1, a, cText), ppReq);
		cText++;
	    }
	    else if (a && b && (((d == ATTR && (rc == 1 || indLevel <= 1)) ||
				d == NS_DEC || d == (ATTR | NS_DEC)) &&
				((a->children && b->children) || (!a->children && !b->children)))) {
		GSList *lastAttr = fOptimize == XML_DIFF_SIZE_OPTIMIZE ? g_slist_last (last ? last : *ppReq) : NULL;
		fTextNode = FALSE;

		add_level (pLevel, indLevel, cElem);
		/* attribute changes, a & b elements */
		if ((d & ATTR) == ATTR)
		    diff_cmp_attr (a->properties, b->properties, a, COUNT (a), indLevel, pLevel, ppReq);
		else /* ns declaration changes */
		    diff_cmp_ns (a->nsDef, b->nsDef, a, COUNT (a), indLevel, pLevel, ppReq);

		rc = diff_cmp_steps_children (fOptimize, a, b, &d, indLevel, pLevel, ppReq, pcChanges);
		if (rc)
		    *diff |= CHILD;

		if (*pcChanges) {
		    remove_list_end (lastAttr, ppReq);

		    diff_add_request (REPLACE, a, INIT, COUNT (a), b, a,
				      get_sel (a->parent, pLevel, indLevel - 1, a, COUNT (a)),
				      ppReq);
		}
		cElem++;
	    }
	    else {
		int i = 0, fAdd = b ? TRUE : FALSE;
		xmlNodePtr nA;

		/* update in the root elements ? */
		if (a && a->parent->type == XML_DOCUMENT_NODE && b) {
		    diff_add_request (REPLACE, a, INIT, COUNT (a), b, a,
				      get_sel (a->parent, pLevel, indLevel - 1, a, COUNT (a)),
				      ppReq);
		    UPDATE (b)
		    a = get_next (a);
		    b = get_next (b);
		    continue;
		}
		for (nA = get_next (a); nA; nA = get_next (nA)) {
		    if (i < 6 && !diff_cmp_nodes (nA, b, &d)) {
			fAdd = FALSE;
			break;
		    }
		    i++;
		}
#if 0
		if (REPLACE CASE) {
		    diff_add_request (REPLACE, a, INIT, COUNT(a), b, a,
				      get_sel (a->parent, pLevel, indLevel - 1, a, COUNT(a)),
				      ppReq);
		    UPDATE (b)
		    a = get_next (a);
		    b = get_next (b);

		} else
#endif
		if (fAdd) {
		    /* adding b */

		    if (a)
			diff_add_request (ADD, a, BEFORE, COUNT (a), b, NULL,
					  get_sel (a->parent, pLevel, indLevel - 1, a, COUNT (a)),
					  ppReq);
		    else
			diff_add_request (ADD, nodeParentFrom, INIT, 0, b, NULL,
					  get_sel (nodeParentFrom, pLevel, indLevel - 1, b, 0),
					  ppReq);
		    UPDATE (b)
		    b = get_next (b);
		}
		else {
		    /* delete node A
		     * nasty things will happen if incorrect order with delete,
		     * i.e. text-nodes combine if a node existing betweeen of them, is being deleted */

		    if (a) {
			if (fTextNode && a->next && (a->next->type == XML_TEXT_NODE ||
						     a->next->type == XML_CDATA_SECTION_NODE)) {
			    if (xmlIsBlankNode (a->next)) {
				diff_request_t *diff =
				    diff_add_request (DELETE, a, INIT, COUNT (a), a, a,
						      get_sel (a->parent, pLevel, indLevel - 1, a, COUNT (a)),
						      ppReq);
				diff->ws = AFTER;
				a = get_next (a);
			    }
			    else {
				xmlNodePtr sib = a;

				a = get_next (a);
				diff_add_request (DELETE, a, INIT, COUNT (a), a, a,
						  get_sel (a->parent, pLevel, indLevel - 1, a, COUNT (a)),
						  ppReq);
				diff_add_request (DELETE, sib, INIT, COUNT (sib), sib, sib,
						  get_sel (sib->parent, pLevel, indLevel - 1, sib, COUNT (sib)),
						  ppReq);
			    }
			}
			else {
			    diff_add_request (DELETE, a, INIT, COUNT (a), a, a,
					      get_sel (a->parent, pLevel, indLevel - 1, a, COUNT (a)),
					      ppReq);
			}
			a = get_next (a);
		    }
		}
		continue;
	    }
	}
	else if (a && a->type == XML_ELEMENT_NODE &&
		 b && b->type == XML_ELEMENT_NODE) {
	    fTextNode = FALSE;

	    add_level (pLevel, indLevel, cElem);
	    rc = diff_cmp_steps_children (fOptimize, a, b, &d, indLevel, pLevel, ppReq, pcChanges);
	    if (rc)
		*diff |= CHILD;

	    if (*pcChanges)
		diff_add_request (REPLACE, a, INIT, COUNT (a), b, a,
				  get_sel (a->parent, pLevel, indLevel - 1, a, COUNT (a)),
				  ppReq);
	    cElem++;
	}
	else if (!rc) {
	    UPDATE (b)
	}
	a = get_next (a);
	b = get_next (b);
    }

    /* try size optimization, this is rough but works */
    *pcChanges = FALSE;

    if (fOptimize == XML_DIFF_SIZE_OPTIMIZE) {
	cInit = g_slist_length (*ppReq) - cInit;

	if (indLevel > 1 && cInit > 1) {
	    int cb = 0;
	    GSList *l = last ? last->next : *ppReq;
	    xmlBufferPtr buf;

	    for ( ; l; l = l->next) {
		diff_request_t *p = l->data;

		if (p->method == ADD || p->method == REPLACE) {
		    buf = xmlBufferCreate ();
		    xmlNodeDump (buf, p->pNode->doc, p->pNode, 0, 0);
		    cb += buf->use + strlen (p->pch);
		    xmlBufferFree (buf);
		}
		else if (p->method == DELETE)
		    cb += strlen (p->pch);
	    }
	    buf = xmlBufferCreate ();
	    xmlNodeDump (buf, nodeParentTo->doc, nodeParentTo, 0, 0);

	    if (cb > buf->use) {
		remove_list_end (last, ppReq);
		*pcChanges = TRUE;
	    }
	    xmlBufferFree (buf);
	}
    }
    return *diff == NONE ? 0 : 1;
}

/** if last step has a qualified attribute, check ns declaration existence */
static xmlNsPtr
attr_has_ns (xmlAttrPtr attr, xmlNodePtr nodeChanges)
{
    xmlNsPtr ns, *nsList;
    int i;

    if (!attr->ns)
	return NULL;

    nsList = xmlGetNsList (nodeChanges->doc, nodeChanges);

    for (i = 0; (ns = nsList ? nsList[i] : NULL); i++) {
	if (!STRCMP (ns->href, attr->ns->href) && ns->prefix)
	    break;
    }
    if (!ns) {
	int j;
	char *pch = NULL;

	for (j = 0; ; j++) {
	    int k;

	    pch = j ? g_strdup_printf ("%s%d", attr->ns->prefix, j - 1) :
		      g_strdup ((char*)attr->ns->prefix);

	    /* prefixes within the in-scope list */
	    for (k = 0; (ns = nsList ? nsList[k] : NULL); k++) {
		if (ns->prefix && !STRCMP (pch, ns->prefix))
		    break;
	    }
	    if (ns) {
		g_free (pch);
		continue;
	    }
	    break;
	}
	ns = xmlNewNs (nodeChanges, attr->ns->href, (xmlChar*)pch);
	g_free (pch);
    }
    xmlFree (nsList);
    return ns;
}

/** replaces old ns with new for node and all of it's children */
static void
set_ns_recursively (xmlNodePtr node,
		    const xmlNsPtr old,
		    const xmlNsPtr ns)
{
    for ( ; node; node = node->next) {
	if (node->type == XML_ELEMENT_NODE) {
	    if (node->ns == old)
		node->ns = ns;

	    if (node->children)
		set_ns_recursively (node->children, old, ns);

	    if (node->properties)
		set_ns_recursively ((xmlNodePtr)node->properties, old, ns);
	}
	else if (node->type == XML_ATTRIBUTE_NODE) {
	    xmlAttrPtr attr = (xmlAttrPtr)node;

	    if (attr->ns == old)
		attr->ns = ns;
	}
    }
}

/** find's all ns declarations */
static GSList *
find_all_ns (xmlNodePtr node,
	     GSList *list)
{
    for ( ; node; node = node->next) {
	if (node->type == XML_ELEMENT_NODE) {
	    xmlNsPtr ns = node->nsDef;

	    for ( ; ns; ns = ns->next)
		list = g_slist_append (list, ns);

	    list = find_all_ns (node, list);
	}
    }
    return list;
}

/** change prefix to unused one */
static void
change_prefix (xmlNodePtr node,
               xmlNsPtr nsChange)
{
    GSList *t, *list = find_all_ns (node, NULL);
    gchar *pch;
    int i;

    /* change prefix of the diff "body" */
    for (i = 'a'; i < 'z'; i++) {
	pch = g_strdup_printf ("%c", i);

	for (t = list; t; t = t->next) {
	    xmlNsPtr ns = t->data;

	    if (ns->prefix && !STRCMP (ns->prefix, pch))
		break;
	}
	if (t == NULL)
	    break;
	g_free (pch);
    }
    for (i = 0; t; i++) {
	pch = g_strdup_printf ("n%d", i);

	for (t = list; t; t = t->next) {
	    xmlNsPtr ns = t->data;

	    if (ns->prefix && !STRCMP (ns->prefix, pch))
		break;
	}
	if (t == NULL)
	    break;
	g_free (pch);
    }
    xmlFree ((xmlChar*)nsChange->prefix);
    nsChange->prefix = xmlMalloc (strlen (pch) + 1);
    strcpy ((char*)nsChange->prefix, pch);
    g_free (pch);

    g_slist_free (list);
}

/** add an element and apply ns-changes */
static xmlNodePtr
add_element (xmlNodePtr pNode,       /* new added elem */
	     xmlNodePtr nodeChanges, /* diff-context */
	     xmlNodePtr add)
{
    if (pNode->parent && pNode->parent->type == XML_DOCUMENT_NODE) {
	return xmlCopyNode (pNode, 1);
    }
    else {
	xmlNsPtr *pns;
	xmlNodePtr n = xmlCopyNode (pNode, 1);

	/* remove possible additional ns declarations from <n>
	 * generated with the above copy */
	for (pns = &n->nsDef; *pns; ) {
	    xmlNsPtr nsNode = pNode->nsDef;

	    for ( ; nsNode && nsNode->prefix != pns[0]->prefix; nsNode = nsNode->next)
		;
	    if (!nsNode) {
		xmlNsPtr nsnew = xmlSearchNs (nodeChanges->doc, nodeChanges, pns[0]->prefix);

		if (!nsnew)
		    nsnew = xmlNewNs (nodeChanges, pns[0]->href, pns[0]->prefix);
		else {
		    /* overlapping definition ? */
		    if (STRCMP (pns[0]->href, nsnew->href)) {
			int i, j = 0;
			gchar *pch = NULL;
			/* check that only a single in-scope ns declaration exists */
			xmlNsPtr ns, *nsList = xmlGetNsList (pNode->doc, pNode->parent);

			for (i = 0; (ns = nsList ? nsList[i] : NULL); i++) {
			    if (!STRCMP (nsnew->href, ns->href))
				j++;
			}
			xmlFree (nsList);
			/* add ns declaration to <add> element */
			if (j > 1) {
			    /* has <add> this same prefix ? */
			    if (add->ns && !STRCMP (add->ns->prefix, pns[0]->prefix))
				change_prefix (nodeChanges, add->ns);

			    nsnew = xmlNewNs (nodeChanges, pns[0]->href, pns[0]->prefix);
			}
			else {
			    /* change prefix of the diff "body" */
			    for (i = 'a'; i < 'z'; i++) {
				pch = g_strdup_printf ("%c", i);
				if (!(nsnew = xmlSearchNs (nodeChanges->doc, nodeChanges, (xmlChar*)pch)))
				    break;
				g_free (pch), pch = NULL;
			    }
			    if (nsnew) {
				for (i = 0; ; i++) {
				    pch = g_strdup_printf ("n%d", i);
				    if (!xmlSearchNs (nodeChanges->doc, nodeChanges, (xmlChar*)pch))
					break;
				    g_free (pch), pch = NULL;
				}
			    }
			    /* and add this declaration to diff element */
			    nsnew = xmlNewNs (nodeChanges, pns[0]->href, (xmlChar*)pch);
			    g_free (pch);
			}
		    }
		}
		/* update references accordingly */
		set_ns_recursively (n, *pns, nsnew);
		nsnew = *pns;
		*pns = (*pns)->next;
		xmlFreeNs (nsnew);
	    }
	    else
		pns = &(*pns)->next;
	}
	return n;
     }
}

/** add sibling node types */
static void
add_node (diff_request_t *p, xmlNodePtr add, xmlNodePtr nodeChanges)
{
    if (p->type == XML_ELEMENT_NODE) {
	xmlAddChild (add, add_element (p->pNode, nodeChanges, add));
    }
    else if (p->type == XML_TEXT_NODE) {
	if (p->addPos == AFTER &&
	    p->pNodeDeleted &&
	    p->pNodeDeleted->type == XML_TEXT_NODE) {

	    int c = p->pNodeDeleted->content ? strlen ((char*)p->pNodeDeleted->content) : 0;

	    if (p->pNode->content && strlen ((char*)p->pNode->content) >= c)
		xmlNodeAddContent (add, p->pNode->content + c);
	}
	else {
	    xmlNodeAddContent (add, p->pNode->content ? p->pNode->content : (const xmlChar*)"");
	}
    }
    else if (p->type == XML_CDATA_SECTION_NODE) {
	xmlAddChild (add, xmlNewCDataBlock (add->doc,
					    p->pNode->content,
					    strlen ((char*)p->pNode->content)));
    }
    else if (p->type == XML_COMMENT_NODE) {
	xmlAddChild (add, xmlNewComment (p->pNode->content));
    }
    else if (p->type == XML_PI_NODE) {
	xmlAddChild (add, xmlNewPI (p->pNode->name, p->pNode->content));
    }
    if (p->type == XML_CDATA_SECTION_NODE || p->type == XML_TEXT_NODE) {
	xmlNodePtr next = p->pNode->next;

	for ( ; next; next = next->next) {
	    if (next->type == XML_CDATA_SECTION_NODE)
		xmlAddChild (add, xmlNewCDataBlock (add->doc,
						    next->content,
						    strlen ((char*)next->content)));
	    else if (next->type == XML_TEXT_NODE)
		xmlNodeAddContent (add, next->content ? next->content : (const xmlChar*)"");
	    else
		break;
	}
    }
}

/** add/replace opers */
static void
add_patch_oper (xmlNodePtr nodeChanges, xmlNodePtr add, int fShort)
{
    if (fShort == XML_DIFF_WHITESPACE)
	xmlAddChild (nodeChanges, xmlNewText ((const xmlChar*)"\n  "));
    xmlAddChild (nodeChanges, add);
}

/** process diffing */
static int
diff_process (xmlDocPtr a,
	      xmlDocPtr b,
	      diff_e *diff,
	      int fPrint,
	      int fShort,
	      xmlNodePtr nodeChanges)
{
    GSList *req = NULL, *r;
    int cbChild = 0;
    diff_level_t Level = { NULL, 0 };
    const xmlChar *pcszRemove  = (const xmlChar*)"remove";
    const xmlChar *pcszAdd = (const xmlChar*)"add";
    const xmlChar *pcszReplace = (const xmlChar*)"replace";

    if (!a || !b)
	return -1;

    if (!nodeChanges) {
	fprintf (stdout, "ERROR: not any node where to add patch operations !\n");
	return -1;
    }

    add_level (&Level, 0, 0);
    diff_cmp_steps_children (fShort, (xmlNodePtr)a, (xmlNodePtr)b, diff, 0, &Level, &req, &cbChild);
    free_levels (&Level);

    if (fPrint && *diff != NONE) {
	fprintf (stdout, "OLD doc:\n");
	xmlDocFormatDump (stdout, a, 1);
	fprintf (stdout, "\nNEW doc:\n");
	xmlDocFormatDump (stdout, b, 1);
	fprintf (stdout, "\nCHANGES:\n");
    }

    for (r = req; r; r = r->next) {
	diff_request_t *p = r->data;
	xmlNodePtr add = NULL;

	switch (p->method) {
	case SKIP:
	    break;

	case DELETE:
	    add = xmlNewNode (nodeChanges->ns, pcszRemove);
	    if (!fShort)
		xmlAddChild (nodeChanges, xmlNewText ((const xmlChar*)"\n  "));
	    xmlAddChild (nodeChanges, add);
	    break;

	case ADD:
	    if (p->type == XML_ATTRIBUTE_NODE) {
		xmlAttrPtr attr = (xmlAttrPtr)p->pNode;

		add = xmlNewNode (nodeChanges->ns, pcszAdd);
		xmlNodeAddContent (add, attr->children ? attr->children->content : (const xmlChar*)"");
		add_patch_oper (nodeChanges, add, fShort);
		break;
	    }
	    else if (p->type == XML_NAMESPACE_DECL) {
		xmlNsPtr ns = (xmlNsPtr)p->pNode;

		add = xmlNewNode (nodeChanges->ns, pcszAdd);
		xmlNodeAddContent (add, ns ? ns->href : (const xmlChar*)"");
		add_patch_oper (nodeChanges, add, fShort);
		break;
	    }

	case REPLACE:
	    if (p->method == REPLACE) {
		add = xmlNewNode (nodeChanges->ns, pcszReplace);

		if (p->type == XML_ELEMENT_NODE ||
		    p->type == XML_COMMENT_NODE ||
		    p->type == XML_TEXT_NODE ||
		    p->type == XML_CDATA_SECTION_NODE ||
		    p->type == XML_PI_NODE) {

		    add_node (p, add, nodeChanges);
		}
		else if (p->type == XML_ATTRIBUTE_NODE) {
		    xmlAttrPtr attr = (xmlAttrPtr)p->pNode;

		    xmlNodeAddContent (add, attr->children ? attr->children->content : (const xmlChar*)"");
		}
		else if (p->type == XML_NAMESPACE_DECL) {
		    xmlNsPtr ns = (xmlNsPtr)p->pNode;

		    xmlNodeAddContent (add, ns->href ? ns->href : (const xmlChar*)"");
		}
		add_patch_oper (nodeChanges, add, fShort);
		break;
	    }
	    if (p->method == ADD) {
		add = xmlNewNode (nodeChanges->ns, pcszAdd);
		add_node (p, add, nodeChanges);
		add_patch_oper (nodeChanges, add, fShort);
		{
		    diff_request_t *pNext = p->nextAdd;

		    for ( ; pNext; pNext = pNext->nextAdd) {
			add_node (pNext, add, nodeChanges);
			pNext->method = SKIP;
		    }
		}
	    }
	    break;
	}
	if (p->method != SKIP) {
	    if (p->type == XML_ATTRIBUTE_NODE && (p->method == DELETE || p->method == REPLACE)) {
		xmlAttrPtr attr = (xmlAttrPtr)p->pNodeDeleted;
		xmlNsPtr ns = attr_has_ns(attr, nodeChanges);
		char *pch = p->pch;

		p->pch = g_strdup_printf ("%s/@%s%s%s",
					  pch,
					  ns ? (char*)ns->prefix : "",
					  ns ? ":" : "",
					  attr->name);
		g_free (pch);
	    }
	    else if (p->type == XML_NAMESPACE_DECL && (p->method == DELETE || p->method == REPLACE)) {
		xmlNsPtr ns = (xmlNsPtr)p->pNodeDeleted;
		char *pch = p->pch;

		p->pch = g_strdup_printf ("%s/namespace::%s",
					  pch,
					  ns ? (char*)ns->prefix : "");
		g_free (pch);
	    }

	    if (add) {
		xmlSetProp (add, (const xmlChar*)"sel", (const xmlChar*)p->pch);

		if (p->addPos != INIT)
		    xmlSetProp (add, (const xmlChar*)"pos",
				p->addPos == AFTER ? (const xmlChar*)"after" : (const xmlChar*)"before");

		if (p->method == ADD && p->type == XML_ATTRIBUTE_NODE) {
		    xmlAttrPtr attr = (xmlAttrPtr)p->pNode;
		    xmlNsPtr ns = attr_has_ns (attr, nodeChanges);

		    char *pch = g_strdup_printf ("@%s%s%s",
						 ns ? (char*)ns->prefix : "",
						 ns ? ":" : "",
						 (char*)attr->name);

		    xmlSetProp (add, (const xmlChar*)"type", (const xmlChar*)pch);
		    g_free (pch);
		}
		else if (p->method == ADD && p->type == XML_NAMESPACE_DECL) {
		    xmlNsPtr ns = (xmlNsPtr)p->pNode;

		    char *pch = g_strdup_printf ("namespace::%s", ns ? (char*)ns->prefix : "");
		    xmlSetProp (add, (const xmlChar*)"type", (const xmlChar*)pch);
		    g_free (pch);
		}
		else if (p->method == DELETE && p->ws != INIT) {
		    if ((p->ws & BEFORE) == BEFORE && (p->ws & AFTER) == AFTER)
			xmlSetProp (add, (const xmlChar*)"ws", (const xmlChar*)"both");
		    if ((p->ws & BEFORE) == BEFORE)
			xmlSetProp (add, (const xmlChar*)"ws", (const xmlChar*)"before");
		    else
			xmlSetProp (add, (const xmlChar*)"ws", (const xmlChar*)"after");
		}
	    }
	}
	g_free (p->pch);
	g_free (p);
    }
    if (req)
	g_slist_free (req);

    return *diff == NONE ? 0 : 1;
}

/** public interface to diff */
int
xml_exec_diff (xmlDocPtr prev,		/**< old doc */
	       xmlDocPtr new,		/**< new doc */
	       int fPrint,		/**< some output */
	       int fShort,		/**< try to shorten payload */
	       xmlNodePtr node)		/**< node to append patches */
{
    diff_e diff = NONE;
    int rc;

    rc = diff_process (prev, new, &diff, fPrint, fShort, node);
    if (rc < 0)
	fprintf (stderr, "error producing the diff document\n");
    else if (fPrint) {
	if (rc)
	    printf ("differencies in the documents\n");
	else
	    printf ("equal documents within xml-diff scope\n");
    }
    return rc;
}
