/*
 * $Id: serialize.c,v 1.4 2006/05/07 20:48:51 miconda Exp $
 *
 * sequential forking implementation
 *
 * Copyright (C) 2005 Juha Heinanen
 *
 * This file is part of openser, a free SIP server.
 *
 * openser 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
 *
 * openser 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * History:
 * -------
 *  2005-11-29 splitted from lcr module (bogdan)
 */

#include "str.h"
#include "qvalue.h"
#include "usr_avp.h"
#include "dset.h"
#include "action.h"
#include "route.h"
#include "parser/msg_parser.h"



struct serial_contact {
	str uri;
	qvalue_t q;
	unsigned short q_flag;
	int next;
};

/* usr_avp flag for sequential forking */
#define Q_FLAG            (1<<4)
/* avp alias to be used */
#define SERIAL_AVP_ALIAS  "serial_branch"

static int serial_avp_id = 0xff3434;



int init_serialization()
{
	str alias = { SERIAL_AVP_ALIAS, sizeof(SERIAL_AVP_ALIAS)-1 };

	return add_avp_galias( &alias, 0 /*type*/, (int_str)serial_avp_id );
}



/* 
 * Loads contacts in destination set into "serial_fork" AVP in reverse
 * priority order and associated each contact with Q_FLAG telling if
 * contact is the last one in its priority class.  Finally, removes
 * all branches from destination set.
 */

int serialize_branches(struct sip_msg *msg, int clean_before )
{
	static struct serial_contact contacts[MAX_BRANCHES];
	int n, last, first, i;
	str branch, *ruri;
	qvalue_t q, ruri_q;
	int_str val;
	int idx;

	/* Check if anything needs to be done */
	if (nr_branches == 0) {
		DBG("DEBUG:serialize_branches: nothing to do - no branches!\n");
		return 0;
	}

	ruri = GET_RURI(msg);
	ruri_q = get_ruri_q();

	for( idx=0 ; (branch.s=get_branch(idx,&branch.len,&q,0,0,0,0))!=0 ; idx++ ) {
		if (q != ruri_q)
			break;
	}
	if (branch.s==0) {
		DBG("DEBUG:serialize_branches: nothing to do - all same q!\n");
		return 0;
	}

	/* reset contact array */
	n = 0;

	/* Insert Request-URI to contact list */
	contacts[n].uri = *ruri;
	contacts[n].q = ruri_q;
	contacts[n].next = -1;
	last = n;
	first = n;
	n++;

	/* Insert branch URIs to contact list in increasing q order */
	for( idx=0 ; (branch.s=get_branch(idx,&branch.len,&q,0,0,0,0))!=0 ; idx++ ) {
		contacts[n].uri = branch;
		contacts[n].q = q;

		/* insert based on q */
		for( i=0 ; i!=-1 && contacts[i].q < q ; i=contacts[i].next );
		if (i==-1) {
			/* append */
			last = contacts[last].next = n;
			contacts[n].next = -1;
		} else {
			if (i==0) {
				/* first element */
				contacts[n].next = first;
				first = n;
			} else {
				/* after pos i */
				contacts[n].next = contacts[i].next;
				contacts[i].next = n;
			}
		}

		n++;
	}

	/* Assign values for q_flags */
	for( i=first ; contacts[i].next!=-1 ; i=contacts[i].next ) {
		if (contacts[i].q < contacts[contacts[i].next].q)
			contacts[contacts[i].next].q_flag = Q_FLAG;
		else
			contacts[contacts[i].next].q_flag = 0;
	}

	if (clean_before)
		destroy_avps( 0/*type*/, (int_str)serial_avp_id, 1/*all*/);

	/* Add contacts to "contacts" AVP */
	for ( i=first ; i!=-1; i=contacts[i].next ) {
		val.s = contacts[i].uri;
		if (add_avp( AVP_VAL_STR|contacts[i].q_flag, (int_str)serial_avp_id,
		val)!=0 ) {
			LOG(L_ERR,"ERROR:serialize_branches: failed to add avp\n");
			goto error;
		}
		DBG("DEBUG:serialize_branches: loaded <%s>, q=%d q_flag <%d>\n",
			val.s.s, contacts[i].q, contacts[i].q_flag);
	}

	/* Clear all branches */
	clear_branches();

	return 0;
error:
	return -1;
}



/*
 * Adds to request a destination set that includes all highest priority
 * class contacts in "serial_avp_id" AVP.   If called from a route block,
 * rewrites the request uri with first contact and adds the remaining
 * contacts as branches.  If called from failure route block, adds all
 * contacts as brances.  Removes added contacts from "serial_avp_id" AVP.
 */

int next_branches( struct sip_msg *msg)
{
	struct usr_avp *avp, *prev;
	int_str val;
	struct action act;
	int rval;

	if ( route_type!=REQUEST_ROUTE && route_type!=FAILURE_ROUTE ) {
		/* unsupported route type */
		LOG(L_ERR,"ERROR:next_branch: called from unsupported route type %d\n",
			route_type);
		goto error;
	}

	/* Find first avp  */
	avp = search_first_avp( 0, (int_str)serial_avp_id, &val, 0);
	if (!avp) {
		DBG("DEBUG:next_branches: no AVPs -- we are done!\n");
		goto error;
	}

	if ( route_type == REQUEST_ROUTE) {
		/* Set Request-URI */
		act.type = SET_URI_T;
		act.p1_type = STRING_ST;
		act.p1.string = val.s.s;
		rval = do_action(&act, msg);
		if (rval != 1)
			goto error1;
		DBG("DEBUG:next_branches: R-URI is <%s>\n", val.s.s);
		if (avp->flags & Q_FLAG) {
			destroy_avp(avp);
			return 0;
		}
		if ( (avp=search_next_avp(avp, &val))==0 )
			return 0;
		/* continue */
	}

	/* Append branches until out of branches or Q_FLAG is set */
	do {
		act.type = APPEND_BRANCH_T;
		act.p1_type = STRING_ST;
		act.p1.string = val.s.s;
		act.p2_type = NUMBER_ST;
		act.p2.number = 0;
		rval = do_action(&act, msg);
		if (rval != 1) {
			LOG(L_ERR, "ERRORL:next_branches: do_action failed "
				"with return value <%d>\n", rval);
			goto error1;
			}
		DBG("DEBUG:next_branches: branch is <%s>\n", val.s.s);

		/* continuu ? */
		if (avp->flags & Q_FLAG) {
			destroy_avp(avp);
			return 0;
		}
		prev = avp;
		avp=search_next_avp(prev, &val);
		destroy_avp(prev);
	}while ( avp );

	return 0;
error1:
	destroy_avp(avp);
error:
	return -1;
}
