/*
 * Copyright (C) 2013-21 Robert Boisvert
 *
 * This file is part of the mohqueue module for Kamailio, a free SIP server.
 *
 * The mohqueue module 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
 *
 * The mohqueue module 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 <sys/types.h>
#include <stdlib.h>

#include "mohqueue_mod.h"
#include "mohq_db.h"
#include "mohq_funcs.h"

MODULE_VERSION

/**********
* local function declarations
**********/

static int fixup_count(void **, int);
static int fixup_free_count(void **param, int param_no);
static int mod_child_init(int);
static void mod_destroy(void);
static int mod_init(void);

/**********
* global varbs
**********/

mod_data *pmod_data;
pv_spec_t *prtp_pv;

/**********
* module exports
**********/
/* PARAMETERS */
str db_url = str_init(DEFAULT_DB_URL);
str db_ctable = str_init("mohqcalls");
str db_qtable = str_init("mohqueues");
char *mohdir = "";
int moh_maxcalls = 50;

/* clang-format off */
/* COMMANDS */
static cmd_export_t mod_cmds[] = {
	{"mohq_count", (cmd_function)mohq_count, 2,
		fixup_count, fixup_free_count,
		REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE},
	{"mohq_process", (cmd_function)mohq_process, 0,
		0, 0, REQUEST_ROUTE},
	{"mohq_retrieve", (cmd_function)mohq_retrieve, 2,
		fixup_spve_spve, fixup_free_spve_spve,
		REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE},
	{"mohq_send", (cmd_function)mohq_send, 1,
		fixup_spve_spve, fixup_free_spve_spve, REQUEST_ROUTE},
	{NULL, NULL, -1, 0, 0},
};

static param_export_t mod_parms[] = {
	{"db_url", PARAM_STR, &db_url},
	{"db_ctable", PARAM_STR, &db_ctable},
	{"db_qtable", PARAM_STR, &db_qtable},
	{"mohdir", PARAM_STRING, &mohdir},
	{"moh_maxcalls", PARAM_INT, &moh_maxcalls},
	{NULL, 0, NULL},
};

static const char *mohqueue_rpc_debug_doc[] = {
	"Toggle mohqueue debug mode. Parameters: queue name, state (0=off, <>0=on)",
	0
};

static const char *mohqueue_rpc_drop_call_doc[] = {
	"Drop a mohqueue call. Parameters: queue name, callID (*=all)",
	0
};

/* RPC COMMANDS */
rpc_export_t mohqueue_rpc[] = {
	{"mohqueue.debug", mohqueue_rpc_debug, mohqueue_rpc_debug_doc, 0},
	{"mohqueue.drop_call", mohqueue_rpc_drop_call,
			mohqueue_rpc_drop_call_doc, 0},
	{0, 0, 0, 0}
};

/* MODULE EXPORTS */
struct module_exports exports = {
	"mohqueue",		 /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	mod_cmds,		 /* exported functions */
	mod_parms,		 /* exported parameters */
	0,				 /* RPC methods */
	0,				 /* exported pseudo-variables */
	0,				 /* response handling function */
	mod_init,		 /* module initialization function */
	mod_child_init,	 /* per-child initialization function */
	mod_destroy		 /* destructor function */
};
/* clang-format on */

/**********
* local constants
**********/

str prtpstat[1] = {STR_STATIC_INIT("$rtpstat")};

/**********
* local functions
**********/

/**********
* Fixup Count
*
* INPUT:
*   Arg (1) = parameter array pointer
*   Arg (1) = parameter number
* OUTPUT: -1 if failed; 0 if saved as pv_elem_t
**********/

static int fixup_count(void **param, int param_no)

{
	if(param_no == 1) {
		return fixup_spve_spve(param, 1);
	}
	if(param_no == 2) {
		return fixup_pvar_null(param, 1);
	}
	return 0;
}

static int fixup_free_count(void **param, int param_no)

{
	if(param_no == 1) {
		return fixup_free_spve_spve(param, 1);
	}
	if(param_no == 2) {
		return fixup_free_pvar_null(param, 1);
	}
	return 0;
}

/**********
* Configuration Initialization
*
* INPUT:
*   pmod_data memory allocated
*   configuration values set
* OUTPUT: 0 if failed; else pmod_data has config values
**********/

static int init_cfg(void)

{
	int bfnd = 0;
	int berror = 0;
	struct stat psb[1];

	/**********
* db_url, db_ctable, db_qtable exist?
**********/

	if(!db_url.s || db_url.len <= 0) {
		LM_ERR("db_url parameter not set!\n");
		berror = 1;
	}
	if(!db_ctable.s || db_ctable.len <= 0) {
		LM_ERR("db_ctable parameter not set!\n");
		berror = 1;
	}
	if(!db_qtable.s || db_qtable.len <= 0) {
		LM_ERR("db_qtable parameter not set!\n");
		berror = 1;
	}

	/**********
* mohdir
* o exists?
* o directory?
**********/

	if(!*mohdir) {
		LM_ERR("mohdir parameter not set!\n");
		berror = 1;
	} else if(strlen(mohdir) > MOHDIRLEN) {
		LM_ERR("mohdir too long!\n");
		berror = 1;
	} else {
		if(!lstat(mohdir, psb)) {
			if((psb->st_mode & S_IFMT) == S_IFDIR) {
				bfnd = 1;
			}
		}
		if(!bfnd) {
			LM_ERR("mohdir is not a directory!\n");
			berror = 1;
		}
	}

	/**********
* o max calls valid?
* o alloc memory
* o save data
**********/

	if(moh_maxcalls < 1 || moh_maxcalls > 5000) {
		LM_ERR("moh_maxcalls not in range of 1-5000!\n");
		berror = 1;
	}
	if(berror) {
		return 0;
	}
	pmod_data->pcall_lst =
			(call_lst *)shm_malloc(sizeof(call_lst) * moh_maxcalls);
	if(!pmod_data->pcall_lst) {
		LM_ERR("Unable to allocate shared memory!\n");
		return 0;
	}
	pmod_data->pcfg->db_url = db_url;
	pmod_data->pcfg->db_ctable = db_ctable;
	pmod_data->pcfg->db_qtable = db_qtable;
	pmod_data->pcfg->mohdir = mohdir;
	memset(pmod_data->pcall_lst, 0, sizeof(call_lst) * moh_maxcalls);
	pmod_data->call_cnt = moh_maxcalls;
	return -1;
}

/**********
* DB Initialization
*
* INPUT:
*   pmod_data memory allocated and cfg values set
* OUTPUT: 0 if failed; else pmod_data has db_api
**********/

static int init_db(void)

{
	/**********
* o bind to DB
* o check capabilities
* o init DB
**********/

	str *pdb_url = &pmod_data->pcfg->db_url;
	if(db_bind_mod(pdb_url, pmod_data->pdb)) {
		LM_ERR("Unable to bind DB API using %s!\n", pdb_url->s);
		return 0;
	}
	db_func_t *pdb = pmod_data->pdb;
	if(!DB_CAPABILITY((*pdb), DB_CAP_ALL)) {
		LM_ERR("Selected database %s lacks required capabilities!\n",
				pdb_url->s);
		return 0;
	}
	db1_con_t *pconn = mohq_dbconnect();
	if(!pconn) {
		return 0;
	}

	/**********
* o check schema
* o remove all call recs
* o load queue list
**********/

	if(db_check_table_version(
			   pdb, pconn, &pmod_data->pcfg->db_ctable, MOHQ_CTABLE_VERSION)
			< 0) {
		DB_TABLE_VERSION_ERROR(pmod_data->pcfg->db_ctable);
		goto dberror;
	}
	if(db_check_table_version(
			   pdb, pconn, &pmod_data->pcfg->db_qtable, MOHQ_QTABLE_VERSION)
			< 0) {
		DB_TABLE_VERSION_ERROR(pmod_data->pcfg->db_qtable);
		goto dberror;
	}
	clear_calls(pconn);
	update_mohq_lst(pconn);
	pmod_data->mohq_update = time(0);
	mohq_dbdisconnect(pconn);
	return -1;

	/**********
* close DB
**********/

dberror:
	pdb->close(pconn);
	pconn = 0;
	return 0;
}

/**********
* Child Module Initialization
*
* INPUT:
*   Arg (1) = child type
* OUTPUT: -1 if db_api not ready; else 0
**********/

int mod_child_init(int rank)

{
	/**********
* make sure DB initialized
**********/

	if(rank == PROC_INIT || rank == PROC_TCP_MAIN || rank == PROC_MAIN) {
		return 0;
	}
	if(!pmod_data->pdb->init) {
		LM_CRIT("DB API not loaded!\n");
		return -1;
	}
	return 0;
}

/**********
* Module Teardown
*
* INPUT: none
* OUTPUT: none
**********/

void mod_destroy(void)

{
	/**********
* o destroy MOH can call queue locks
* o deallocate shared mem
**********/

	if(!pmod_data) {
		return;
	}
	if(pmod_data->pmohq_lock->plock) {
		mohq_lock_destroy(pmod_data->pmohq_lock);
	}
	if(pmod_data->pcall_lock->plock) {
		mohq_lock_destroy(pmod_data->pcall_lock);
	}
	if(pmod_data->pmohq_lst) {
		shm_free(pmod_data->pmohq_lst);
	}
	if(pmod_data->pcall_lst) {
		shm_free(pmod_data->pcall_lst);
	}
	shm_free(pmod_data);
	return;
}

/**********
* Module Initialization
*
* INPUT: none
* OUTPUT: -1 if failed; 0 if success
**********/

int mod_init(void)

{
	int rtplen;

	/**********
* o allocate shared mem and init
* o init configuration data
* o init DB
**********/

	pmod_data = (mod_data *)shm_malloc(sizeof(mod_data));
	if(!pmod_data) {
		LM_ERR("Unable to allocate shared memory!\n");
		return -1;
	}
	memset(pmod_data, 0, sizeof(mod_data));
	if(!init_cfg()) {
		goto initerr;
	}
	if(!init_db()) {
		goto initerr;
	}

	/**********
* o bind to SL/TM/RR modules
* o bind to RTPPROXY functions
**********/

	if(sl_load_api(pmod_data->psl)) {
		LM_ERR("Unable to load SL module!\n");
		goto initerr;
	}
	if(load_tm_api(pmod_data->ptm)) {
		LM_ERR("Unable to load TM module!\n");
		goto initerr;
	}
	if(load_rr_api(pmod_data->prr)) {
		LM_ERR("Unable to load RR module!\n");
		goto initerr;
	}
	pmod_data->fn_rtp_answer = find_export("rtpproxy_answer", 0, 0);
	if(!pmod_data->fn_rtp_answer) {
		LM_ERR("Unable to load rtpproxy_answer!\n");
		goto initerr;
	}
	pmod_data->fn_rtp_offer = find_export("rtpproxy_offer", 0, 0);
	if(!pmod_data->fn_rtp_offer) {
		LM_ERR("Unable to load rtpproxy_offer!\n");
		goto initerr;
	}
	pmod_data->fn_rtp_stream_c = find_export("rtpproxy_stream2uac", 2, 0);
	if(!pmod_data->fn_rtp_stream_c) {
		LM_ERR("Unable to load rtpproxy_stream2uac!\n");
		goto initerr;
	}
	pmod_data->fn_rtp_stream_s = find_export("rtpproxy_stream2uas", 2, 0);
	if(!pmod_data->fn_rtp_stream_s) {
		LM_ERR("Unable to load rtpproxy_stream2uas!\n");
		goto initerr;
	}
	pmod_data->fn_rtp_stop_c = find_export("rtpproxy_stop_stream2uac", 0, 0);
	if(!pmod_data->fn_rtp_stop_c) {
		LM_ERR("Unable to load rtpproxy_stop_stream2uac!\n");
		goto initerr;
	}
	pmod_data->fn_rtp_stop_s = find_export("rtpproxy_stop_stream2uas", 0, 0);
	if(!pmod_data->fn_rtp_stop_s) {
		LM_ERR("Unable to load rtpproxy_stop_stream2uas!\n");
		goto initerr;
	}
	pmod_data->fn_rtp_destroy = find_export("rtpproxy_destroy", 0, 0);
	if(!pmod_data->fn_rtp_destroy) {
		LM_ERR("Unable to load rtpproxy_destroy!\n");
		goto initerr;
	}

	/**********
* register RPC
**********/

	if(rpc_register_array(mohqueue_rpc)) {
		LM_ERR("Unable to register RPC commands!\n");
		return -1;
	}

	/**********
* get RTPSTAT pv spec
**********/

	rtplen = pv_locate_name(prtpstat);
	if(rtplen != prtpstat->len) {
		LM_ERR("Unable to find RTPSTAT pv!\n");
		goto initerr;
	}
	prtp_pv = pv_cache_get(prtpstat);
	if(!prtp_pv) {
		LM_ERR("Unable to find pv spec for RTPSTAT!\n");
		goto initerr;
	}

	/**********
* init MOH and call queue locks
**********/

	if(!mohq_lock_init(pmod_data->pmohq_lock)) {
		goto initerr;
	}
	if(!mohq_lock_init(pmod_data->pcall_lock)) {
		goto initerr;
	}
	return 0;

	/**********
* o release shared mem
* o exit with error
**********/

initerr:
	if(pmod_data->mohq_cnt) {
		shm_free(pmod_data->pmohq_lst);
	}
	if(pmod_data->pcall_lock->plock) {
		mohq_lock_destroy(pmod_data->pcall_lock);
	}
	shm_free(pmod_data);
	pmod_data = NULL;
	return -1;
}
