/*
 * $Id$
 *
 * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
 * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
 *
 * The initial version of this code was written by Dragos Vingarzan
 * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
 * Fraunhofer FOKUS Institute. It was and still is maintained in a separate
 * branch of the original SER. We are therefore migrating it to
 * Kamailio/SR and look forward to maintaining it from here on out.
 * 2011/2012 Smile Communications, Pty. Ltd.
 * ported/maintained/improved by
 * Jason Penton (jason(dot)penton(at)smilecoms.com and
 * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
 * effort to add full IMS support to Kamailio/SR using a new and
 * improved architecture
 *
 * NB: A lot of this code was originally part of OpenIMSCore,
 * FhG Fokus.
 * Copyright (C) 2004-2006 FhG Fokus
 * Thanks for great work! This is an effort to
 * break apart the various CSCF functions into logically separate
 * components. We hope this will drive wider use. We also feel
 * that in this way the architecture is more complete and thereby easier
 * to manage in the Kamailio/SR environment
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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 "../../core/timer.h"
#include "../../modules/sl/sl.h"

#include "ims_icscf_mod.h"
#include "scscf_list.h"
#include "cxdx_uar.h"
#include "cxdx_lir.h"
#include "db.h"
#include "nds.h"

MODULE_VERSION

struct tm_binds tmb;
struct cdp_binds cdpb;
cdp_avp_bind_t *cdp_avp;

/** SL API structure */
sl_api_t slb;


stat_var *stat_uar_timeouts = NULL;
stat_var *stat_lir_timeouts = NULL;
stat_var *uar_replies_received = NULL;
stat_var *lir_replies_received = NULL;
stat_var *uar_replies_response_time = NULL;
stat_var *lir_replies_response_time = NULL;

//module parameters
char *ims_icscf_db_url = DEFAULT_RODB_URL;			  /**< DB URL */
char *ims_icscf_db_nds_table = "nds_trusted_domains"; /**< NDS table in DB */
char *ims_icscf_db_scscf_table = "s_cscf";			  /**< S-CSCF table in db */
char *ims_icscf_db_capabilities_table =
		"s_cscf_capabilities"; /**< S-CSCF capabilities table in db */

int ims_icscf_hash_size = 128;
int scscf_entry_expiry = 300;

/* parameters storage */
char *cxdx_dest_realm_s = "ims.smilecoms.com";
str cxdx_dest_realm;

str preferred_scscf_uri = str_init("sip:scscf.ims.smilecoms.com:4060");
int use_preferred_scscf_uri = 0;

//Only used if we want to force the Rx peer
//Usually this is configured at a stack level and the first request uses realm routing
char *cxdx_forced_peer_s = "";
str cxdx_forced_peer;

/**
 * Name of the route, if a user is not found in the HSS (LIR)
 */
char *route_lir_user_unknown = 0; /* default is the main route */
/**
 * Number of the route, if a user is not found in the HSS (LIR)
 */
int route_lir_user_unknown_no = -1;

/**
 * Name of the route, if a user is not found in the HSS (UAR)
 */
char *route_uar_user_unknown = 0; /* default is the main route */
/**
 * Number of the route, if a user is not found in the HSS (UAR)
 */
int route_uar_user_unknown_no = -1;


/** module functions */
static int mod_init(void);
static int fixup_uar_lir(void **param, int param_no);
static int fixup_free_uar_lir(void **param, int param_no);

/* clang-format off */
static cmd_export_t cmds[] = {
	{"I_perform_user_authorization_request", (cmd_function)I_perform_user_authorization_request, 2,
		fixup_uar_lir, fixup_free_uar_lir, REQUEST_ROUTE},
	{"I_perform_location_information_request",(cmd_function)I_perform_location_information_request, 2,
		fixup_uar_lir, fixup_free_uar_lir, REQUEST_ROUTE},
	{"I_scscf_select", (cmd_function)I_scscf_select, 1,
		0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"I_scscf_drop", (cmd_function)I_scscf_drop, 0,
		0, 0, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
	{0, 0, 0, 0, 0, 0}
};

static param_export_t params[] = {
	{"route_lir_user_unknown", PARAM_STRING, &route_lir_user_unknown},
	{"route_uar_user_unknown", PARAM_STRING, &route_uar_user_unknown},
	{"scscf_entry_expiry", PARAM_INT, &scscf_entry_expiry},
	{"db_url", PARAM_STRING, &ims_icscf_db_url},
	{"db_nds_table", PARAM_STRING, &ims_icscf_db_nds_table},
	{"db_scscf_table", PARAM_STRING, &ims_icscf_db_scscf_table},
	{"db_capabilities_table", PARAM_STRING, &ims_icscf_db_capabilities_table},
	{"cxdx_forced_peer", PARAM_STR, &cxdx_forced_peer},
	{"cxdx_dest_realm", PARAM_STR, &cxdx_dest_realm},
	{"preferred_scscf_uri", PARAM_STR, &preferred_scscf_uri},
	{"use_preferred_scscf_uri", PARAM_INT, &use_preferred_scscf_uri},
	{0, 0, 0}
};

stat_export_t mod_stats[] = {
	{"uar_avg_response_time", STAT_IS_FUNC, (stat_var **)get_avg_uar_response_time},
	{"lir_avg_response_time", STAT_IS_FUNC, (stat_var **)get_avg_lir_response_time},
	{"uar_timeouts", 0, (stat_var **)&stat_uar_timeouts},
	{"lir_timeouts", 0, (stat_var **)&stat_lir_timeouts},
	{0, 0, 0}
};

/** module exports */
struct module_exports exports = {
	"ims_icscf", DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,						  /* Exported functions */
	params, 0,					  /* exported RPC methods */
	0,							  /* exported pseudo-variables */
	0,							  /* reponse handling function */
	mod_init,					  /* module initialization function */
	0,							  /* per-child init function */
	0							  /* module destroy function */
};
/* clang-format on */

/**
 * init module function
 */
static int mod_init(void)
{
	cdp_avp = 0;
	int route_no;

#ifdef STATISTICS
	if(register_module_stats("ims_icscf", mod_stats) != 0) {
		LM_ERR("failed to register core statistics\n");
		goto error;
	}
	if(!register_stats()) {
		LM_ERR("Unable to register statistics\n");
		goto error;
	}
#endif

	/* initialising hash table*/
	if(!i_hash_table_init(ims_icscf_hash_size)) {
		LM_ERR("Error initializing the Hash Table for stored S-CSCF lists\n");
		goto error;
	}

	/* load the TM API */
	if(load_tm_api(&tmb) != 0) {
		LM_ERR("can't load TM API\n");
		goto error;
	}

	/* load the CDP API */
	if(load_cdp_api(&cdpb) != 0) {
		LM_ERR("can't load CDP API\n");
		goto error;
	}

	cdp_avp = load_cdp_avp();
	if(!cdp_avp) {
		LM_ERR("can't load CDP_AVP API\n");
		goto error;
	}

	/* cache the trusted domain names and capabilities */
	/* bind to the db module */
	if(ims_icscf_db_bind(ims_icscf_db_url) < 0)
		goto error;

	/* bind the SL API */
	if(sl_load_api(&slb) != 0) {
		LM_ERR("cannot bind to SL API\n");
		return -1;
	}

	ims_icscf_db_init(ims_icscf_db_url, ims_icscf_db_nds_table,
			ims_icscf_db_scscf_table, ims_icscf_db_capabilities_table);

	I_NDS_get_trusted_domains();
	I_get_capabilities();

	ims_icscf_db_close();

	if(!i_hash_table_init(ims_icscf_hash_size)) {
		LOG(L_ERR, "ERR" M_NAME ":mod_init: Error initializing the Hash Table "
				   "for stored S-CSCF lists\n");
		goto error;
	}

	/* register global timer used to get rid of stale scscf_lists*/
	if(register_timer(ims_icscf_timer_routine, 0, 60) < 0) {
		LM_ERR("failed to register timer \n");
		return -1;
	}

	/* try to fix the xmlrpc route */
	if(route_lir_user_unknown) {
		route_no = route_get(&main_rt, route_lir_user_unknown);
		if(route_no == -1) {
			ERR("ims_icscf: failed to fix route \"%s\": route_get() failed\n",
					route_lir_user_unknown);
			return -1;
		}
		if(main_rt.rlist[route_no] == 0) {
			WARN("ims_icscf: ims_icscf route \"%s\" is empty / doesn't exist\n",
					route_lir_user_unknown);
		}
		route_lir_user_unknown_no = route_no;
	}
	/* try to fix the xmlrpc route */
	if(route_uar_user_unknown) {
		route_no = route_get(&main_rt, route_uar_user_unknown);
		if(route_no == -1) {
			ERR("ims_icscf: failed to fix route \"%s\": route_get() failed\n",
					route_uar_user_unknown);
			return -1;
		}
		if(main_rt.rlist[route_no] == 0) {
			WARN("ims_icscf: ims_icscf route \"%s\" is empty / doesn't exist\n",
					route_uar_user_unknown);
		}
		route_uar_user_unknown_no = route_no;
	}

	LM_DBG("ims_icscf module successfully initialised\n");

	return 0;
error:
	LM_ERR("Failed to initialise ims_icscf module\n");
	return -1;
}

static int fixup_uar_lir(void **param, int param_no)
{
	if(strlen((char *)*param) <= 0) {
		LM_ERR("empty parameter %d not allowed\n", param_no);
		return -1;
	}

	switch(param_no) {
		case 1:
			return fixup_spve_null(param, param_no);
		case 2:
			return fixup_var_int_12(param, 1);
	}
	return 0;
}

static int fixup_free_uar_lir(void **param, int param_no)
{
	switch(param_no) {
		case 1:
			return fixup_free_spve_null(param, param_no);
		case 2:
			return fixup_free_fparam_all(param, 1);
	}
	return 0;
}
