/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 *  Jabber
 *  Copyright (C) 1998-1999 The Jabber Team http://jabber.org/
 */
/*! \file
 * \ingroup xmpp
 */


#include "xode.h"

static int _xode_strcmp(const char *a, const char *b)
{
	if(a == NULL || b == NULL)
		return -1;

	return strcmp(a, b);
}

/*! \brief Internal routines */
static xode _xode_new(xode_pool p, const char *name, unsigned int type)
{
	xode result = NULL;
	if(type > XODE_TYPE_LAST)
		return NULL;

	if(type != XODE_TYPE_CDATA && name == NULL)
		return NULL;

	if(p == NULL) {
		p = xode_pool_heap(1 * 1024);
	}

	/* Allocate & zero memory */
	result = (xode)xode_pool_malloc(p, sizeof(_xode));
	memset(result, '\0', sizeof(_xode));

	/* Initialize fields */
	if(type != XODE_TYPE_CDATA)
		result->name = xode_pool_strdup(p, name);
	result->type = type;
	result->p = p;
	return result;
}

static xode _xode_appendsibling(
		xode lastsibling, const char *name, unsigned int type)
{
	xode result;

	result = _xode_new(xode_get_pool(lastsibling), name, type);
	if(result != NULL) {
		/* Setup sibling pointers */
		result->prev = lastsibling;
		lastsibling->next = result;
	}
	return result;
}

static xode _xode_insert(xode parent, const char *name, unsigned int type)
{
	xode result;

	if(parent == NULL || name == NULL)
		return NULL;

	/* If parent->firstchild is NULL, simply create a new node for the first child */
	if(parent->firstchild == NULL) {
		result = _xode_new(parent->p, name, type);
		parent->firstchild = result;
	}
	/* Otherwise, append this to the lastchild */
	else {
		result = _xode_appendsibling(parent->lastchild, name, type);
	}
	result->parent = parent;
	parent->lastchild = result;
	return result;
}

static xode _xode_search(xode firstsibling, const char *name, unsigned int type)
{
	xode current;

	/* Walk the sibling list, looking for a XODE_TYPE_TAG xode with
    the specified name */
	current = firstsibling;
	while(current != NULL) {
		if(name != NULL && (current->type == type)
				&& (_xode_strcmp(current->name, name) == 0))
			return current;
		else
			current = current->next;
	}
	return NULL;
}

static char *_xode_merge(xode_pool p, char *dest, unsigned int destsize,
		const char *src, unsigned int srcsize)
{
	char *result;
	result = (char *)xode_pool_malloc(p, destsize + srcsize + 1);
	memcpy(result, dest, destsize);
	memcpy(result + destsize, src, srcsize);
	result[destsize + srcsize] = '\0';

	/* WARNING: major ugly hack: since we're throwing the old data away, let's jump in the xode_pool and subtract it from the size, this is for xmlstream's big-node checking */
	p->size -= destsize;

	return result;
}

static void _xode_hidesibling(xode child)
{
	if(child == NULL)
		return;

	if(child->prev != NULL)
		child->prev->next = child->next;
	if(child->next != NULL)
		child->next->prev = child->prev;
}

static void _xode_tag2str(xode_spool s, xode node, int flag)
{
	xode tmp;

	if(flag == 0 || flag == 1) {
		xode_spooler(s, "<", xode_get_name(node), s);
		tmp = xode_get_firstattrib(node);
		while(tmp) {
			xode_spooler(s, " ", xode_get_name(tmp), "='",
					xode_strescape(xode_get_pool(node), xode_get_data(tmp)),
					"'", s);
			tmp = xode_get_nextsibling(tmp);
		}
		if(flag == 0)
			xode_spool_add(s, "/>");
		else
			xode_spool_add(s, ">");
	} else {
		xode_spooler(s, "</", xode_get_name(node), ">", s);
	}
}

static xode_spool _xode_tospool(xode node)
{
	xode_spool s;
	int level = 0, dir = 0;
	xode tmp;

	if(!node || xode_get_type(node) != XODE_TYPE_TAG)
		return NULL;

	s = xode_spool_newfrompool(xode_get_pool(node));
	if(!s)
		return (NULL);

	while(1) {
		if(dir == 0) {
			if(xode_get_type(node) == XODE_TYPE_TAG) {
				if(xode_has_children(node)) {
					_xode_tag2str(s, node, 1);
					node = xode_get_firstchild(node);
					level++;
					continue;
				} else {
					_xode_tag2str(s, node, 0);
				}
			} else {
				xode_spool_add(s, xode_strescape(xode_get_pool(node),
										  xode_get_data(node)));
			}
		}

		tmp = xode_get_nextsibling(node);
		if(!tmp) {
			node = xode_get_parent(node);
			level--;
			if(level >= 0)
				_xode_tag2str(s, node, 2);
			if(level < 1)
				break;
			dir = 1;
		} else {
			node = tmp;
			dir = 0;
		}
	}

	return s;
}


/* External routines */


/*
 *  xode_new_tag -- create a tag node
 *  Automatically creates a memory xode_pool for the node.
 *
 *  parameters
 *      name -- name of the tag
 *
 *  returns
 *      a pointer to the tag node
 *      or NULL if it was unsuccessful
 */
xode xode_new(const char *name)
{
	return _xode_new(NULL, name, XODE_TYPE_TAG);
}

/*
 * alias for 'xode_new'
 */
xode xode_new_tag(const char *name)
{
	return _xode_new(NULL, name, XODE_TYPE_TAG);
}

/*
 *  xode_new_tag_pool -- create a tag node within given pool
 *
 *  parameters
 *      p -- previously created memory pool
 *      name -- name of the tag
 *
 *  returns
 *      a pointer to the tag node
 *      or NULL if it was unsuccessful
 */
xode xode_new_frompool(xode_pool p, const char *name)
{
	return _xode_new(p, name, XODE_TYPE_TAG);
}


/*
 *  xode_insert_tag -- append a child tag to a tag
 *
 *  parameters
 *      parent -- pointer to the parent tag
 *      name -- name of the child tag
 *
 *  returns
 *      a pointer to the child tag node
 *      or NULL if it was unsuccessful
 */
xode xode_insert_tag(xode parent, const char *name)
{
	return _xode_insert(parent, name, XODE_TYPE_TAG);
}


/*
 *  xode_insert_cdata -- append character data to a tag
 *  If last child of the parent is CDATA, merges CDATA nodes. Otherwise
 *  creates a CDATA node, and appends it to the parent's child list.
 *
 *  parameters
 *      parent -- parent tag
 *      CDATA -- character data
 *      size -- size of CDATA
 *              or -1 for null-terminated CDATA strings
 *
 *  returns
 *      a pointer to the child CDATA node
 *      or NULL if it was unsuccessful
 */
xode xode_insert_cdata(xode parent, const char *CDATA, unsigned int size)
{
	xode result;

	if(CDATA == NULL || parent == NULL)
		return NULL;

	if(size == -1)
		size = strlen(CDATA);

	if((parent->lastchild != NULL)
			&& (parent->lastchild->type == XODE_TYPE_CDATA)) {
		result = parent->lastchild;
		result->data = _xode_merge(
				result->p, result->data, result->data_sz, CDATA, size);
		result->data_sz = result->data_sz + size;
	} else {
		result = _xode_insert(parent, "", XODE_TYPE_CDATA);
		if(result != NULL) {
			result->data = (char *)xode_pool_malloc(result->p, size + 1);
			memcpy(result->data, CDATA, size);
			result->data[size] = '\0';
			result->data_sz = size;
		}
	}

	return result;
}


/*
 *  xode_gettag -- find given tag in an xode tree
 *
 *  parameters
 *      parent -- pointer to the parent tag
 *      name -- "name" for the child tag of that name
 *              "name/name" for a sub child (recurses)
 *              "?attrib" to match the first tag with that attrib defined
 *              "?attrib=value" to match the first tag with that attrib and value
 *              or any combination: "name/name/?attrib", etc
 *
 *  results
 *      a pointer to the tag matching search criteria
 *      or NULL if search was unsuccessful
 */
xode xode_get_tag(xode parent, const char *name)
{
	char *str, *slash, *qmark, *equals;
	xode step, ret;

	if(parent == NULL || parent->firstchild == NULL || name == NULL
			|| *name == '\0')
		return NULL;

	if(strstr(name, "/") == NULL && strstr(name, "?") == NULL)
		return _xode_search(parent->firstchild, name, XODE_TYPE_TAG);

	/* jer's note: why can't I modify the name directly, why do I have to strdup it?  damn c grrr! */
	str = strdup(name);
	slash = strstr(str, "/");
	qmark = strstr(str, "?");
	equals = strstr(str, "=");

	if(qmark != NULL
			&& (slash == NULL || qmark < slash)) { /* of type ?attrib */

		*qmark = '\0';
		qmark++;
		if(equals != NULL) {
			*equals = '\0';
			equals++;
		}

		for(step = parent->firstchild; step != NULL;
				step = xode_get_nextsibling(step)) {
			if(xode_get_type(step) != XODE_TYPE_TAG)
				continue;

			if(*str != '\0')
				if(_xode_strcmp(xode_get_name(step), str) != 0)
					continue;

			if(xode_get_attrib(step, qmark) == NULL)
				continue;

			if(equals != NULL
					&& _xode_strcmp(xode_get_attrib(step, qmark), equals) != 0)
				continue;

			break;
		}

		free(str);
		return step;
	}


	*slash = '\0';
	++slash;

	for(step = parent->firstchild; step != NULL;
			step = xode_get_nextsibling(step)) {
		if(xode_get_type(step) != XODE_TYPE_TAG)
			continue;

		if(_xode_strcmp(xode_get_name(step), str) != 0)
			continue;

		ret = xode_get_tag(step, slash);
		if(ret != NULL) {
			free(str);
			return ret;
		}
	}

	free(str);
	return NULL;
}


/* return the cdata from any tag */
char *xode_get_tagdata(xode parent, const char *name)
{
	xode tag;

	tag = xode_get_tag(parent, name);
	if(tag == NULL)
		return NULL;

	return xode_get_data(tag);
}


void xode_put_attrib(xode owner, const char *name, const char *value)
{
	xode attrib;

	if(owner == NULL || name == NULL || value == NULL)
		return;

	/* If there are no existing attributes, allocate a new one to start
    the list */
	if(owner->firstattrib == NULL) {
		attrib = _xode_new(owner->p, name, XODE_TYPE_ATTRIB);
		owner->firstattrib = attrib;
		owner->lastattrib = attrib;
	} else {
		attrib = _xode_search(owner->firstattrib, name, XODE_TYPE_ATTRIB);
		if(attrib == NULL) {
			attrib = _xode_appendsibling(
					owner->lastattrib, name, XODE_TYPE_ATTRIB);
			owner->lastattrib = attrib;
		}
	}
	/* Update the value of the attribute */
	attrib->data_sz = strlen(value);
	attrib->data = xode_pool_strdup(owner->p, value);
}

char *xode_get_attrib(xode owner, const char *name)
{
	xode attrib;

	if(owner != NULL && owner->firstattrib != NULL) {
		attrib = _xode_search(owner->firstattrib, name, XODE_TYPE_ATTRIB);
		if(attrib != NULL)
			return (char *)attrib->data;
	}
	return NULL;
}

void xode_put_vattrib(xode owner, const char *name, void *value)
{
	xode attrib;

	if(owner != NULL) {
		attrib = _xode_search(owner->firstattrib, name, XODE_TYPE_ATTRIB);
		if(attrib == NULL) {
			xode_put_attrib(owner, name, "");
			attrib = _xode_search(owner->firstattrib, name, XODE_TYPE_ATTRIB);
		}
		if(attrib != NULL)
			attrib->firstchild = (xode)value;
	}
}

void *xode_get_vattrib(xode owner, const char *name)
{
	xode attrib;

	if(owner != NULL && owner->firstattrib != NULL) {
		attrib = _xode_search(owner->firstattrib, name, XODE_TYPE_ATTRIB);
		if(attrib != NULL)
			return (void *)attrib->firstchild;
	}
	return NULL;
}

xode xode_get_firstattrib(xode parent)
{
	if(parent != NULL)
		return parent->firstattrib;
	return NULL;
}

xode xode_get_firstchild(xode parent)
{
	if(parent != NULL)
		return parent->firstchild;
	return NULL;
}

xode xode_get_lastchild(xode parent)
{
	if(parent != NULL)
		return parent->lastchild;
	return NULL;
}

xode xode_get_nextsibling(xode sibling)
{
	if(sibling != NULL)
		return sibling->next;
	return NULL;
}

xode xode_get_prevsibling(xode sibling)
{
	if(sibling != NULL)
		return sibling->prev;
	return NULL;
}

xode xode_get_parent(xode node)
{
	if(node != NULL)
		return node->parent;
	return NULL;
}

char *xode_get_name(xode node)
{
	if(node != NULL)
		return node->name;
	return NULL;
}

char *xode_get_data(xode node)
{
	xode cur;

	if(node == NULL)
		return NULL;

	if(xode_get_type(node) == XODE_TYPE_TAG) /* loop till we find a CDATA */
	{
		for(cur = xode_get_firstchild(node); cur != NULL;
				cur = xode_get_nextsibling(cur))
			if(xode_get_type(cur) == XODE_TYPE_CDATA)
				return cur->data;
	} else {
		return node->data;
	}
	return NULL;
}

int xode_get_datasz(xode node)
{

	if(node == NULL) {
		return (int)(long)NULL;
	} else if(xode_get_type(node)
			  == XODE_TYPE_TAG) /* loop till we find a CDATA */
	{
		xode cur;
		for(cur = xode_get_firstchild(node); cur != NULL;
				cur = xode_get_nextsibling(cur))
			if(xode_get_type(cur) == XODE_TYPE_CDATA)
				return cur->data_sz;
	} else {
		return node->data_sz;
	}
	return (int)(long)NULL;
}

int xode_get_type(xode node)
{
	if(node != NULL) {
		return node->type;
	}
	return (int)(long)NULL;
}

int xode_has_children(xode node)
{
	if((node != NULL) && (node->firstchild != NULL))
		return 1;
	return 0;
}

int xode_has_attribs(xode node)
{
	if((node != NULL) && (node->firstattrib != NULL))
		return 1;
	return 0;
}

xode_pool xode_get_pool(xode node)
{
	if(node != NULL)
		return node->p;
	return (xode_pool)NULL;
}

void xode_hide(xode child)
{
	xode parent;

	if(child == NULL || child->parent == NULL)
		return;

	parent = child->parent;

	/* first fix up at the child level */
	_xode_hidesibling(child);

	/* next fix up at the parent level */
	if(parent->firstchild == child)
		parent->firstchild = child->next;
	if(parent->lastchild == child)
		parent->lastchild = child->prev;
}

void xode_hide_attrib(xode parent, const char *name)
{
	xode attrib;

	if(parent == NULL || parent->firstattrib == NULL || name == NULL)
		return;

	attrib = _xode_search(parent->firstattrib, name, XODE_TYPE_ATTRIB);
	if(attrib == NULL)
		return;

	/* first fix up at the child level */
	_xode_hidesibling(attrib);

	/* next fix up at the parent level */
	if(parent->firstattrib == attrib)
		parent->firstattrib = attrib->next;
	if(parent->lastattrib == attrib)
		parent->lastattrib = attrib->prev;
}


/*
 *  xode2str -- convert given xode tree into a string
 *
 *  parameters
 *      node -- pointer to the xode structure
 *
 *  results
 *      a pointer to the created string
 *      or NULL if it was unsuccessful
 */
char *xode_to_str(xode node)
{
	return xode_spool_tostr(_xode_tospool(node));
}


/* loop through both a and b comparing everything, attribs, cdata, children, etc */
int xode_cmp(xode a, xode b)
{
	int ret = 0;

	while(1) {
		if(a == NULL && b == NULL)
			return 0;

		if(a == NULL || b == NULL)
			return -1;

		if(xode_get_type(a) != xode_get_type(b))
			return -1;

		switch(xode_get_type(a)) {
			case XODE_TYPE_ATTRIB:
				ret = _xode_strcmp(xode_get_name(a), xode_get_name(b));
				if(ret != 0)
					return -1;
				ret = _xode_strcmp(xode_get_data(a), xode_get_data(b));
				if(ret != 0)
					return -1;
				break;
			case XODE_TYPE_TAG:
				ret = _xode_strcmp(xode_get_name(a), xode_get_name(b));
				if(ret != 0)
					return -1;
				ret = xode_cmp(
						xode_get_firstattrib(a), xode_get_firstattrib(b));
				if(ret != 0)
					return -1;
				ret = xode_cmp(xode_get_firstchild(a), xode_get_firstchild(b));
				if(ret != 0)
					return -1;
				break;
			case XODE_TYPE_CDATA:
				ret = _xode_strcmp(xode_get_data(a), xode_get_data(b));
				if(ret != 0)
					return -1;
		}
		a = xode_get_nextsibling(a);
		b = xode_get_nextsibling(b);
	}
}


xode xode_insert_tagnode(xode parent, xode node)
{
	xode child;

	child = xode_insert_tag(parent, xode_get_name(node));
	if(xode_has_attribs(node))
		xode_insert_node(child, xode_get_firstattrib(node));
	if(xode_has_children(node))
		xode_insert_node(child, xode_get_firstchild(node));

	return child;
}

/* places copy of node and node's siblings in parent */
void xode_insert_node(xode parent, xode node)
{
	if(node == NULL || parent == NULL)
		return;

	while(node != NULL) {
		switch(xode_get_type(node)) {
			case XODE_TYPE_ATTRIB:
				xode_put_attrib(
						parent, xode_get_name(node), xode_get_data(node));
				break;
			case XODE_TYPE_TAG:
				xode_insert_tagnode(parent, node);
				break;
			case XODE_TYPE_CDATA:
				xode_insert_cdata(
						parent, xode_get_data(node), xode_get_datasz(node));
		}
		node = xode_get_nextsibling(node);
	}
}


/* produce full duplicate of x with a new xode_pool, x must be a tag! */
xode xode_dup(xode x)
{
	xode x2;

	if(x == NULL)
		return NULL;

	x2 = xode_new(xode_get_name(x));

	if(xode_has_attribs(x))
		xode_insert_node(x2, xode_get_firstattrib(x));
	if(xode_has_children(x))
		xode_insert_node(x2, xode_get_firstchild(x));

	return x2;
}

xode xode_dup_frompool(xode_pool p, xode x)
{
	xode x2;

	if(x == NULL)
		return NULL;

	x2 = xode_new_frompool(p, xode_get_name(x));

	if(xode_has_attribs(x))
		xode_insert_node(x2, xode_get_firstattrib(x));
	if(xode_has_children(x))
		xode_insert_node(x2, xode_get_firstchild(x));

	return x2;
}

xode xode_wrap(xode x, const char *wrapper)
{
	xode wrap;
	if(x == NULL || wrapper == NULL)
		return NULL;
	wrap = xode_new_frompool(xode_get_pool(x), wrapper);
	if(wrap == NULL)
		return NULL;
	wrap->firstchild = x;
	wrap->lastchild = x;
	x->parent = wrap;
	return wrap;
}

void xode_free(xode node)
{
	if(node == NULL)
		return;

	xode_pool_free(node->p);
}


void _xode_to_prettystr(xode_spool s, xode x, int deep)
{
	int i;
	xode y;

	if(xode_get_type(x) != XODE_TYPE_TAG)
		return;

	for(i = 0; i < deep; i++)
		xode_spool_add(s, "\t");

	xode_spooler(s, "<", xode_get_name(x), s);

	y = xode_get_firstattrib(x);
	while(y) {
		xode_spooler(s, " ", xode_get_name(y), "='", xode_get_data(y), "'", s);

		y = xode_get_nextsibling(y);
	}
	xode_spool_add(s, ">");
	xode_spool_add(s, "\n");

	if(xode_get_data(x)) {
		for(i = 0; i <= deep; i++)
			xode_spool_add(s, "\t");
		xode_spool_add(s, xode_get_data(x));
	}

	y = xode_get_firstchild(x);
	while(y) {
		_xode_to_prettystr(s, y, deep + 1);
		y = xode_get_nextsibling(y);
		xode_spool_add(s, "\n");
	}

	for(i = 0; i < deep; i++)
		xode_spool_add(s, "\t");
	xode_spooler(s, "</", xode_get_name(x), ">", s);

	return;
}

char *xode_to_prettystr(xode x)
{
	xode_spool s;

	if(!x)
		return NULL;

	s = xode_spool_newfrompool(xode_get_pool(x));

	_xode_to_prettystr(s, x, 0);

	return xode_spool_tostr(s);
}
