/*
 * Copyright (C) 2007 1&1 Internet AG
 *
 * 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 <string.h>

#include "../../core/mem/shm_mem.h"
#include "../../core/sr_module.h"
#include "../../core/mem/mem.h"
#include "../../core/usr_avp.h"
#include "../../core/locking.h"
#include "../../core/error.h"
#include "../../core/ut.h"
#include "../../core/mod_fix.h"
#include "../../core/rpc_lookup.h"


#include "db_matrix.h"

MODULE_VERSION




#define MAXCOLS 1000




str matrix_db_url = str_init(DEFAULT_RODB_URL);




/**
 * Generic parameter that holds a string, an int or an pseudo-variable
 * @todo replace this with gparam_t
 */
struct multiparam_t {
	enum {
		MP_INT,
		MP_STR,
		MP_AVP,
		MP_PVE,
	} type;
	union {
		int n;
		str s;
		struct {
			unsigned short flags;
			int_str name;
		} a;
		pv_elem_t *p;
	} u;
};



int matrix_rpc_init(void);

/* ---- fixup functions: */
static int matrix_fixup(void** param, int param_no);

/* ---- exported commands: */
static int lookup_matrix(struct sip_msg *msg, struct multiparam_t *_first, struct multiparam_t *_second, struct multiparam_t *_dstavp);

/* ---- module init functions: */
static int mod_init(void);
static int child_init(int rank);
static void mod_destroy(void);


static cmd_export_t cmds[]={
	{ "matrix", (cmd_function)lookup_matrix, 3, matrix_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE },
	{ 0, 0, 0, 0, 0, 0}
};




static param_export_t params[] = {
	matrix_DB_URL
		matrix_DB_TABLE
		matrix_DB_COLS
		{ 0, 0, 0}
};




struct module_exports exports = {
	"matrix",        /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,            /* cmd (cfg function) exports */
	params,          /* param exports */
	0,               /* RPC method exports */
	0,               /* pseudo-variables exports */
	0,               /* response handling function */
	mod_init,        /* module init function */
	child_init,      /* per-child init function */
	mod_destroy      /* module destroy function */
};




struct first_t {
	struct first_t *next;
	int id;
	short int second_list[MAXCOLS+1];
};




struct matrix_t {
	struct first_t *head;
};




static gen_lock_t *lock = NULL;
static struct matrix_t *matrix = NULL;




/**
 * fixes the module functions' parameters if it is a phone number.
 * supports string, pseudo-variables and AVPs.
 *
 * @param param the parameter
 *
 * @return 0 on success, -1 on failure
 */
static int mp_fixup(void ** param) {
	pv_spec_t avp_spec;
	struct multiparam_t *mp;
	str s;

	mp = (struct multiparam_t *)pkg_malloc(sizeof(struct multiparam_t));
	if (mp == NULL) {
		LM_ERR("out of pkg memory\n");
		return -1;
	}
	memset(mp, 0, sizeof(struct multiparam_t));

	s.s = (char *)(*param);
	s.len = strlen(s.s);

	if (s.s[0]!='$') {
		/* This is string */
		mp->type=MP_STR;
		mp->u.s=s;
	}
	else {
		/* This is a pseudo-variable */
		if (pv_parse_spec(&s, &avp_spec)==0) {
			LM_ERR("pv_parse_spec failed for '%s'\n", (char *)(*param));
			pkg_free(mp);
			return -1;
		}
		if (avp_spec.type==PVT_AVP) {
			/* This is an AVP - could be an id or name */
			mp->type=MP_AVP;
			if(pv_get_avp_name(0, &(avp_spec.pvp), &(mp->u.a.name), &(mp->u.a.flags))!=0) {
				LM_ERR("Invalid AVP definition <%s>\n", (char *)(*param));
				pkg_free(mp);
				return -1;
			}
		} else {
			mp->type=MP_PVE;
			if(pv_parse_format(&s, &(mp->u.p))<0) {
				LM_ERR("pv_parse_format failed for '%s'\n", (char *)(*param));
				pkg_free(mp);
				return -1;
			}
		}
	}
	*param = (void*)mp;

	return 0;
}




/**
 * fixes the module functions' parameters in case of AVP names.
 *
 * @param param the parameter
 *
 * @return 0 on success, -1 on failure
 */
static int avp_name_fixup(void ** param) {
	pv_spec_t avp_spec;
	struct multiparam_t *mp;
	str s;

	s.s = (char *)(*param);
	s.len = strlen(s.s);
	if (s.len <= 0) return -1;
	if (pv_parse_spec(&s, &avp_spec)==0 || avp_spec.type!=PVT_AVP) {
		LM_ERR("Malformed or non AVP definition <%s>\n", (char *)(*param));
		return -1;
	}

	mp = (struct multiparam_t *)pkg_malloc(sizeof(struct multiparam_t));
	if (mp == NULL) {
		LM_ERR("out of pkg memory\n");
		return -1;
	}
	memset(mp, 0, sizeof(struct multiparam_t));

	mp->type=MP_AVP;
	if(pv_get_avp_name(0, &(avp_spec.pvp), &(mp->u.a.name), &(mp->u.a.flags))!=0) {
		LM_ERR("Invalid AVP definition <%s>\n", (char *)(*param));
		pkg_free(mp);
		return -1;
	}

	*param = (void*)mp;

	return 0;
}




static int matrix_fixup(void** param, int param_no)
{
	if (param_no == 1) {
		/* source id */
		if (mp_fixup(param) < 0) {
			LM_ERR("cannot fixup parameter %d\n", param_no);
			return -1;
		}
	}
	else if (param_no == 2) {
		/* destination id */
		if (mp_fixup(param) < 0) {
			LM_ERR("cannot fixup parameter %d\n", param_no);
			return -1;
		}
	}
	else if (param_no == 3) {
		/* destination avp name */
		if (avp_name_fixup(param) < 0) {
			LM_ERR("cannot fixup parameter %d\n", param_no);
			return -1;
		}
	}

	return 0;
}




static void matrix_clear(void)
{
	struct first_t *srcitem;
	if (matrix) {
		while (matrix->head) {
			srcitem = matrix->head;
			matrix->head = srcitem->next;
			shm_free(srcitem);
		}
	}
}




static int matrix_insert(int first, short int second, int res)
{
	struct first_t *srcitem;
	int i;

	if ((second<0) || (second>MAXCOLS)) {
		LM_ERR("invalid second value %d\n", second);
		return -1;
	}
	LM_DBG("searching for %d, %d\n", first, second);
	if (matrix) {
		srcitem = matrix->head;
		while (srcitem) {
			if (srcitem->id == first) {
				srcitem->second_list[second] = res;
				LM_DBG("inserted (%d, %d, %d)\n", first, second, res);
				return 0;
			}
			srcitem = srcitem->next;
		}
		/* not found */
		srcitem = shm_malloc(sizeof(struct first_t));
		if (srcitem == NULL) {
			LM_ERR("out of shared memory.\n");
			return -1;
		}
		memset(srcitem, 0, sizeof(struct first_t));

		/* Mark all new cells as empty */
		for (i=0; i<=MAXCOLS; i++) srcitem->second_list[i] = -1;

		srcitem->next = matrix->head;
		srcitem->id = first;
		srcitem->second_list[second] = res;
		matrix->head = srcitem;
	}

	LM_DBG("inserted new row for (%d, %d, %d)\n", first, second, res);
	return 0;
}




/* Returns the res id if the matrix contains an entry for the given indices, -1 otherwise.
*/
static int internal_lookup(int first, short int second)
{
	struct first_t *item;

	if ((second<0) || (second>MAXCOLS)) {
		LM_ERR("invalid second value %d\n", second);
		return -1;
	}

	if (matrix) {
		item = matrix->head;
		while (item) {
			if (item->id == first) {
				return item->second_list[second];
			}
			item = item->next;
		}
	}

	return -1;
}




static int lookup_matrix(struct sip_msg *msg, struct multiparam_t *_srctree, struct multiparam_t *_second, struct multiparam_t *_dstavp)
{
	int first;
	int second;
	struct usr_avp *avp;
	int_str avp_val;

	switch (_srctree->type) {
		case MP_INT:
			first = _srctree->u.n;
			break;
		case MP_AVP:
			avp = search_first_avp(_srctree->u.a.flags, _srctree->u.a.name, &avp_val, 0);
			if (!avp) {
				LM_ERR("cannot find srctree AVP\n");
				return -1;
			}
			if ((avp->flags&AVP_VAL_STR)) {
				LM_ERR("cannot process string value in srctree AVP\n");
				return -1;
			}
			else first = avp_val.n;
			break;
		default:
			LM_ERR("invalid srctree type\n");
			return -1;
	}

	switch (_second->type) {
		case MP_INT:
			second = _second->u.n;
			break;
		case MP_AVP:
			avp = search_first_avp(_second->u.a.flags, _second->u.a.name, &avp_val, 0);
			if (!avp) {
				LM_ERR("cannot find second_value AVP\n");
				return -1;
			}
			if ((avp->flags&AVP_VAL_STR)) {
				LM_ERR("cannot process string value in second_value AVP\n");
				return -1;
			}
			else second = avp_val.n;
			break;
		default:
			LM_ERR("invalid second_value type\n");
			return -1;
	}


	/* critical section start: avoids dirty reads when updating d-tree */
	lock_get(lock);

	avp_val.n=internal_lookup(first, second);

	/* critical section end */
	lock_release(lock);

	if (avp_val.n<0) {
		LM_INFO("lookup failed\n");
		return -1;
	}

	/* set avp ! */
	if (add_avp(_dstavp->u.a.flags, _dstavp->u.a.name, avp_val)<0) {
		LM_ERR("add AVP failed\n");
		return -1;
	}
	LM_INFO("result from lookup: %d\n", avp_val.n);
	return 1;
}




/**
 * Rebuild matrix using database entries
 * \return negative on failure, positive on success, indicating the number of matrix entries
 */
static int db_reload_matrix(void)
{
	db_key_t columns[3] = { &matrix_first_col, &matrix_second_col, &matrix_res_col };
	db1_res_t *res;
	int i;
	int n = 0;

	if (matrix_dbf.use_table(matrix_dbh, &matrix_table) < 0) {
		LM_ERR("cannot use table '%.*s'.\n", matrix_table.len, matrix_table.s);
		return -1;
	}
	if (matrix_dbf.query(matrix_dbh, NULL, NULL, NULL, columns, 0, 3, NULL, &res) < 0) {
		LM_ERR("error while executing query.\n");
		return -1;
	}

	/* critical section start: avoids dirty reads when updating d-tree */
	lock_get(lock);

	matrix_clear();

	if (RES_COL_N(res) > 2) {
		for(i = 0; i < RES_ROW_N(res); i++) {
			if ((!RES_ROWS(res)[i].values[0].nul) && (!RES_ROWS(res)[i].values[1].nul)) {
				if ((RES_ROWS(res)[i].values[0].type == DB1_INT) &&
						(RES_ROWS(res)[i].values[1].type == DB1_INT) &&
						(RES_ROWS(res)[i].values[2].type == DB1_INT)) {
					matrix_insert(RES_ROWS(res)[i].values[0].val.int_val, RES_ROWS(res)[i].values[1].val.int_val, RES_ROWS(res)[i].values[2].val.int_val);
					n++;
				}
				else {
					LM_ERR("got invalid result type from query.\n");
				}
			}
		}
	}

	/* critical section end */
	lock_release(lock);

	matrix_dbf.free_result(matrix_dbh, res);

	LM_INFO("loaded %d matrix entries.\n", n);
	return n;
}




static int init_shmlock(void)
{
	lock = lock_alloc();
	if (!lock) {
		LM_CRIT("cannot allocate memory for lock.\n");
		return -1;
	}
	if (lock_init(lock) == 0) {
		LM_CRIT("cannot initialize lock.\n");
		return -1;
	}

	return 0;
}




static void destroy_shmlock(void)
{
	if (lock) {
		lock_destroy(lock);
		lock_dealloc((void *)lock);
		lock = NULL;
	}
}



static void matrix_rpc_reload(rpc_t* rpc, void* c)
{
	if (matrix_db_open() != 0) {
		rpc->fault(c, 500, "Failed to connect to db");
		return;
	}
	if(db_reload_matrix() < 0) {
		rpc->fault(c, 500, "Reload failed");
	}
	matrix_db_close();
}

static const char *matrix_rpc_reload_doc[2] = {
	"reload matrix records from database",
	0
};


rpc_export_t matrix_rpc_cmds[] = {
	{"matrix.reload", matrix_rpc_reload, matrix_rpc_reload_doc, 0},
	{0, 0, 0, 0}
};

int matrix_rpc_init(void)
{
	if (rpc_register_array(matrix_rpc_cmds)!=0)
	{
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}
	return 0;

}

static int init_matrix(void)
{
	matrix = shm_malloc(sizeof(struct matrix_t));
	if (!matrix) {
		LM_ERR("out of shared memory\n");
		return -1;
	}
	memset(matrix, 0, sizeof(struct matrix_t));
	if (db_reload_matrix() < 0) {
		LM_ERR("cannot populate matrix\n");
		return -1;
	}

	return 0;
}




static void destroy_matrix(void)
{
	if (matrix) {
		matrix_clear();
		shm_free(matrix);
	}
}




static int mod_init(void)
{
	if(matrix_rpc_init()<0) {
		LM_ERR("failed to init RPC commands");
		return -1;
	}

	if (init_shmlock() != 0) return -1;
	if (matrix_db_init() != 0) return -1;
	if (matrix_db_open() != 0) return -1;
	if (init_matrix() != 0) return -1;
	matrix_db_close();
	return 0;
}



static int child_init(int rank)
{
	if(rank==PROC_INIT || rank==PROC_TCP_MAIN)
		return 0;
	if (matrix_db_open() != 0) return -1;
	return 0;
}



static void mod_destroy(void)
{
	destroy_matrix();
	destroy_shmlock();
	matrix_db_close();
}