/*
 * Copyright (C) 2004-2006 Voice Sistem SRL
 * Copyright (C) 2008 Juha Heinanen
 *
 * 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.
 *
 *
 */

/*! \file
 * \ingroup acc
 * \brief Acc:: Extra attributes
 *
 * - Module: \ref acc
 */


#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/usr_avp.h"
#include "../../core/mem/mem.h"
#include "acc_api.h"
#include "acc_extra.h"

#define EQUAL '='
#define SEPARATOR ';'

/* here we copy the strings returned by int2str (which uses a static buffer) */
static char *int_buf = NULL;

struct acc_extra *parse_acc_leg(char *extra_str)
{
	struct acc_extra *legs;
	struct acc_extra *it;
	int n;

	legs = parse_acc_extra(extra_str);
	if(legs == 0) {
		LM_ERR("failed to parse extra leg\n");
		return 0;
	}

	/* check the type and len */
	for(it = legs, n = 0; it; it = it->next) {
		if(it->spec.type != PVT_AVP) {
			LM_ERR("only AVP are accepted as leg info\n");
			destroy_extras(legs);
			return 0;
		}
		n++;
		if(n > MAX_ACC_LEG) {
			LM_ERR("too many leg info; MAX=%d\n", MAX_ACC_LEG);
			destroy_extras(legs);
			return 0;
		}
	}

	return legs;
}


struct acc_extra *parse_acc_extra(char *extra_str)
{
	struct acc_extra *head;
	struct acc_extra *tail;
	struct acc_extra *extra;
	char *foo;
	char *s;
	int n;
	str stmp;

	n = 0;
	head = 0;
	extra = 0;
	tail = 0;
	s = extra_str;

	if(s == 0) {
		LM_ERR("null string received\n");
		goto error;
	}

	while(*s) {
		/* skip white spaces */
		while(*s && isspace((int)*s))
			s++;
		if(*s == 0)
			goto parse_error;
		if(n == acc_extra_size) {
			LM_ERR("too many extras -> please increase the internal buffer\n");
			goto error;
		}
		extra = (struct acc_extra *)pkg_malloc(sizeof(struct acc_extra));
		if(extra == 0) {
			PKG_MEM_ERROR;
			goto error;
		}
		memset(extra, 0, sizeof(struct acc_extra));

		/* link the new extra at the end */
		if(tail == 0) {
			head = extra;
		} else {
			tail->next = extra;
		}
		tail = extra;
		n++;

		/* get name */
		foo = s;
		while(*s && !isspace((int)*s) && EQUAL != *s)
			s++;
		if(*s == 0) {
			LM_ERR("unexpected end of string\n");
			goto parse_error;
		}
		if(*s == EQUAL) {
			extra->name.len = (s++) - foo;
		} else {
			extra->name.len = (s++) - foo;
			/* skip spaces */
			while(*s && isspace((int)*s))
				s++;
			if(*s != EQUAL) {
				LM_ERR("unexpected char '%c' instead of '='\n", *s);
				goto parse_error;
			}
			s++;
		}
		extra->name.s = foo;

		/* skip spaces */
		while(*s && isspace((int)*s))
			s++;

		/* get value type */
		stmp.s = s;
		stmp.len = strlen(s);
		if((foo = pv_parse_spec(&stmp, &extra->spec)) == 0) {
			LM_ERR("failed to parse variable name\n");
			goto parse_error;
		}
		s = foo;

		/* skip spaces */
		while(*s && isspace((int)*s))
			s++;
		if(*s && (*(s++) != SEPARATOR || *s == 0)) {
			LM_ERR("unexpected char at end of name=var group\n");
			goto parse_error;
		}
	}

	/* go throught all extras and make the names null terminated */
	for(extra = head; extra; extra = extra->next)
		extra->name.s[extra->name.len] = 0;

	return head;
parse_error:
	LM_ERR("parse failed in <%s> "
		   "around position %d\n",
			extra_str, (int)(long)(s - extra_str));
error:
	LM_ERR("error\n");
	destroy_extras(head);
	return 0;
}


void destroy_extras(struct acc_extra *extra)
{
	struct acc_extra *foo;

	while(extra) {
		foo = extra;
		extra = extra->next;
		pkg_free(foo);
	}
}


/*! \brief converts the name of the extra from str to integer
 * and stores it over str.len ; str.s is freed and made zero
 */
int extra2int(struct acc_extra *extra, int *attrs)
{
	unsigned int ui;
	int i;

	for(i = 0; extra; i++, extra = extra->next) {
		if(str2int(&extra->name, &ui) != 0) {
			LM_ERR("<%s> is not a number\n", extra->name.s);
			return -1;
		}
		attrs[i] = (int)ui;
	}
	return i;
}

int extra2strar(struct acc_extra *extra, struct sip_msg *rq, str *val_arr,
		int *int_arr, char *type_arr)
{
	pv_value_t value;
	int n;
	int i;

	n = 0;
	i = 0;

	while(extra) {
		/* get the value */
		if(pv_get_spec_value(rq, &extra->spec, &value) != 0) {
			LM_ERR("failed to get '%.*s'\n", extra->name.len, extra->name.s);
		}

		/* check for overflow */
		if(n == acc_extra_size) {
			LM_WARN("array to short -> omitting extras for accounting\n");
			goto done;
		}

		if(value.flags & PV_VAL_NULL) {
			/* convert <null> to empty to have consistency */
			val_arr[n].s = 0;
			val_arr[n].len = 0;
			type_arr[n] = TYPE_NULL;
		} else {
			val_arr[n].s = (char *)pkg_malloc(value.rs.len);
			if(val_arr[n].s == NULL) {
				PKG_MEM_ERROR;
				/* Cleanup already allocated memory and
				 * return that we didn't do anything */
				for(i = 0; i < n; i++) {
					if(NULL != val_arr[i].s) {
						pkg_free(val_arr[i].s);
						val_arr[i].s = NULL;
					}
				}
				n = 0;
				goto done;
			}
			memcpy(val_arr[n].s, value.rs.s, value.rs.len);
			val_arr[n].len = value.rs.len;
			if(value.flags & PV_VAL_INT) {
				int_arr[n] = value.ri;
				type_arr[n] = TYPE_INT;
			} else {
				type_arr[n] = TYPE_STR;
			}
		}
		LM_DBG("type of the extra attr[%d] set to: %d\n", n, type_arr[n]);
		n++;

		extra = extra->next;
	}

done:
	return n;
}

int extra2strar_dlg_only(struct acc_extra *extra, struct dlg_cell *dlg,
		str *val_arr, int *int_arr, char *type_arr,
		const struct dlg_binds *p_dlgb)
{
	//string value;
	str dval = {0};
	int n = 0;
	int i;

	if(!dlg || !val_arr || !int_arr || !type_arr || !p_dlgb) {
		LM_ERR("invalid input parameter!\n");
		return 0;
	}

	while(extra) {

		/* check for overflow */
		if(n == acc_extra_size) {
			LM_WARN("array to short -> omitting extras for accounting\n");
			goto done;
		}

		val_arr[n].s = 0;
		val_arr[n].len = 0;
		type_arr[n] = TYPE_NULL;

		str key = extra->spec.pvp.pvn.u.isname.name.s;
		if(key.len == 0 || !key.s) {
			n++;
			extra = extra->next;
			continue;
		}
		/* get the dialog var value */
		p_dlgb->get_dlg_varval(dlg, &key, &dval);

		if(dval.s) {
			val_arr[n].s = (char *)pkg_malloc(dval.len + 1);
			if(val_arr[n].s == NULL) {
				PKG_MEM_ERROR;
				/* cleanup already allocated memory and
				 * return that we didn't do anything */
				for(i = 0; i < n; i++) {
					if(NULL != val_arr[i].s) {
						pkg_free(val_arr[i].s);
						val_arr[i].s = NULL;
					}
				}
				n = 0;
				goto done;
			}
			memcpy(val_arr[n].s, dval.s, dval.len);
			val_arr[n].s[dval.len] = '\0';
			val_arr[n].len = dval.len;
			type_arr[n] = TYPE_STR;
		}

		n++;
		extra = extra->next;
	}
done:
	return n;
}

int legs2strar(struct acc_extra *legs, struct sip_msg *rq, str *val_arr,
		int *int_arr, char *type_arr, int start)
{
	static struct usr_avp *avp[MAX_ACC_LEG];
	static struct search_state st[MAX_ACC_LEG];
	unsigned short name_type;
	int_str name;
	int_str value;
	int n;
	int found;
	int r;

	found = 0;
	r = 0;

	for(n = 0; legs; legs = legs->next, n++) {
		/* search for the AVP */
		if(start) {
			if(pv_get_avp_name(rq, &(legs->spec.pvp), &name, &name_type) < 0)
				goto exit;
			avp[n] = search_first_avp(name_type, name, &value, st + n);
		} else {
			avp[n] = search_next_avp(st + n, &value);
		}

		/* set new leg record */
		if(avp[n]) {
			found = 1;
			/* get its value */
			if(avp[n]->flags & AVP_VAL_STR) {
				val_arr[n] = value.s;
				type_arr[n] = TYPE_STR;
			} else {
				val_arr[n].s = int2bstr(value.n, int_buf + r * INT2STR_MAX_LEN,
						&val_arr[n].len);
				r++;
				int_arr[n] = value.n;
				type_arr[n] = TYPE_INT;
			}
		} else {
			val_arr[n].s = 0;
			val_arr[n].len = 0;
			type_arr[n] = TYPE_NULL;
		}
	}

	if(found || start)
		return n;
exit:
	return 0;
}

int acc_extra_arrays_alloc(void)
{
	int acc_int_buf_size;

	if(acc_extra_size < MAX_ACC_LEG) {
		acc_int_buf_size = MAX_ACC_LEG;
	} else {
		acc_int_buf_size = acc_extra_size;
	}

	if((int_buf = pkg_malloc(
				(INT2STR_MAX_LEN * acc_int_buf_size) * sizeof(char)))
			== NULL) {
		LM_ERR("failed to alloc int_buf\n");
		return -1;
	}

	return 1;
}

void acc_extra_arrays_free(void)
{
	if(int_buf) {
		pkg_free(int_buf);
	}

	return;
}
