/*
 * Copyright (C) 2007-2020 1&1 Internet AG
 *
 * 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 "cr_rpc.h"
#include "cr_data.h"
#include "cr_carrier.h"
#include "cr_domain.h"
#include "carrierroute.h"
#include "db_carrierroute.h"


/**
 * Defines the option set for the different fifo commands
 * Every line is for a command,
 * The first field defines the required options, the second field defines the
 * optional options and the third field defines the invalid options.
 */
static unsigned int opt_settings[5][3] = {
		{O_PREFIX | O_DOMAIN | O_HOST | O_PROB,
				O_R_PREFIX | O_R_SUFFIX | O_H_INDEX, O_NEW_TARGET},
		{O_HOST | O_DOMAIN | O_PREFIX, O_PROB,
				O_R_PREFIX | O_R_SUFFIX | O_NEW_TARGET | O_H_INDEX},
		{O_HOST | O_NEW_TARGET, O_PREFIX | O_DOMAIN | O_PROB,
				O_R_PREFIX | O_R_SUFFIX | O_H_INDEX},
		{O_HOST | O_DOMAIN | O_PREFIX, O_PROB | O_NEW_TARGET,
				O_R_PREFIX | O_R_SUFFIX | O_H_INDEX},
		{O_HOST | O_DOMAIN | O_PREFIX, O_PROB,
				O_R_PREFIX | O_R_SUFFIX | O_NEW_TARGET | O_H_INDEX}};


static void cr_rpc_reload_routes(rpc_t *rpc, void *c)
{

	if(mode == CARRIERROUTE_MODE_DB) {
		if(carrierroute_dbh == NULL) {
			carrierroute_dbh = carrierroute_dbf.init(&carrierroute_db_url);
			if(carrierroute_dbh == 0) {
				rpc->fault(c, 500,
						"Internal error -- cannot initialize database "
						"connection");
				LM_ERR("cannot initialize database connection\n");
				return;
			}
		}
	}

	if((reload_route_data()) != 0) {
		rpc->fault(c, 500, "Internal error -- failed to load routing data\n");
		LM_ERR("failed to load routing data\n");
		return;
	}
}

static const char *cr_rpc_reload_routes_doc[2] = {
		"Reload carrierroute routes", 0};


/**
 * prints the routing data
 *
 * @param rpc - RPC API structure
 * @param ctx - RPC context
 */
void cr_rpc_dump_routes(rpc_t *rpc, void *ctx)
{
	struct route_data_t *rd;
	str *tmp_str;
	str empty_str = str_init("<empty>");
	void *th;
	void *ih;
	void *dh;
	void *eh;
	void *fh;
	void *gh;
	int i, j;

	if((rd = get_data()) == NULL) {
		LM_ERR("error during retrieve data\n");
		rpc->fault(ctx, 500, "Internal error - cr data");
		return;
	}

	/* add root node */
	if(rpc->add(ctx, "{", &th) < 0) {
		rpc->fault(ctx, 500, "Internal error root reply");
		goto error;
	}
	if(rpc->struct_add(th, "[", "routes", &ih) < 0) {
		rpc->fault(ctx, 500, "Internal error - routes structure");
		goto error;
	}

	LM_DBG("start processing of data\n");
	for(i = 0; i < rd->carrier_num; i++) {
		if(rd->carriers[i]) {
			if(rpc->array_add(ih, "{", &dh) < 0) {
				LM_ERR("add carrier data failure at count %d\n", i);
				rpc->fault(ctx, 500, "Response failure - carrier data");
				goto error;
			}
			tmp_str = (rd->carriers[i] ? rd->carriers[i]->name : &empty_str);
			if(rpc->struct_add(dh, "Sd[", "carrier", tmp_str, "id",
					   (rd->carriers[i] ? rd->carriers[i]->id : 0), "domains",
					   &eh)
					< 0) {
				LM_ERR("add carrier structure failure at count %d"
					   " (carrier: %d/%.*s)\n",
						i, tmp_str->len, tmp_str->len, tmp_str->s);
				rpc->fault(ctx, 500, "Internal error - carrier structure");
				goto error;
			}

			for(j = 0; j < rd->carriers[i]->domain_num; j++) {
				if(rd->carriers[i]->domains[j]
						&& rd->carriers[i]->domains[j]->tree) {
					if(rpc->array_add(eh, "{", &fh) < 0) {
						LM_ERR("add domain data failure at count %d/%d\n", i,
								j);
						rpc->fault(ctx, 500, "Response failure - domain data");
						goto error;
					}
					tmp_str = (rd->carriers[i]->domains[j]
									   ? rd->carriers[i]->domains[j]->name
									   : &empty_str);
					if(rpc->struct_add(fh, "Sd[", "domain", tmp_str, "id",
							   rd->carriers[i]->domains[j]->id, "data", &gh)
							< 0) {
						LM_ERR("add domain structure failure at count %d/%d\n",
								i, j);
						rpc->fault(
								ctx, 500, "Internal error - domain structure");
						goto error;
					}
					if(dump_tree_recursor(rpc, ctx, gh,
							   rd->carriers[i]->domains[j]->tree, "")
							< 0) {
						LM_ERR("dump tree recursor failure at count %d/%d\n", i,
								j);
						goto error;
					}
				}
			}
		}
	}
	release_data(rd);
	return;

error:
	release_data(rd);
	return;
}

static const char *cr_rpc_dump_routes_doc[2] = {"Dump carrierroute routes", 0};


static void cr_rpc_activate_host(rpc_t *rpc, void *ctx)
{
	int ret;
	str argument;
	rpc_opt_t options;

	if(mode != CARRIERROUTE_MODE_FILE) {
		rpc->fault(ctx, 500,
				"Not running in config file mode, cannot modify route from "
				"command line");
		return;
	}


	if(rpc->scan(ctx, "S", &argument) < 1) {
		rpc->fault(ctx, 500, "Get argument failed");
		return;
	}

	if((ret = get_rpc_opts(&argument, &options, opt_settings[OPT_ACTIVATE]))
			< 0) {
		rpc->fault(ctx, 500, "Get options failed");
		return;
	}

	options.status = 1;
	options.cmd = OPT_ACTIVATE;

	if(update_route_data(&options) < 0) {
		rpc->fault(ctx, 500, "Update options failed");
		return;
	}

	rpc->add(ctx, "s", "200 ok");
	return;
}

static const char *cr_rpc_activate_host_doc[2] = {
		"Activate carrierroute host", 0};


static void cr_rpc_deactivate_host(rpc_t *rpc, void *ctx)
{
	int ret;
	str argument;
	rpc_opt_t options;

	if(mode != CARRIERROUTE_MODE_FILE) {
		rpc->fault(ctx, 500,
				"Not running in config file mode, cannot modify route from "
				"command line");
		return;
	}


	if(rpc->scan(ctx, "S", &argument) < 1) {
		rpc->fault(ctx, 500, "Get argument failed");
		return;
	}

	if((ret = get_rpc_opts(&argument, &options, opt_settings[OPT_DEACTIVATE]))
			< 0) {
		rpc->fault(ctx, 500, "Get options failed");
		return;
	}

	options.status = 0;
	options.cmd = OPT_DEACTIVATE;

	if(update_route_data(&options) < 0) {
		rpc->fault(ctx, 500, "Update options failed");
		return;
	}

	rpc->add(ctx, "s", "200 ok");
	return;
}

static const char *cr_rpc_deactivate_host_doc[2] = {
		"Deactivate carrierroute host", 0};


static void cr_rpc_add_host(rpc_t *rpc, void *ctx)
{
	int ret;
	str argument;
	rpc_opt_t options;

	if(mode != CARRIERROUTE_MODE_FILE) {
		rpc->fault(ctx, 500,
				"Not running in config file mode, cannot modify route from "
				"command line");
		return;
	}

	if(rpc->scan(ctx, "S", &argument) < 1) {
		rpc->fault(ctx, 500, "Get argument failed");
		return;
	}

	if((ret = get_rpc_opts(&argument, &options, opt_settings[OPT_ADD])) < 0) {
		rpc->fault(ctx, 500, "Get options failed");
		return;
	}

	options.status = 1;
	options.cmd = OPT_ADD;

	if(update_route_data(&options) < 0) {
		rpc->fault(ctx, 500, "Update options failed");
		return;
	}

	rpc->add(ctx, "s", "200 ok");
	return;
}

static const char *cr_rpc_add_host_doc[2] = {"Add carrierroute host", 0};


static void cr_rpc_delete_host(rpc_t *rpc, void *ctx)
{
	int ret;
	str argument;
	rpc_opt_t options;

	if(mode != CARRIERROUTE_MODE_FILE) {
		rpc->fault(ctx, 500,
				"Not running in config file mode, cannot modify route from "
				"command line");
		return;
	}

	if(rpc->scan(ctx, "S", &argument) < 1) {
		rpc->fault(ctx, 500, "Get argument failed");
		return;
	}

	if((ret = get_rpc_opts(&argument, &options, opt_settings[OPT_REMOVE]))
			< 0) {
		rpc->fault(ctx, 500, "Get options failed");
		return;
	}

	options.cmd = OPT_REMOVE;

	if(update_route_data(&options) < 0) {
		rpc->fault(ctx, 500, "Update options failed");
		return;
	}

	rpc->add(ctx, "s", "200 ok");
	return;
}

static const char *cr_rpc_delete_host_doc[2] = {"Remove carrierroute host", 0};


static void cr_rpc_replace_host(rpc_t *rpc, void *ctx)
{
	int ret;
	str argument;
	rpc_opt_t options;

	if(mode != CARRIERROUTE_MODE_FILE) {
		rpc->fault(ctx, 500,
				"Not running in config file mode, cannot modify route from "
				"command line");
		return;
	}


	if(rpc->scan(ctx, "S", &argument) < 1) {
		rpc->fault(ctx, 500, "Get argument failed");
		return;
	}

	if((ret = get_rpc_opts(&argument, &options, opt_settings[OPT_REPLACE]))
			< 0) {
		rpc->fault(ctx, 500, "Get options failed");
		return;
	}

	options.status = 1;
	options.cmd = OPT_REPLACE;

	if(update_route_data(&options) < 0) {
		rpc->fault(ctx, 500, "Update options failed");
		return;
	}

	rpc->add(ctx, "s", "200 ok");
	return;
}

static const char *cr_rpc_replace_host_doc[2] = {
		"Deactivate carrierroute host", 0};


rpc_export_t cr_rpc_methods[] = {
		{"cr.reload_routes", cr_rpc_reload_routes, cr_rpc_reload_routes_doc, 0},
		{"cr.dump_routes", cr_rpc_dump_routes, cr_rpc_dump_routes_doc, 0},
		{"cr.activate_host", cr_rpc_activate_host, cr_rpc_activate_host_doc, 0},
		{"cr.deactivate_host", cr_rpc_deactivate_host,
				cr_rpc_deactivate_host_doc, 0},
		{"cr.add_host", cr_rpc_add_host, cr_rpc_add_host_doc, 0},
		{"cr.delete_host", cr_rpc_delete_host, cr_rpc_delete_host_doc, 0},
		{"cr.replace_host", cr_rpc_replace_host, cr_rpc_replace_host_doc, 0},
		{0, 0, 0, 0}};
