/*
 * main.c: Datamodel daemon
 *
 * Copyright (C) 2023 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: Iryna Antsyferova <iryna.antsyferova@iopsys.eu>
 *
 * See LICENSE file for license related information.
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <libubox/blobmsg.h>
#include <libubox/uloop.h>
#include <libubus.h>
#include <sys/prctl.h>
#include <sys/mman.h>

#include "bbfdmd.h"
#include "dmjson.h"
#include "plugin.h"
#include "set.h"
#include "get.h"
#include "add_delete.h"
#include "event.h"
#include "get_helper.h"
#include "cli.h"
#include "libdmtree/dmentry.h"

#define TELCOVOICEMGR_CONF_FILE "/etc/rdk/conf/telcovoice_manager_conf.json"

#ifndef DAEMON_JSON_INPUT
#define BBFDM_JSON_INPUT "/etc/bbfdm/input.json"
#else
#define BBFDM_JSON_INPUT DAEMON_JSON_INPUT
#endif

// Global variables
static void *deamon_lib_handle = NULL;
static bool is_json_server_init_done = false;

extern struct list_head loaded_json_files;
extern struct list_head json_list;
extern struct list_head json_memhead;

static const char g_ubus_path[] = "voice.sip.data";

int rpc_handler_init()
{
	if (json_hal_server_init(TELCOVOICEMGR_CONF_FILE) != RETURN_OK)	{
		ERR("Failed to init HAL server");
		return -1;
	}

	if (json_hal_server_register_action_callback("setParameters",
		bbfdm_set_value) != RETURN_OK) {
		ERR("Failed to register setParameters callback");
		goto Error;
	}

	if (json_hal_server_register_action_callback("getParameters",
		bbfdm_get_value) != RETURN_OK) {
		ERR("Failed to register getParameters callback");
		goto Error;
	}

	if (json_hal_server_register_action_callback("deleteObject",
		bbfdm_del_object) != RETURN_OK) {
		ERR("Failed to register deleteObject callback");
		goto Error;
	}

	subs_event_init();

	if (json_hal_server_register_action_callback("subscribeEvent",
		subs_event_handler) != RETURN_OK) {
		ERR("Failed to register subscribeEvent callback");
		goto Error;
	}

	if (json_hal_server_run() != RETURN_OK) {
		ERR("Failed to run HAL server");
		goto Error;
	}

	is_json_server_init_done = true;

	return 0;

Error:
	json_hal_server_terminate();
	is_json_server_init_done = false;
	return -1;
}

int rpc_handler_uninit()
{
	if (!is_json_server_init_done)
		return 0;
	is_json_server_init_done = false;

	if (json_hal_server_terminate() != RETURN_OK)
		ERR("Failed to terminate HAL server");

	subs_event_uninit();

	return 0;
}

static void usage(char *prog)
{
	fprintf(stderr, "Usage: %s [options]\n", prog);
	fprintf(stderr, "\n");
	fprintf(stderr, "options:\n");
	fprintf(stderr, "    -c <command input>  Run cli command\n");
	fprintf(stderr, "    -h                 Displays this help\n");
	fprintf(stderr, "\n");
}

static int bbfdm_load_deamon_config(bbfdm_config_t *config, const char *json_path)
{
	char *opt_val = NULL;
	int err = 0;

	if (!json_path || !strlen(json_path))
		return -1;

	json_object *json_obj = json_object_from_file(json_path);
	if (!json_obj)
		return -1;

	json_object *deamon_obj = dmjson_get_obj(json_obj, 1, "daemon");
	if (!deamon_obj) {
		err = -1;
		goto exit;
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "config", "loglevel");
	if (DM_STRLEN(opt_val)) {
		config->log_level = (uint8_t) strtoul(opt_val, NULL, 10);
		set_debug_level(config->log_level);
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "config", "refresh_time");
	if (DM_STRLEN(opt_val)) {
		config->refresh_time = (unsigned int) strtoul(opt_val, NULL, 10) * 1000;
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "config", "transaction_timeout");
	if (DM_STRLEN(opt_val)) {
		config->transaction_timeout = (int) strtol(opt_val, NULL, 10);
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "config", "subprocess_level");
	if (DM_STRLEN(opt_val)) {
		config->subprocess_level = (unsigned int) strtoul(opt_val, NULL, 10);
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "output", "name");
	if (DM_STRLEN(opt_val)) {
		strncpyt(config->out_name, opt_val, sizeof(config->out_name));
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "output", "parent_dm");
	if (DM_STRLEN(opt_val)) {
		strncpyt(config->out_parent_dm, opt_val, sizeof(config->out_parent_dm));
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "output", "object");
	if (DM_STRLEN(opt_val)) {
		strncpyt(config->out_object, opt_val, sizeof(config->out_object));
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "output", "root_obj");
	if (DM_STRLEN(opt_val)) {
		strncpyt(config->out_root_obj, opt_val, sizeof(config->out_root_obj));
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "input", "plugin_dir");
	if (DM_STRLEN(opt_val)) {
		strncpyt(config->in_plugin_dir, opt_val, sizeof(config->in_plugin_dir));
	}

	opt_val = dmjson_get_value(deamon_obj, 2, "input", "type");
	if (DM_STRLEN(opt_val)) {
		strncpyt(config->in_type, opt_val, sizeof(config->in_type));
	}
	opt_val = dmjson_get_value(deamon_obj, 2, "input", "name");
	if (DM_STRLEN(opt_val)) {
		strncpyt(config->in_name, opt_val, sizeof(config->in_name));
	}

	opt_val = dmjson_get_value(json_obj, 3, "cli", "config", "proto");
	if (DM_STRLEN(opt_val)) {
		config->proto = get_proto_type(opt_val);
	} else {
		config->proto = BBFDM_BOTH;
	}

	opt_val = dmjson_get_value(json_obj, 3, "cli", "config", "instance_mode");
	if (DM_STRLEN(opt_val)) {
		config->instance_mode = get_instance_mode((int) strtol(opt_val, NULL, 10));
	} else {
		config->instance_mode = INSTANCE_MODE_NUMBER;
	}

	opt_val = dmjson_get_value(json_obj, 3, "cli", "input", "type");
	if (DM_STRLEN(opt_val)) {
		snprintf(config->cli_in_type, sizeof(config->cli_in_type), "%s", opt_val);
	}

	opt_val = dmjson_get_value(json_obj, 3, "cli", "input", "name");
	if (DM_STRLEN(opt_val)) {
		snprintf(config->cli_in_name, sizeof(config->cli_in_name), "%s", opt_val);
	}

	opt_val = dmjson_get_value(json_obj, 3, "cli", "input", "plugin_dir");
	if (DM_STRLEN(opt_val)) {
		strncpyt(config->cli_in_plugin_dir, opt_val, sizeof(config->cli_in_plugin_dir));
	}

	opt_val = dmjson_get_value(json_obj, 3, "cli", "output", "type");
	if (DM_STRLEN(opt_val)) {
		snprintf(config->cli_out_type, sizeof(config->cli_out_type), "%s", opt_val);
	}

exit:
	json_object_put(json_obj);

	return err;
}

void bbfdm_ctx_init(struct bbfdm_context *bbfdm_ctx)
{
	memset(bbfdm_ctx, 0, sizeof(struct bbfdm_context));
}

int daemon_load_datamodel(struct bbfdm_context *daemon_ctx)
{
	int err = -1;
	char *tmp = daemon_ctx->config.in_type;
	char *file_path = daemon_ctx->config.in_name;

	if (DM_STRLEN(tmp) == 0 || DM_STRLEN(file_path) == 0) {
		ERR("Input type/name not supported or defined");
		return -1;
	}

	if (DM_STRLEN(daemon_ctx->config.out_name) == 0) {
		ERR("output name not defined");
		return -1;
	}

	if (is_micro_service) {
		if (DM_STRLEN(daemon_ctx->config.out_parent_dm) == 0) {
			ERR("output parent dm not defined");
			return -1;
		}
		if (DM_STRLEN(daemon_ctx->config.out_object) == 0) {
			ERR("output object not defined");
			return -1;
		}
		if (DM_STRLEN(daemon_ctx->config.out_root_obj) == 0) {
			ERR("output root obj not defined");
			return -1;
		}
	}

	if (strcasecmp(tmp, "JSON") == 0) {
		err = load_json_plugin(&loaded_json_files, &json_list, &json_memhead, file_path, &DEAMON_DM_ROOT_OBJ);
	} else if (strcasecmp(tmp, "DotSo") == 0) {
		err = load_dotso_plugin(&deamon_lib_handle, file_path, &DEAMON_DM_ROOT_OBJ);
	} else {
		ERR("Input type %s not supported", tmp);
	}

	if (!err) {
		bbf_global_init(DEAMON_DM_ROOT_OBJ, daemon_ctx->config.in_plugin_dir);
	} else {
		ERR("Failed loading %s", file_path);
	}

	return err;
}

static void bbfdm_cleanup()
{
	bbf_global_clean(DEAMON_DM_ROOT_OBJ);
	/* DotSo Plugin */
	free_dotso_plugin(deamon_lib_handle);
	/* JSON Plugin */
	free_json_plugin();
}

static void program_exit(int ret)
{
	rpc_handler_uninit();
	bbfdm_cleanup();
	closelog();
	uloop_done();
	exit(ret);
}

static void sig_handler(__attribute__((unused)) int signum)
{
	program_exit(0);
}

static void signal_init()
{
	signal(SIGINT, sig_handler);
}

static void voice_event_cb(struct ubus_context *ctx __attribute__((unused)),
		struct ubus_event_handler *ev __attribute__((unused)),
		const char *type, struct blob_attr *msg)
{
	const struct blobmsg_policy policy[] = {
		[VOICE_CON_ENABLED] = { .name = "enabled", .type = BLOBMSG_TYPE_BOOL},
		[VOICE_CON_PROTOCOL] = { .name = "protocol", .type = BLOBMSG_TYPE_STRING},
		[VOICE_CON_PORT] = { .name = "port", .type = BLOBMSG_TYPE_INT32},
		[VOICE_CON_IP] = { .name = "ip", .type = BLOBMSG_TYPE_STRING},
	};
	struct blob_attr *tb[_VOICE_CON_MAX];

	if (type && strcmp(type, g_ubus_path) != 0)
		return;

	if (blobmsg_parse(policy, _VOICE_CON_MAX, tb, blob_data(msg), blob_len(msg))){
		ERR("Failed to parse blob");
		return;
	}

	send_fw_rule_event(blobmsg_get_bool(tb[VOICE_CON_ENABLED]),
		blobmsg_get_string(tb[VOICE_CON_PROTOCOL]),
		blobmsg_get_string(tb[VOICE_CON_IP]),
		blobmsg_get_u32(tb[VOICE_CON_PORT]));
}

int main(int argc, char **argv)
{
	char *cli_argv[4] = {0};
	struct bbfdm_context bbfdm_ctx;
	int err = 0, ch, cli_argc = 0, i;
	struct ubus_context *ubus_ctx = NULL;
	struct ubus_event_handler add_event = {};
	const char *input_file = BBFDM_JSON_INPUT;

	signal_init();

	while ((ch = getopt(argc, argv, "h:c:")) != -1) {
		switch (ch) {
		case 'c':
			cli_argc = argc-optind+1;
			for (i = 0; i < cli_argc; i++) {
				cli_argv[i] = argv[optind - 1 + i];
			}
			break;
		case 'h':
			usage(argv[0]);
			exit(0);
		default:
			break;
		}
	}

	openlog(argv[0], LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);

	bbfdm_ctx_init(&bbfdm_ctx);

	err = bbfdm_load_deamon_config(&bbfdm_ctx.config, input_file);
	if (err) {
		fprintf(stderr, "Failed to load %s config from json file (%s)\n",
			bbfdm_ctx.config.out_name, input_file);
		goto Exit;
	}

	if (cli_argc) {
		err = bbfdm_cli_exec_command(&bbfdm_ctx.config , cli_argc, cli_argv);
		goto Exit;
	}

	err = daemon_load_datamodel(&bbfdm_ctx);
	if (err) {
		ERR("Failed to load datamodel");
		goto Exit;
	}

	err = rpc_handler_init();
	if (err) {
		ERR("Voice HAL Server Initialization Failed");
		goto Exit;
	}

	uloop_init();

	ubus_ctx = ubus_connect(NULL);
	if (!ubus_ctx) {
		ERR("Failed to connect to ubus");
		return -1;
	}

	ubus_add_uloop(ubus_ctx);
	add_event.cb = voice_event_cb;
	if (ubus_register_event_handler(ubus_ctx, &add_event, g_ubus_path))
		ERR("Failed to connect to ubus");

	INFO("Waiting on uloop....");
	uloop_run();

Exit:
	ubus_shutdown(ubus_ctx);
	program_exit(err);

	return err;
}
