/**
 * $Id: dispatcher.c 1827 2007-03-12 15:22:53Z bogdan_iancu $
 *
 * dispatcher module -- stateless load balancing
 *
 * Copyright (C) 2004-2005 FhG Fokus
 * Copyright (C) 2006 Voice Sistem SRL
 *
 * 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
 * -------
 * 2004-07-31  first version, by daniel
 * 
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

#include "../../mi/mi.h"
#include "../../sr_module.h"
#include "../../dprint.h"
#include "../../error.h"
#include "../../ut.h"
#include "../../route.h"
#include "../../mem/mem.h"

#include "dispatch.h"

MODULE_VERSION

/** parameters */
char *dslistfile = CFG_DIR"dispatcher.list";
int  ds_force_dst   = 0;
int  ds_flags       = 0; 
int  ds_use_default = 0; 
char*  dst_avp_param     = NULL;
char*  grp_avp_param     = NULL;
char*  cnt_avp_param     = NULL;
int_str dst_avp_name;
unsigned short dst_avp_type;
int_str grp_avp_name;
unsigned short grp_avp_type;
int_str cnt_avp_name;
unsigned short cnt_avp_type;


/** module functions */
static int mod_init(void);
static int child_init(int);

static int w_ds_select_dst(struct sip_msg*, char*, char*);
static int w_ds_select_domain(struct sip_msg*, char*, char*);
static int w_ds_next_dst(struct sip_msg*, char*, char*);
static int w_ds_next_domain(struct sip_msg*, char*, char*);
static int w_ds_mark_dst0(struct sip_msg*, char*, char*);
static int w_ds_mark_dst1(struct sip_msg*, char*, char*);

void destroy(void);

static int ds_fixup(void** param, int param_no);
static int ds_warn_fixup(void** param, int param_no);

struct mi_root* ds_mi_set(struct mi_root* cmd, void* param);
struct mi_root* ds_mi_list(struct mi_root* cmd, void* param);

static cmd_export_t cmds[]={
	{"ds_select_dst",    w_ds_select_dst,    2, ds_fixup, REQUEST_ROUTE},
	{"ds_select_domain", w_ds_select_domain, 2, ds_fixup, REQUEST_ROUTE},
	{"ds_next_dst",      w_ds_next_dst,      0, ds_warn_fixup, FAILURE_ROUTE},
	{"ds_next_domain",   w_ds_next_domain,   0, ds_warn_fixup, FAILURE_ROUTE},
	{"ds_mark_dst",      w_ds_mark_dst0,     0, ds_warn_fixup, FAILURE_ROUTE},
	{"ds_mark_dst",      w_ds_mark_dst1,     1, ds_warn_fixup, FAILURE_ROUTE},
	{0,0,0,0,0}
};


static param_export_t params[]={
	{"list_file",      STR_PARAM, &dslistfile},
	{"force_dst",      INT_PARAM, &ds_force_dst},
	{"flags",          INT_PARAM, &ds_flags},
	{"use_default",    INT_PARAM, &ds_use_default},
	{"dst_avp",        STR_PARAM, &dst_avp_param},
	{"grp_avp",        STR_PARAM, &grp_avp_param},
	{"cnt_avp",        STR_PARAM, &cnt_avp_param},
	{0,0,0}
};


static mi_export_t mi_cmds[] = {
	{ "ds_set_state",   ds_mi_set,   0,                 0,  0 },
	{ "ds_list",        ds_mi_list,  MI_NO_INPUT_FLAG,  0,  0 },
	{ 0, 0, 0, 0, 0}
};


/** module exports */
struct module_exports exports= {
	"dispatcher",
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,
	params,
	0,          /* exported statistics */
	mi_cmds,    /* exported MI functions */
	0,          /* exported pseudo-variables */
	mod_init,   /* module initialization function */
	(response_function) 0,
	(destroy_function) destroy,
	child_init  /* per-child init function */
};

/**
 * init module function
 */
static int mod_init(void)
{
	xl_spec_t avp_spec;
	DBG("DISPATCHER: initializing ...\n");

	if(ds_load_list(dslistfile)!=0)
	{
		LOG(L_ERR, "DISPATCHER:mod_init:ERROR -- couldn't load list file\n");
		return -1;
	}
	
	if (dst_avp_param && *dst_avp_param) {
		if (xl_parse_spec(dst_avp_param, &avp_spec,
					XL_THROW_ERROR|XL_DISABLE_MULTI|XL_DISABLE_COLORS)==0
				|| avp_spec.type!=XL_AVP) {
			LOG(L_ERR, "ERROR:DISPATCHER:mod_init: malformed or non AVP %s "
				"AVP definition\n", dst_avp_param);
			return -1;
		}

		if(xl_get_avp_name(0, &avp_spec, &dst_avp_name, &dst_avp_type)!=0)
		{
			LOG(L_ERR, "ERROR:DISPATCHER:mod_init: [%s]- invalid "
				"AVP definition\n", dst_avp_param);
			return -1;
		}
	} else {
		dst_avp_name.n = 0;
		dst_avp_type = 0;
	}
	if (grp_avp_param && *grp_avp_param) {
		if (xl_parse_spec(grp_avp_param, &avp_spec,
					XL_THROW_ERROR|XL_DISABLE_MULTI|XL_DISABLE_COLORS)==0
				|| avp_spec.type!=XL_AVP) {
			LOG(L_ERR, "ERROR:DISPATCHER:mod_init: malformed or non AVP %s "
				"AVP definition\n", grp_avp_param);
			return -1;
		}

		if(xl_get_avp_name(0, &avp_spec, &grp_avp_name, &grp_avp_type)!=0)
		{
			LOG(L_ERR, "ERROR:DISPATCHER:mod_init: [%s]- invalid "
				"AVP definition\n", grp_avp_param);
			return -1;
		}
	} else {
		grp_avp_name.n = 0;
		grp_avp_type = 0;
	}
	if (cnt_avp_param && *cnt_avp_param) {
		if (xl_parse_spec(cnt_avp_param, &avp_spec,
					XL_THROW_ERROR|XL_DISABLE_MULTI|XL_DISABLE_COLORS)==0
				|| avp_spec.type!=XL_AVP) {
			LOG(L_ERR, "ERROR:DISPATCHER:mod_init: malformed or non AVP %s "
				"AVP definition\n", cnt_avp_param);
			return -1;
		}

		if(xl_get_avp_name(0, &avp_spec, &cnt_avp_name, &cnt_avp_type)!=0)
		{
			LOG(L_ERR, "ERROR:DISPATCHER:mod_init: [%s]- invalid "
				"AVP definition\n", cnt_avp_param);
			return -1;
		}
	} else {
		cnt_avp_name.n = 0;
		cnt_avp_type = 0;
	}

	return 0;
}

/**
 * Initialize children
 */
static int child_init(int rank)
{
	DBG("DISPATCHER:init_child #%d / pid <%d>\n", rank, getpid());
	return 0;
}

static inline int ds_get_ivalue(struct sip_msg* msg, ds_param_p dp, int *val)
{
	xl_value_t value;
	if(dp->type==0) {
		*val = dp->v.id;
		return 0;
	}
	
	DBG("DISPATCHER:ds_get_ivalue: searching %d %d %d\n", dp->v.sp.type,
			dp->v.sp.p.ind, dp->v.sp.p.val.len);
	if(xl_get_spec_value(msg, &dp->v.sp, &value, 0)!=0 
			|| value.flags&XL_VAL_NULL || !(value.flags&XL_VAL_INT))
	{
		LOG(L_ERR,
			"DISPATCHER:ds_get_ivalue: no AVP found (error in scripts)\n");
		return -1;
	}
	*val = value.ri;
	return 0;
}

/**
 *
 */
static int w_ds_select_dst(struct sip_msg* msg, char* set, char* alg)
{
	int a, s;
	
	if(msg==NULL)
		return -1;
	if(ds_get_ivalue(msg, (ds_param_p)set, &s)!=0)
	{
		LOG(L_ERR,
			"DISPATCHER:w_ds_select_dst: no set value\n");
		return -1;
	}
	if(ds_get_ivalue(msg, (ds_param_p)alg, &a)!=0)
	{
		LOG(L_ERR,
			"DISPATCHER:ds_get_ivalue: no alg value\n");
		return -1;
	}

	return ds_select_dst(msg, s, a, 0 /*set dst uri*/);
}

/**
 *
 */
static int w_ds_select_domain(struct sip_msg* msg, char* set, char* alg)
{
	int a, s;
	if(msg==NULL)
		return -1;

	if(ds_get_ivalue(msg, (ds_param_p)set, &s)!=0)
	{
		LOG(L_ERR,
			"DISPATCHER:w_ds_select_dst: no set value\n");
		return -1;
	}
	if(ds_get_ivalue(msg, (ds_param_p)alg, &a)!=0)
	{
		LOG(L_ERR,
			"DISPATCHER:ds_get_ivalue: no alg value\n");
		return -1;
	}

	return ds_select_dst(msg, s, a, 1/*set host port*/);
}

/**
 *
 */
static int w_ds_next_dst(struct sip_msg *msg, char *str1, char *str2)
{
	return ds_next_dst(msg, 0/*set dst uri*/);
}

/**
 *
 */
static int w_ds_next_domain(struct sip_msg *msg, char *str1, char *str2)
{
	return ds_next_dst(msg, 1/*set host port*/);
}

/**
 *
 */
static int w_ds_mark_dst0(struct sip_msg *msg, char *str1, char *str2)
{
	return ds_mark_dst(msg, 0);
}

/**
 *
 */
static int w_ds_mark_dst1(struct sip_msg *msg, char *str1, char *str2)
{
	if(str1 && (str1[0]=='i' || str1[0]=='I' || str1[0]=='0'))
		return ds_mark_dst(msg, 0);
	else
		return ds_mark_dst(msg, 1);
}

/**
 * destroy function
 */
void destroy(void)
{
	DBG("DISPATCHER: destroy module ...\n");
	ds_destroy_list();
}

/**
 *
 */
static int ds_fixup(void** param, int param_no)
{
	int err;
	ds_param_p dsp;
	
	if(param_no==1 || param_no==2)
	{
		dsp = (ds_param_p)pkg_malloc(sizeof(ds_param_t));
		if(dsp == NULL)
		{
			LOG(L_ERR, "DISPATCHER:ds_fixup: no more memory\n");
			return E_UNSPEC;
		}
		memset(dsp, 0, sizeof(ds_param_t));
		if(((char*)(*param))[0]=='$')
		{
			dsp->type = 1;
			if(xl_parse_spec((char*)*param, &dsp->v.sp,
					XL_THROW_ERROR|XL_DISABLE_MULTI|XL_DISABLE_COLORS)==NULL
				|| dsp->v.sp.type!=XL_AVP)
			{
				LOG(L_ERR,
					"DISPATCHER:ds_fixup: Unsupported User Field identifier\n");
				return E_UNSPEC;
			}
		} else {
			dsp->type = 0;
			dsp->v.id = str2s(*param, strlen(*param), &err);
			if (err == 0)
			{
				pkg_free(*param);
			} else {
				LOG(L_ERR, "DISPATCHER:ds_fixup: Bad number <%s>\n",
				    (char*)(*param));
				return E_UNSPEC;
			}
		}
		*param = (void*)dsp;
	}
	return 0;
}

static int ds_warn_fixup(void** param, int param_no)
{
	if(dst_avp_param==NULL || grp_avp_param == NULL || cnt_avp_param == NULL)
	{
		LOG(L_ERR, "DISPATCHER:ds_fixup: failover functions used, but"
				" AVPs paraamters required are NULL -- feature disabled\n");
	}
	return 0;
}

/************************** MI STUFF ************************/

struct mi_root* ds_mi_set(struct mi_root* cmd_tree, void* param)
{
	str sp;
	int ret;
	unsigned int group, state;
	struct mi_node* node;

	node = cmd_tree->node.kids;
	if(node == NULL)
		return init_mi_tree( 400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
	sp = node->value;
	if(sp.len<=0 || sp.s == NULL)
	{
		LOG(L_ERR, "DISPATCHER:ds_mi_set: bad state value\n");
		return init_mi_tree( 500, "bad state value", 15);
	}

	state = 1;
	if(sp.s[0]=='0' || sp.s[0]=='I' || sp.s[0]=='i')
		state = 0;
	node = node->next;
	if(node == NULL)
		return init_mi_tree( 400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
	sp = node->value;
	if(sp.s == NULL)
	{
		return init_mi_tree(500, "group not found", 15);
	}

	if(str2int(&sp, &group))
	{
		LOG(L_ERR, "DISPATCHER:ds_mi_set: bad group value\n");
		return init_mi_tree( 500, "bad group value", 16);
	}

	node= node->next;
	if(node == NULL)
		return init_mi_tree( 400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);

	sp = node->value;
	if(sp.s == NULL)
	{
		return init_mi_tree(500,"address not found", 18 );
	}

	if(state==1)
		ret = ds_set_state(group, &sp, DS_INACTIVE_DST, 0);
	else
		ret = ds_set_state(group, &sp, DS_INACTIVE_DST, 1);

	if(ret!=0)
	{
		return init_mi_tree( 404, "destination not found", 21);
	}

	return init_mi_tree( 200, MI_OK_S, MI_OK_LEN);
}




struct mi_root* ds_mi_list(struct mi_root* cmd_tree, void* param)
{
	struct mi_root* rpl_tree;

	rpl_tree = init_mi_tree(200, MI_OK_S, MI_OK_LEN);
	if (rpl_tree==NULL)
		return 0;

	if( ds_print_mi_list(&rpl_tree->node)< 0 )
	{
		LOG(L_ERR,"ERROR:mi_ps: failed to add node\n");
		free_mi_tree(rpl_tree);
		return 0;
	}

	return rpl_tree;
}

