/*
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 * All Rights Reserved.
 *
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 *
 * SPDX-License-Identifier: GPL-2.0+
 *
 *  This program 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.
 *
 *  This program 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include <osmocom/core/application.h>
#include <osmocom/core/signal.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/msgb.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/cpu_sched_vty.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/ports.h>
#include <osmocom/vty/tdef_vty.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ctrl/control_vty.h>
#include <osmocom/ctrl/ports.h>
#include <osmocom/core/sockaddr_str.h>

#include <osmocom/pfcp/pfcp_endpoint.h>

#include "pfcp_tool.h"

#define _GNU_SOURCE
#include <getopt.h>

/* build switches from the configure script */
#include "config.h"

#include <signal.h>
#include <stdio.h>
#include <string.h>

extern void *tall_vty_ctx;

void *tall_pfcp_tool_ctx = NULL;
static int quit = 0;

static struct {
	const char *config_file;
	int daemonize;
	enum vty_ref_gen_mode vty_ref_gen_mode;
	const char *command_file;
} pfcp_tool_cmdline_config = {
	.config_file = "osmo-pfcp-tool.cfg",
	.vty_ref_gen_mode = VTY_REF_GEN_MODE_DEFAULT,
};

static void print_usage()
{
	printf("Usage: osmo-pfcp-tool [command-file.vty]\n       telnet localhost %d\n", OSMO_VTY_PORT_PFCP_TOOL);
}

static void print_help()
{
	const struct value_string *vty_ref_gen_mode_name;

	printf("Some useful options:\n");
	printf("  -h --help                  This text.\n");
	printf("  -D --daemonize             Fork the process into a background daemon.\n");
	printf("  -c --config-file filename  The config file to use, for logging etc.\n");
	printf("  -V --version               Print the version of OsmoMSC.\n");

	printf("\nVTY reference generation:\n");
	printf("     --vty-ref-xml           Generate the VTY reference XML output and exit.\n");
	printf("     --vty-ref-mode MODE     Mode for --vty-ref-xml:\n");
	/* List all VTY ref gen modes */
	for (vty_ref_gen_mode_name = vty_ref_gen_mode_names; vty_ref_gen_mode_name->str; vty_ref_gen_mode_name++)
		printf("                    %s: %s\n",
		       vty_ref_gen_mode_name->str,
		       get_value_string(vty_ref_gen_mode_desc, vty_ref_gen_mode_name->value));
}

static void handle_long_options(const char *prog_name, const int long_option)
{
	switch (long_option) {
	case 1:
		pfcp_tool_cmdline_config.vty_ref_gen_mode = get_string_value(vty_ref_gen_mode_names, optarg);
		if (pfcp_tool_cmdline_config.vty_ref_gen_mode < 0) {
			fprintf(stderr, "%s: Unknown VTY reference generation mode: '%s'\n", prog_name, optarg);
			exit(2);
		}
		break;
	case 2:
		fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
			get_value_string(vty_ref_gen_mode_names, pfcp_tool_cmdline_config.vty_ref_gen_mode),
			get_value_string(vty_ref_gen_mode_desc, pfcp_tool_cmdline_config.vty_ref_gen_mode));
		vty_dump_xml_ref_mode(stdout, pfcp_tool_cmdline_config.vty_ref_gen_mode);
		exit(0);
	default:
		fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
		exit(2);
	}
}

static void handle_options(int argc, char **argv)
{
	while (1) {
		int option_index = 0, c;
		static int long_option = 0;
		static struct option long_options[] = {
			{"help", 0, 0, 'h'},
			{"daemonize", 0, 0, 'D'},
			{"config-file", 1, 0, 'c'},
			{"version", 0, 0, 'V' },
			{"vty-ref-mode", 1, &long_option, 1},
			{"vty-ref-xml", 0, &long_option, 2},
			{0, 0, 0, 0}
		};

		c = getopt_long(argc, argv, "hDc:V", long_options, &option_index);
		if (c == -1)
			break;

		switch (c) {
		case 'h':
			print_usage();
			print_help();
			exit(0);
		case 0:
			handle_long_options(argv[0], long_option);
			break;
		case 'D':
			pfcp_tool_cmdline_config.daemonize = 1;
			break;
		case 'c':
			pfcp_tool_cmdline_config.config_file = optarg;
			break;
		case 'V':
			print_version(1);
			exit(0);
			break;
		default:
			/* catch unknown options *as well as* missing arguments. */
			fprintf(stderr, "%s: Error in command line options. Exiting.\n", argv[0]);
			exit(-1);
		}
	}

	if (argc > optind) {
		pfcp_tool_cmdline_config.command_file = argv[optind];
		optind++;
	}

	if (argc > optind) {
		fprintf(stderr, "%s: Unsupported positional arguments on command line\n", argv[optind]);
		exit(2);
	}
}

static void signal_handler(int signum)
{
	fprintf(stdout, "signal %u received\n", signum);

	switch (signum) {
	case SIGINT:
	case SIGTERM:
		LOGP(DLGLOBAL, LOGL_NOTICE, "Terminating due to signal %d\n", signum);
		quit++;
		break;
	case SIGABRT:
		osmo_generate_backtrace();
		/* in case of abort, we want to obtain a talloc report and
		 * then run default SIGABRT handler, who will generate coredump
		 * and abort the process. abort() should do this for us after we
		 * return, but program wouldn't exit if an external SIGABRT is
		 * received.
		 */
		talloc_report(tall_vty_ctx, stderr);
		talloc_report_full(tall_pfcp_tool_ctx, stderr);
		signal(SIGABRT, SIG_DFL);
		raise(SIGABRT);
		break;
	case SIGUSR1:
		talloc_report(tall_vty_ctx, stderr);
		talloc_report_full(tall_pfcp_tool_ctx, stderr);
		break;
	case SIGUSR2:
		talloc_report_full(tall_vty_ctx, stderr);
		break;
	default:
		break;
	}
}


static struct vty_app_info pfcp_tool_vty_app_info = {
	.name = "osmo-pfcp-tool",
	.version = PACKAGE_VERSION,
	.copyright =
	"OsmoPFCPTool - Osmocom Packet Forwarding Control Protocol tool for testing\r\n"
	"Copyright (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>\r\n"
	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
	"This is free software: you are free to change and redistribute it.\r\n"
	"There is NO WARRANTY, to the extent permitted by law.\r\n",
};

static const struct log_info_cat pfcp_tool_default_categories[] = {
};

const struct log_info log_info = {
	.cat = pfcp_tool_default_categories,
	.num_cat = ARRAY_SIZE(pfcp_tool_default_categories),
};

int pfcp_tool_mainloop()
{
	log_reset_context();
	osmo_select_main_ctx(0);

	/* If the user hits Ctrl-C the third time, just terminate immediately. */
	if (quit >= 3)
		return 1;

	/* Has SIGTERM been received (and not yet been handled)? */
	if (quit && !osmo_select_shutdown_requested()) {
		osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL);

		/* Request write-only mode in osmo_select_main_ctx() */
		osmo_select_shutdown_request();
		/* continue the main select loop until all write queues are serviced. */
	}
	return 0;
}

int main(int argc, char **argv)
{
	int rc;

	/* Track the use of talloc NULL memory contexts */
	talloc_enable_null_tracking();

	osmo_fsm_set_dealloc_ctx(OTC_SELECT);

	tall_pfcp_tool_ctx = talloc_named_const(NULL, 1, "osmo-pfcp-tool");
	pfcp_tool_vty_app_info.tall_ctx = tall_pfcp_tool_ctx;

	msgb_talloc_ctx_init(tall_pfcp_tool_ctx, 0);
	osmo_signal_talloc_ctx_init(tall_pfcp_tool_ctx);

	osmo_init_logging2(tall_pfcp_tool_ctx, &log_info);
	log_set_print_category_hex(osmo_stderr_target, 0);
	log_set_print_category(osmo_stderr_target, 1);
	log_set_print_level(osmo_stderr_target, 1);
	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
	log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
	log_set_print_extended_timestamp(osmo_stderr_target, 1);

	osmo_fsm_log_timeouts(true);
	osmo_fsm_log_addr(true);

	osmo_stats_init(tall_pfcp_tool_ctx);

	g_pfcp_tool_alloc(tall_pfcp_tool_ctx);

	/* For --version, vty_init() must be called before handling options */
	vty_init(&pfcp_tool_vty_app_info);

	ctrl_vty_init(tall_pfcp_tool_ctx);
	logging_vty_add_cmds();
	osmo_talloc_vty_add_cmds();
	osmo_cpu_sched_vty_init(tall_pfcp_tool_ctx);
	osmo_fsm_vty_add_cmds();
	osmo_tdef_vty_groups_init(CONFIG_NODE, g_pfcp_tool_tdef_groups);

	pfcp_tool_vty_init_cfg();

	/* Parse options */
	handle_options(argc, argv);

	if (pfcp_tool_cmdline_config.config_file) {
		rc = vty_read_config_file(pfcp_tool_cmdline_config.config_file, NULL);
		if (rc < 0) {
			LOGP(DLGLOBAL, LOGL_ERROR, "Failed to parse the config file: '%s'\n",
			     pfcp_tool_cmdline_config.config_file);
		}
	}

	/* start telnet VTY */
	rc = telnet_init_default(tall_pfcp_tool_ctx, &g_pfcp_tool, OSMO_VTY_PORT_PFCP_TOOL);
	if (rc < 0)
		return 2;

	/* start control interface, after reading config for ctrl_vty_get_bind_addr() */
	g_pfcp_tool->ctrl = ctrl_interface_setup(g_pfcp_tool, OSMO_CTRL_PORT_PFCP_TOOL, NULL);
	if (!g_pfcp_tool->ctrl) {
		fprintf(stderr, "Failed to initialize control interface. Exiting.\n");
		return -1;
	}

	signal(SIGINT, &signal_handler);
	signal(SIGTERM, &signal_handler);
	signal(SIGABRT, &signal_handler);
	signal(SIGUSR1, &signal_handler);
	signal(SIGUSR2, &signal_handler);
	osmo_init_ignore_signals();

	if (pfcp_tool_cmdline_config.daemonize) {
		rc = osmo_daemonize();
		if (rc < 0) {
			perror("Error during daemonize");
			return 6;
		}
	}

	pfcp_tool_mainloop();

	pfcp_tool_vty_init_cmds();

	if (pfcp_tool_cmdline_config.command_file) {
		printf("Reading '%s'\n", pfcp_tool_cmdline_config.command_file);
		rc = vty_read_config_file(pfcp_tool_cmdline_config.command_file, NULL);
		if (rc < 0) {
			LOGP(DLGLOBAL, LOGL_FATAL, "Failed to parse the command file: '%s'\n",
			     pfcp_tool_cmdline_config.command_file);
			return 1;
		}
		printf("Done reading '%s', waiting for retransmission queue...\n",
		       pfcp_tool_cmdline_config.command_file);
		do {
			if (pfcp_tool_mainloop())
				break;
		} while (osmo_pfcp_endpoint_retrans_queue_is_busy(g_pfcp_tool->ep));
		printf("Done\n");
	} else {
		printf("Listening for commands on VTY...\n");
		do {
			if (pfcp_tool_mainloop())
				break;
		} while (!osmo_select_shutdown_done());
	}

	osmo_pfcp_endpoint_free(&g_pfcp_tool->ep);

	log_fini();

	/* Report the heap state of talloc contexts, then free, so both ASAN and Valgrind are happy... */
	//talloc_report_full(tall_pfcp_tool_ctx, stderr);
	talloc_free(tall_pfcp_tool_ctx);

	//talloc_report_full(tall_vty_ctx, stderr);
	talloc_free(tall_vty_ctx);

	//talloc_report_full(NULL, stderr);
	talloc_disable_null_tracking();
	return 0;
}
