/**
 * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
 *
 * Author: Seudin Kasumovic (seudin.kasumovic@gmail.com)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio 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
 *
 * Kamailio 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
 *
 */

#include <stdlib.h>

#include "../../core/str.h"
#include "../../core/hashes.h"

#include "pv_xbuff.h"

str xbuff_attributes[] = {STR_STATIC_INIT("type"), STR_STATIC_INIT("format"),
		STR_STATIC_INIT("length"), STR_NULL};

str xbuff_types[] = {STR_STATIC_INIT("atom"), STR_STATIC_INIT("integer"),
		STR_STATIC_INIT("string"), STR_STATIC_INIT("tuple"),
		STR_STATIC_INIT("list"), STR_STATIC_INIT("pid"), STR_STATIC_INIT("ref"),
		STR_NULL};

/**
 * atom,tuple,xbuff & list regex
 */
regex_t xbuff_type_re = {0};

static str xbuff_list = str_init("[xbuffs]");

static int counter;
static char *xbuff_fmt_buff = NULL;

void xbuff_data_free(void *p, sr_xavp_sfree_f sfree);

sr_xavp_t *xavp_get_xbuffs()
{
	sr_xavp_t *list;
	list = xavp_get(&xbuff_list, NULL);

	if(!list)
		counter = 0;

	return list;
}

sr_xavp_t *pv_xbuff_get_xbuff(str *name)
{
	return xavp_get_child(&xbuff_list, name);
}

sr_xavp_t *xbuff_new(str *name)
{
	sr_xavp_t *xbuffs_root;
	sr_xavp_t *xbuff;
	sr_xval_t xbuff_val;

	memset((void *)&xbuff_val, 0, sizeof(sr_xval_t));
	xbuff_val.type = SR_XTYPE_NULL;

	xbuffs_root = xavp_get_xbuffs();

	if(!xbuffs_root) {
		xbuffs_root = xavp_add_xavp_value(
				&xbuff_list, name, &xbuff_val, xavp_get_crt_list());
		if(!xbuffs_root) {
			LM_ERR("cannot create xbuffs_root \n");
			return NULL;
		}
	}

	xbuff = xavp_get_child(&xbuff_list, name);

	if(!xbuff) {

		xbuff_val.type = SR_XTYPE_NULL;
		xbuff_val.v.xavp = NULL;

		xbuff = xavp_add_value(name, &xbuff_val, &xbuffs_root->val.v.xavp);
	}

	return xbuff;
}

/***
 * if compile success returns 0
 */
int compile_xbuff_re()
{
	char *pattern = "^<<\\(tuple\\|list\\|atom\\|pid\\|ref\\):\\(0x[[:xdigit:]]"
					"\\+\\)>>$";
	size_t bfsz = 128;
	char errbuff[128];
	int e;

	if((e = regcomp(&xbuff_type_re, pattern, 0))) {
		regerror(e, &xbuff_type_re, errbuff, bfsz);
		LM_ERR("failed to compile pattern '%s' error: %s\n", pattern, errbuff);
		return -1;
	}

	return 0;
}

int xbuff_match_type_re(str *s, xbuff_type_t *type, sr_xavp_t **addr)
{
	size_t nmatch = 3;
	regmatch_t matches[3];
	int e;
	size_t bfsz = 128;
	char errbuff[128];
	str tname;
	str a;
	xbuff_type_t t;

	matches[0].rm_so = 0;
	matches[0].rm_eo = s->len;

	e = regexec(&xbuff_type_re, s->s, nmatch, matches, REG_STARTEND);

	if(e == 0) {

		tname.s = s->s + matches[1].rm_so;
		tname.len = matches[1].rm_eo - matches[1].rm_so;

		a.s = s->s + matches[2].rm_so;
		a.len = matches[2].rm_eo - matches[1].rm_so;

		if(STR_EQ(tname, xbuff_types[XBUFF_TYPE_ATOM])) {
			t = XBUFF_TYPE_ATOM;
		} else if(STR_EQ(tname, xbuff_types[XBUFF_TYPE_LIST])) {
			t = XBUFF_TYPE_LIST;
		} else if(STR_EQ(tname, xbuff_types[XBUFF_TYPE_TUPLE])) {
			t = XBUFF_TYPE_TUPLE;
		} else if(STR_EQ(tname, xbuff_types[XBUFF_TYPE_PID])) {
			t = XBUFF_TYPE_PID;
		} else if(STR_EQ(tname, xbuff_types[XBUFF_TYPE_REF])) {
			t = XBUFF_TYPE_REF;
		} else {
			LM_ERR("BUG: unknown xbuff type");
			return -1;
		}

		if(type)
			*type = t;

		if(addr)
			sscanf(a.s, "%lx>>", (long unsigned int *)addr);

		return 0;

	} else if(e != REG_NOMATCH) {
		regerror(e, &xbuff_type_re, errbuff, bfsz);
		LM_ERR("regexec error: %s\n", errbuff);
	}

	return -1;
}

int is_pv_xbuff_valid_char(char c)
{
	if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')
			|| (c >= 'A' && c <= 'Z') || (c == '_'))
		return 1;
	return 0;
}

int pv_xbuff_parse_name(pv_spec_t *sp, str *in)
{
	char *p;
	str idx;
	str name;
	str attr;
	int l;

	if(in->s == NULL || in->len <= 0)
		return -1;

	p = in->s;

	name.s = p;

	/*pvi.type now defaults to 4, breaking the erlang module's type management*/
	sp->pvp.pvi.type = 0;

	while(is_in_str(p, in)) {
		if(*p == '[' || *p == '=')
			break;
		if(!is_pv_xbuff_valid_char(*p)) {
			l = p - in->s;
			LM_ERR("invalid character in var name %.*s at %d\n", STR_FMT(in),
					l);
			goto error;
		}
		p++;
	}

	/* from in->s to p */
	name.len = p - in->s;

	if(pv_parse_avp_name(sp, &name))
		goto error;

	if(is_in_str(p, in) && *p == '[') {
		idx.s = ++p;

		while(is_in_str(p, in)) {
			if(*p == ']' || *p == '=')
				break;
			p++;
		}

		if(is_in_str(p, in) && *p == ']') {
			idx.len = p - idx.s;

			if(pv_parse_index(sp, &idx))
				goto error;
		}
		p++;
	} else {
		xbuff_set_attr_flag(sp->pvp.pvi.type, XBUFF_NO_IDX);
	}

	if(is_in_str(p, in) && *p == '=') {
		p++;

		if(!is_in_str(p, in) || *p != '>') {
			l = p - in->s;
			LM_ERR("invalid operator (expected =>) for accessing attribute in "
				   "token %.*s at position %d\n",
					STR_FMT(in), l);
			goto error;
		}

		attr.s = ++p;

		while(is_in_str(p, in)) {
			if(!is_pv_xbuff_valid_char(*p)) {
				l = p - in->s;
				LM_ERR("invalid character in attribute name in token %.*s at "
					   "%d\n",
						STR_FMT(in), l);
				goto error;
			}
			p++;
		}

		attr.len = p - attr.s;

		if(attr.len > 0) {

			if(STR_EQ(attr, xbuff_attr_name(XBUFF_ATTR_TYPE))) {
				xbuff_set_attr_flag(sp->pvp.pvi.type, XBUFF_ATTR_TYPE);
			} else if(STR_EQ(attr, xbuff_attr_name(XBUFF_ATTR_FORMAT))) {
				xbuff_set_attr_flag(sp->pvp.pvi.type, XBUFF_ATTR_FORMAT);
			} else if(STR_EQ(attr, xbuff_attr_name(XBUFF_ATTR_LENGTH))) {
				xbuff_set_attr_flag(sp->pvp.pvi.type, XBUFF_ATTR_LENGTH);
			} else {
				LM_ERR("unknown attribute %.*s\n", STR_FMT(&attr));
				goto error;
			}

			if(sp->pvp.pvi.type & PV_IDX_ALL) {
				LM_ERR("index [*] (all) isn't compatible with attribute %.*s\n",
						STR_FMT(&attr));
				goto error;
			}
		}
	}

	if(p < in->s + in->len) {
		l = p - in->s;
		LM_ERR("unexpected token in %.*s at %d\n", STR_FMT(in), l);
		goto error;
	}

	return 0;

error:

	return -1;
}


int pv_xbuff_new_xavp(
		sr_xavp_t **new, pv_value_t *pval, int *counter, char prefix)
{
	char s[101];
	str name;
	sr_xavp_t *xavp = NULL;
	sr_xavp_t *cxavp = NULL;
	sr_xval_t nval;
	xbuff_type_t type;

	if(!new)
		return -1;

	memset((void *)&nval, 0, sizeof(sr_xval_t));

	if(pval->flags & PV_VAL_NULL) {
		nval.type = SR_XTYPE_NULL;
		s[0] = prefix ? prefix : 'n';
	} else if(pval->flags & PV_VAL_INT) {
		nval.type = SR_XTYPE_LONG;
		nval.v.l = pval->ri;
		s[0] = prefix ? prefix : 'i';
	} else if(pval->flags & PV_VAL_STR) {
		/* check what it is */
		if(xbuff_match_type_re(&pval->rs, &type, &xavp)) {
			nval.type = SR_XTYPE_STR;
			nval.v.s = pval->rs;
			s[0] = prefix ? prefix : 's';
		} else {
			switch(type) {
				case XBUFF_TYPE_ATOM:
					s[0] = 'a';
					nval = xavp->val;
					break;
				case XBUFF_TYPE_LIST:
					s[0] = 'l';
					/* copy tree */
					cxavp = xbuff_copy_xavp(xavp);

					if(!cxavp)
						return -1;

					nval = cxavp->val;

					/* free overhead */
					cxavp->next = NULL;
					cxavp->val.v.xavp = NULL;
					xavp_destroy_list(&cxavp);

					break;
				case XBUFF_TYPE_TUPLE:
					s[0] = 't';

					/* copy tree */
					cxavp = xbuff_copy_xavp(xavp);

					if(!cxavp)
						return -1;

					nval = cxavp->val;

					/* free overhead */
					cxavp->next = NULL;
					cxavp->val.v.xavp = NULL;
					xavp_destroy_list(&cxavp);
					break;
				case XBUFF_TYPE_PID:
					s[0] = 'p';
					nval.type = SR_XTYPE_DATA;
					nval.v.data = (sr_data_t *)shm_malloc(
							sizeof(sr_data_t) + sizeof(erlang_pid));
					if(!nval.v.data) {
						LM_ERR("not enough shared memory\n");
						return -1;
					}
					memcpy((void *)nval.v.data, (void *)xavp,
							sizeof(sr_data_t) + sizeof(erlang_pid));
					break;
				case XBUFF_TYPE_REF:
					s[0] = 'r';
					nval.type = SR_XTYPE_DATA;
					nval.v.data = (sr_data_t *)shm_malloc(
							sizeof(sr_data_t) + sizeof(erlang_ref));
					if(!nval.v.data) {
						LM_ERR("not enough shared memory\n");
						return -1;
					}
					memcpy((void *)nval.v.data, (void *)xavp,
							sizeof(sr_data_t) + sizeof(erlang_ref));
					break;
				case XBUFF_TYPE_INT:
				case XBUFF_TYPE_STR:
				default:
					LM_ERR("BUG: unexpected XBUFF type!\n");
					return -1;
			}
		}
	}

	name.s = s;
	name.len = snprintf(s + 1, 99, "%d", (*counter)++) + 1;

	cxavp = xavp_new_value(&name, &nval);

	if(!cxavp) {
		return -1;
	}

	*new = cxavp;

	return 0;
}

int pv_xbuff_get_type(
		struct sip_msg *msg, pv_param_t *param, pv_value_t *res, sr_xavp_t *avp)
{
	if(!avp)
		return pv_get_null(msg, param, res);

	switch(avp->name.s[0]) {
		case 'n':
			return pv_get_null(msg, param, res);
			break;
		case 'i':
			return pv_get_strval(msg, param, res, &xbuff_types[XBUFF_TYPE_INT]);
			break;
		case 'l':
			return pv_get_strval(
					msg, param, res, &xbuff_types[XBUFF_TYPE_LIST]);
			break;
		case 'a':
			return pv_get_strval(
					msg, param, res, &xbuff_types[XBUFF_TYPE_ATOM]);
			break;
		case 's':
			return pv_get_strval(msg, param, res, &xbuff_types[XBUFF_TYPE_STR]);
			break;
		case 't':
			return pv_get_strval(
					msg, param, res, &xbuff_types[XBUFF_TYPE_TUPLE]);
			break;
		case 'p':
			return pv_get_strval(msg, param, res, &xbuff_types[XBUFF_TYPE_PID]);
			break;
		case 'r':
			return pv_get_strval(msg, param, res, &xbuff_types[XBUFF_TYPE_REF]);
			break;
	}

	return pv_get_null(msg, param, res);
}

int pv_xbuff_set(
		struct sip_msg *msg, pv_param_t *param, int op, pv_value_t *val)
{
	str name;
	sr_xavp_t *xbuffs_root;
	sr_xavp_t *xbuff;
	sr_xavp_t *new, *old, *prv = NULL;
	sr_xavp_t *xbuff_xavp;
	sr_xval_t xbuff_val;
	pv_param_t p;
	int idx = 0;
	int idxf = 0;
	int attr = 0;

	if(param->pvn.type != PV_NAME_INTSTR
			|| !(param->pvn.u.isname.type & AVP_NAME_STR)) {
		LM_ERR("invalid variable name type\n");
		return -1;
	}

	/* xbuff name */
	name = param->pvn.u.isname.name.s;

	xbuffs_root = xavp_get_xbuffs();

	if(!xbuffs_root) {

		memset((void *)&xbuff_val, 0, sizeof(sr_xval_t));

		xbuff_val.type = SR_XTYPE_NULL;

		xbuff = xavp_add_xavp_value(
				&xbuff_list, &name, &xbuff_val, xavp_get_crt_list());

		if(!xbuff)
			goto err;
	}

	if(pv_xbuff_new_xavp(&xbuff_xavp, val, &counter, 0)) {
		LM_ERR("failed to create new value\n");
		return -1;
	}

	xbuff = xavp_get_child(&xbuff_list, &name);

	if(!xbuff) {

		xbuff_val.type = SR_XTYPE_NULL;
		xbuff_val.v.xavp = NULL;

		xbuff = xavp_add_value(&name, &xbuff_val, &xbuffs_root->val.v.xavp);

		if(!xbuff)
			goto err;
	}

	/* work on copy of index! */
	p = *param;

	/* fix index */
	attr = xbuff_get_attr_flags(p.pvi.type);
	p.pvi.type = xbuff_fix_index(p.pvi.type);

	/* get the index */
	if(pv_get_spec_index(msg, &p, &idx, &idxf)) {
		LM_ERR("invalid index\n");
		return -1;
	}

	if(xbuff_is_attr_set(attr)) {
		LM_ERR("read only attribute %.*s\n", STR_FMT(&xbuff_attr_name(attr)));
		return -1;
	}

	/* check container type */

	if(xbuff->val.v.xavp == NULL) {
		xbuff->val.type = SR_XTYPE_XAVP;
		xbuff->val.v.xavp = xbuff_xavp;
		return 0;
	}

	switch(xbuff->val.v.xavp->name.s[0]) {
		case 'l': /* list  */
		case 't': /* tuple */

			/* prepend on list when no index */
			if(xbuff_is_no_index(attr)) {
				return xavp_add(xbuff_xavp, &xbuff->val.v.xavp);
			}

			/* reset list given value */
			if(idxf == PV_IDX_ALL) {
				xavp_destroy_list(&xbuff->val.v.xavp);
				if(xbuff_xavp->val.type == SR_XTYPE_NULL) {
					/* create empty list/tuple */
					xavp_destroy_list(&xbuff_xavp);
					return 0;
				} else {
					return xavp_add(xbuff_xavp, &xbuff->val.v.xavp);
				}
			}

			/* set by index */
			old = xavp_get_nth(&xbuff->val.v.xavp, idx, &prv);
			new = xbuff_xavp;

			if(old) {
				new->next = old->next;
				if(prv) {
					prv->next = new;
				} else {
					new->next = old->next;
					xbuff->val.v.xavp = new;
				}
				old->next = NULL;
				xavp_destroy_list(&old);
			} else {
				if(prv) {
					prv->next = new;
				} else {
					xbuff->val.v.xavp = new;
				}
			}
			break;
		case 'n': /* null (empty) */
		case 's': /* string  */
		case 'a': /* atom    */
		case 'i': /* integer */
		default:  /* default */
			xavp_destroy_list(&xbuff->val.v.xavp);
			xbuff->val.v.xavp = xbuff_xavp;
			break;
	}

	return 0;

err:
	LM_ERR("failed to add value into xbuff\n");
	xavp_destroy_list(&xbuff_xavp);

	return -1;
}

int pv_xbuff_get_value(
		struct sip_msg *msg, pv_param_t *param, pv_value_t *res, sr_xavp_t *avp)
{
	static char _pv_xavp_buf[128];
	str s;

	if(!avp)
		return pv_get_null(msg, param, res);

	switch(avp->val.type) {
		case SR_XTYPE_NULL:
			return pv_get_null(msg, param, res);
			break;
		case SR_XTYPE_LONG:
			return pv_get_sintval(msg, param, res, avp->val.v.l);
			break;
		case SR_XTYPE_STR:
			switch(avp->name.s[0]) {
				case 'a':
					if(snprintf(_pv_xavp_buf, 128, "<<atom:%p>>",
							   avp->val.v.xavp)
							< 0)
						return pv_get_null(msg, param, res);
					break;
				default:
					return pv_get_strval(msg, param, res, &avp->val.v.s);
			}
			break;
		case SR_XTYPE_TIME:
			if(snprintf(_pv_xavp_buf, 128, "%lu", (long unsigned)avp->val.v.t)
					< 0)
				return pv_get_null(msg, param, res);
			break;
		case SR_XTYPE_LLONG:
			if(snprintf(_pv_xavp_buf, 128, "%lld", avp->val.v.ll) < 0)
				return pv_get_null(msg, param, res);
			break;
		case SR_XTYPE_DATA:
			switch(avp->name.s[0]) {
				case 'p':
					if(snprintf(
							   _pv_xavp_buf, 128, "<<pid:%p>>", avp->val.v.data)
							< 0)
						return pv_get_null(msg, param, res);
					break;
				case 'r':
					if(snprintf(
							   _pv_xavp_buf, 128, "<<ref:%p>>", avp->val.v.data)
							< 0)
						return pv_get_null(msg, param, res);
					break;
				default:
					if(snprintf(_pv_xavp_buf, 128, "<<binary:%p>>",
							   avp->val.v.data)
							< 0)
						return pv_get_null(msg, param, res);
			}
			break;
		case SR_XTYPE_XAVP:
			switch(avp->name.s[0]) {
				case 't':
					if(snprintf(_pv_xavp_buf, 128, "<<tuple:%p>>",
							   avp->val.v.xavp)
							< 0)
						return pv_get_null(msg, param, res);
					break;
				case 'l':
				default:
					if(snprintf(_pv_xavp_buf, 128, "<<list:%p>>",
							   avp->val.v.xavp)
							< 0)
						return pv_get_null(msg, param, res);
					//			break;
					//		default:
					//			LM_ERR("unexpected type!\n");
					//			return pv_get_null(msg, param, res);
			}
			break;
		default:
			return pv_get_null(msg, param, res);
	}
	s.s = _pv_xavp_buf;
	s.len = strlen(_pv_xavp_buf);
	return pv_get_strval(msg, param, res, &s);
}

int pv_xbuff_get(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
{
	str name;
	pv_spec_t *nsp = NULL;
	pv_name_t *pvn;
	pv_index_t *pvi;
	sr_xavp_t *xbuffs_root;
	sr_xavp_t *xbuff;
	sr_xavp_t *xavp;
	pv_param_t p;
	int idx = 0;
	int idxf = 0;
	int attr;
	int i, count;

	ei_x_buff x_buff;

	if(param == NULL) {
		LM_ERR("bad parameters\n");
		return -1;
	}

	if(param->pvn.type != PV_NAME_INTSTR
			|| !(param->pvn.u.isname.type & AVP_NAME_STR))
		return -1;

	if(param->pvn.type == PV_NAME_PVAR) {
		nsp = param->pvn.u.dname;
	}

	/* work on copy of index! */
	p = *param;

	if(nsp) {
		pvi = &nsp->pvp.pvi;
		pvn = &nsp->pvp.pvn;
	} else {
		pvi = &p.pvi;
		pvn = &p.pvn;
	}

	/* list name */
	name = pvn->u.isname.name.s;

	/* fix index */
	attr = xbuff_get_attr_flags(pvi->type);
	pvi->type = xbuff_fix_index(pvi->type);

	/* get the index */
	if(pv_get_spec_index(msg, &p, &idx, &idxf)) {
		LM_ERR("invalid index\n");
		return -1;
	}

	xbuffs_root = xavp_get_xbuffs();
	if(!xbuffs_root) {
		return pv_get_null(msg, param, res);
	}

	xbuff = xavp_get(&name, xbuffs_root->val.v.xavp);
	if(!xbuff) {
		return pv_get_null(msg, param, res);
	}

	xavp = xbuff->val.v.xavp;

	switch(xbuff_is_attr_set(attr)) {
		case XBUFF_ATTR_TYPE:
			if(xbuff_is_no_index(attr)) {
				return pv_xbuff_get_type(msg, param, res, xavp);
			} else {
				if(xavp && (xavp->name.s[0] == 'l' || xavp->name.s[0] == 't')) {
					xavp = xavp->val.v.xavp;
				}
				xavp = xavp_get_nth(&xavp, idx, NULL);
				return pv_xbuff_get_type(msg, param, res, xavp);
			}
			break;
		case XBUFF_ATTR_LENGTH:
			if(xbuff_is_no_index(attr)) {
				xavp = xbuff->val.v.xavp;
			} else {
				if(xavp && (xavp->name.s[0] == 'l' || xavp->name.s[0] == 't')) {
					xavp = xavp->val.v.xavp;
				}
				xavp = xavp_get_nth(&xavp, idx, NULL);
			}
			count = xavp ? xavp_get_count(xavp->val.v.xavp) : 0;
			return pv_get_uintval(msg, param, res, (unsigned int)count);
			break;
		case XBUFF_ATTR_FORMAT:
			if(xbuff_is_no_index(attr)) {
				xavp = xbuff->val.v.xavp;
			} else {
				if(xavp && (xavp->name.s[0] == 'l' || xavp->name.s[0] == 't')) {
					xavp = xavp->val.v.xavp;
				}
				xavp = xavp_get_nth(&xavp, idx, NULL);
			}

			/*
		 * Prints a term, in clear text, to the PV value pointed to by res.
		 * It tries to resemble the term printing in the erlang shell.
		 */
			ei_x_new_with_version(&x_buff);
			if(!xavp || xavp_encode(&x_buff, xavp, 1)) {
				ei_x_free(&x_buff);
				return pv_get_null(msg, param, res);
			}

			i = 1;
			if(ei_s_print_term(&xbuff_fmt_buff, x_buff.buff, &i) < 0) {
				LM_ERR("BUG: xbuff doesn't contain a valid term!\n");
				ei_x_free(&x_buff);
				return -1;
			}
			i = pv_get_strzval(msg, param, res, xbuff_fmt_buff);
			ei_x_free(&x_buff);
			return i;
	}

	if(!xavp) {
		return pv_get_null(msg, param, res);
	}

	/* get whole xbuff */
	if(idxf == PV_IDX_ALL) {
		return pv_xbuff_get_value(msg, param, res, xavp);
	}

	/* get by idx */
	if(xavp->name.s[0] == 'l' || xavp->name.s[0] == 't') {
		xavp = xavp->val.v.xavp;
	}

	xavp = xavp_get_nth(&xavp, idx, NULL);
	if(!xavp) {
		return pv_get_null(msg, param, res);
	}

	return pv_xbuff_get_value(msg, param, res, xavp);
}

/**
 * Recursive copy XAVPs, preserve order
 */
sr_xavp_t *xbuff_copy_xavp(sr_xavp_t *xavp)
{
	sr_xavp_t *new = NULL;
	sr_xavp_t *cp = NULL;

	if(!xavp)
		return NULL;

	while(xavp) {
		if(new) {
			new->next = xavp_new_value(&xavp->name, &xavp->val);
			new = new->next;
		} else {
			new = xavp_new_value(&xavp->name, &xavp->val);
		}

		if(!new) {
			LM_ERR("not enough memory\n");
			return cp;
		}

		if(!cp)
			cp = new;

		if(xavp->val.type == SR_XTYPE_XAVP)
			new->val.v.xavp = xbuff_copy_xavp(xavp->val.v.xavp);

		xavp = xavp->next;
	}

	return cp;
}

/**
 * XAVP extension
 */

/* copy from xavp.c (not exported from XAVP) */
sr_xavp_t *xavp_new_value(str *name, sr_xval_t *val)
{
	sr_xavp_t *avp;
	int size;
	unsigned int id;

	if(name == NULL || name->s == NULL || val == NULL)
		return NULL;
	id = get_hash1_raw(name->s, name->len);

	size = sizeof(sr_xavp_t) + name->len + 1;
	if(val->type == SR_XTYPE_STR)
		size += val->v.s.len + 1;
	avp = (sr_xavp_t *)shm_malloc(size);
	if(avp == NULL)
		return NULL;
	memset(avp, 0, size);
	avp->id = id;
	avp->name.s = (char *)avp + sizeof(sr_xavp_t);
	memcpy(avp->name.s, name->s, name->len);
	avp->name.s[name->len] = '\0';
	avp->name.len = name->len;
	memcpy(&avp->val, val, sizeof(sr_xval_t));
	if(val->type == SR_XTYPE_STR) {
		avp->val.v.s.s = avp->name.s + avp->name.len + 1;
		memcpy(avp->val.v.s.s, val->v.s.s, val->v.s.len);
		avp->val.v.s.s[val->v.s.len] = '\0';
		avp->val.v.s.len = val->v.s.len;
	}

	return avp;
}

sr_xavp_t *xavp_get_nth(sr_xavp_t **list, int idx, sr_xavp_t **prv)
{
	sr_xavp_t *avp;
	int n = 0;

	if(list && *list)
		avp = *list;
	else
		return NULL;

	while(avp) {

		if(idx == n)
			return avp;
		n++;

		if(prv)
			*prv = avp;

		avp = avp->next;
	}

	return NULL;
}

/**
 * Encode XAVPs into ei_x_buff
 */
int xavp_encode(ei_x_buff *xbuff, sr_xavp_t *xavp, int level)
{
	int n;

	while(xavp) {
		switch(xavp->name.s[0]) {
			case 'a':
				ei_x_encode_atom_len(xbuff, xavp->val.v.s.s, xavp->val.v.s.len);
				break;
			case 's':
				ei_x_encode_string_len(
						xbuff, xavp->val.v.s.s, xavp->val.v.s.len);
				break;
			case 'i':
				ei_x_encode_long(xbuff, xavp->val.v.l);
				break;
			case 't':
				n = xavp_get_count(xavp->val.v.xavp);
				ei_x_encode_tuple_header(xbuff, n);
				if(xavp_encode(xbuff, xavp->val.v.xavp, level + 1))
					return -1;
				break;
			case 'l':
				n = xavp_get_count(xavp->val.v.xavp);
				ei_x_encode_list_header(xbuff, n);
				if(xavp_encode(xbuff, xavp->val.v.xavp, level + 1))
					return -1;
				ei_x_encode_empty_list(xbuff);
				break;
			case 'p':
				ei_x_encode_pid(xbuff, xavp->val.v.data->p);
				break;
			case 'r':
				ei_x_encode_ref(xbuff, xavp->val.v.data->p);
				break;
			case 'n':
				ei_x_encode_atom(xbuff, "undefined");
				break;
			default:
				LM_ERR("BUG: unknown type for %.*s\n", STR_FMT(&xavp->name));
				return -1;
		}
		xavp = xavp->next;
	}

	return 0;
}

/**
 * Decode XAVP from ei_x_buff
 */
int xavp_decode(ei_x_buff *xbuff, int *index, sr_xavp_t **xavp, int level)
{
	int i = 0;
	int type, size, arity;
	int l;
	char _s[128];
	char _fmt[128];
	str name;
	sr_xval_t val;
	sr_xavp_t **tail;
	sr_xavp_t *new;
	char *pbuf = 0;
	erlang_pid *pid;
	erlang_ref *ref;
	erlang_fun fun;
	double d;
	char *p = NULL;
	sr_data_t *data;

	name.s = _s;

	if(!xavp || !xbuff)
		return -1;

	if(ei_get_type(xbuff->buff, index, &type, &size)) {
		LM_ERR("failed to get type\n");
		return -1;
	}


	switch(type) {
		case ERL_ATOM_EXT:
#ifdef ERL_SMALL_ATOM_EXT
		case ERL_SMALL_ATOM_EXT:
#endif
			name.len = snprintf(_s, sizeof(_s), "a%d", counter++);
			pbuf = (char *)pkg_realloc(pbuf, size + 1);

			if(!pbuf) {
				LM_ERR("not enough memory!\n");
				return -1;
			}

			if(ei_decode_atom(xbuff->buff, index, pbuf)) {
				LM_ERR("failed to decode atom\n");
				goto err;
			}

			val.type = SR_XTYPE_STR;
			val.v.s.s = pbuf;
			val.v.s.len = size;

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				goto err;
			}

			break;
		case ERL_LIST_EXT:
		case ERL_SMALL_TUPLE_EXT:
		case ERL_LARGE_TUPLE_EXT:

			name.len = snprintf(_s, sizeof(_s), "%c%d",
					type == ERL_LIST_EXT ? 'l' : 't', counter++);

			val.v.xavp = NULL;

			if(type == ERL_LIST_EXT) {
				ei_decode_list_header(xbuff->buff, index, &arity);
			} else {
				ei_decode_tuple_header(xbuff->buff, index, &arity);
			}

			if(arity == 0) {
				val.type = SR_XTYPE_NULL;
			} else {
				val.type = SR_XTYPE_XAVP;
			}

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				goto err;
			}

			tail = &(*xavp)->val.v.xavp;

			for(l = 0; l < arity; l++) {

				new = NULL;

				if(xavp_decode(xbuff, index, &new, level + 1)) {
					LM_ERR("failed to decode %.*s\n", STR_FMT(&name));
					return -1;
				}

				if(!new) {
					LM_ERR("failed to create new xavp!\n");
					goto err;
				}

				*tail = new;
				tail = &new->next;
			}

			break;
		case ERL_STRING_EXT:
			name.len = snprintf(_s, sizeof(_s), "s%d", counter++);

			pbuf = (char *)pkg_realloc(pbuf, size + 1);

			if(!pbuf) {
				LM_ERR("not enough memory!\n");
				return -1;
			}

			ei_decode_string(xbuff->buff, index, pbuf);

			val.type = SR_XTYPE_STR;
			val.v.s.s = pbuf;
			val.v.s.len = size;

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				goto err;
			}
			break;
		case ERL_SMALL_INTEGER_EXT:
			name.len = snprintf(_s, sizeof(_s), "i%d", counter++);

			ei_decode_long(xbuff->buff, index, &val.v.l);
			val.type = SR_XTYPE_LONG;

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				goto err;
			}
			break;
		case ERL_SMALL_BIG_EXT:
		case ERL_INTEGER_EXT:
			name.len = snprintf(_s, sizeof(_s), "i%d", counter++);

			if(size > sizeof(long)) {
				ei_decode_longlong(xbuff->buff, index, &val.v.ll);
				val.type = SR_XTYPE_LLONG;
			} else {
				ei_decode_long(xbuff->buff, index, &val.v.l);
				val.type = SR_XTYPE_LONG;
			}

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				goto err;
			}
			break;
		case ERL_FLOAT_EXT:
		case NEW_FLOAT_EXT:
			name.len = snprintf(_s, sizeof(_s), "s%d", counter++);

			ei_decode_double(xbuff->buff, index, &d);
			val.v.s.s = _fmt;
			val.v.s.len = snprintf(_fmt, sizeof(_fmt), "%g", d);

			val.type = SR_XTYPE_STR;

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				goto err;
			}
			break;

		case ERL_PID_EXT:
			name.len = snprintf(_s, sizeof(_s), "p%d", counter++);

			data = (sr_data_t *)shm_malloc(
					sizeof(sr_data_t) + sizeof(erlang_pid));
			if(!data) {
				LM_ERR("not enough shared memory\n");
				goto err;
			}

			memset((void *)data, 0, sizeof(sr_data_t) + sizeof(erlang_pid));

			data->p = pid = (void *)data + sizeof(sr_data_t);
			data->pfree = xbuff_data_free;

			if(ei_decode_pid(xbuff->buff, index, pid) < 0) {
				LM_ERR("failed to decode pid\n");
				shm_free(data);
				goto err;
			}

			val.type = SR_XTYPE_DATA;
			val.v.data = data;

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				shm_free(data);
				goto err;
			}

			break;
		case ERL_REFERENCE_EXT:
		case ERL_NEW_REFERENCE_EXT:
			name.len = snprintf(_s, sizeof(_s), "r%d", counter++);

			data = (sr_data_t *)shm_malloc(
					sizeof(sr_data_t) + sizeof(erlang_ref));
			if(!data) {
				LM_ERR("not enough shared memory\n");
				goto err;
			}

			memset((void *)data, 0, sizeof(sr_data_t) + sizeof(erlang_ref));

			data->p = ref = (void *)data + sizeof(sr_data_t);
			data->pfree = xbuff_data_free;

			if(ei_decode_ref(xbuff->buff, index, ref) < 0) {
				LM_ERR("failed to decode pid\n");
				shm_free(data);
				goto err;
			}

			val.type = SR_XTYPE_DATA;
			val.v.data = data;

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				shm_free(data);
				goto err;
			}

			break;
		case ERL_FUN_EXT:
			name.len = snprintf(_s, sizeof(_s), "s%d", counter++);
			i = *index;

			ei_decode_fun(xbuff->buff, index, &fun);

			if(ei_s_print_term(&p, xbuff->buff, &i) < 0) {
				LM_ERR("failed to decode fun\n");
				goto err;
			}
			val.type = SR_XTYPE_STR;
			val.v.s.s = p;
			val.v.s.len = strlen(p);

			*xavp = xavp_new_value(&name, &val);
			if(!*xavp) {
				LM_ERR("failed to create new xavp!\n");
				goto err;
			}
			break;
		default:
			LM_ERR("unknown type %c(%d)\n", (char)type, type);
	}

	pkg_free(pbuf);
	free(p);
	return 0;

err:
	pkg_free(pbuf);
	free(p);
	return -1;
}

int xavp_get_count(sr_xavp_t *list)
{
	int count = 0;

	while(list) {
		list = list->next;
		count++;
	}
	return count;
}

/*
 * free format buffer for list
 */
void free_xbuff_fmt_buff()
{
	if(xbuff_fmt_buff) {
		free(xbuff_fmt_buff);
	}
	xbuff_fmt_buff = 0;
}

void xbuff_destroy_all()
{
	sr_xavp_t *list;
	list = xavp_get_xbuffs();
	if(list)
		xavp_destroy_list(&list);
}

/* does nothing but must be executed in xavp_free[_unsafe] */
void xbuff_data_free(void *p, sr_xavp_sfree_f sfree)
{
}
