#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/rate_ctr.h>

#include <osmocom/vty/command.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/misc.h>

#include "internal.h"
#include "gtp.h"

#define TUN_STR	"tun device commands\n"
#define GTP_EP_STR "GTP endpoint commands\n"
#define TUNNEL_STR "GTP tunnel commands\n"

static void show_tun_hdr(struct vty *vty)
{
	vty_out(vty,
		" tun device name | netwk  namespace | use count%s", VTY_NEWLINE);
	vty_out(vty,
		"---------------- | ---------------- | ---------%s", VTY_NEWLINE);
}

static void show_one_tun(struct vty *vty, const struct tun_device *tun)
{
	vty_out(vty, "%16s | %16s | %lu%s",
		tun->devname, tun->netns_name, tun->use_count, VTY_NEWLINE);
}

DEFUN(show_tun, show_tun_cmd,
	"show tun-device [IFNAME]",
	SHOW_STR TUN_STR
	"Name of TUN network device\n")
{
	struct tun_device *tun;

	show_tun_hdr(vty);
	pthread_rwlock_rdlock(&g_daemon->rwlock);
	if (argc) {
		tun = _tun_device_find(g_daemon, argv[0]);
		if (!tun) {
			pthread_rwlock_unlock(&g_daemon->rwlock);
			vty_out(vty, "Cannot find TUN device '%s'%s", argv[0], VTY_NEWLINE);
			return CMD_WARNING;
		}
		show_one_tun(vty, tun);
	} else {
		llist_for_each_entry(tun, &g_daemon->tun_devices, list)
			show_one_tun(vty, tun);
	}
	pthread_rwlock_unlock(&g_daemon->rwlock);

	return CMD_SUCCESS;
}

DEFUN(tun_create, tun_create_cmd,
	"tun-device create IFNAME [NETNS]",
	TUN_STR "Create a new TUN interface\n"
	"Name of TUN network device\n"
	"Name of network namespace for tun device\n"
	)
{
	struct tun_device *tun;
	const char *ifname = argv[0];
	const char *netns_name = NULL;

	if (argc > 1)
		netns_name = argv[1];

	tun = tun_device_find_or_create(g_daemon, ifname, netns_name);
	if (!tun) {
		vty_out(vty, "Error creating TUN%s", VTY_NEWLINE);
		return CMD_WARNING;
	}

	return CMD_SUCCESS;
}

DEFUN(tun_destroy, tun_destroy_cmd,
	"tun-device destroy IFNAME",
	TUN_STR "Destroy a TUN interface\n"
	"Name of TUN network device\n"
	)
{
	struct tun_device *tun;
	const char *ifname = argv[0];

	pthread_rwlock_wrlock(&g_daemon->rwlock);
	tun = _tun_device_find(g_daemon, ifname);
	if (!tun) {
		pthread_rwlock_unlock(&g_daemon->rwlock);
		vty_out(vty, "Cannot destrory non-existant TUN%s", VTY_NEWLINE);
		return CMD_WARNING;
	}
	_tun_device_deref_release(tun);
	pthread_rwlock_unlock(&g_daemon->rwlock);

	return CMD_SUCCESS;
}


static void show_ep_hdr(struct vty *vty)
{
	vty_out(vty,
		"                    address port | use count%s", VTY_NEWLINE);
	vty_out(vty,
		" ------------------------------- | ---------%s", VTY_NEWLINE);
}

static void show_one_ep(struct vty *vty, const struct gtp_endpoint *ep)
{
	vty_out(vty, "%32s | %lu%s",
		ep->name, ep->use_count, VTY_NEWLINE);

}

DEFUN(show_gtp, show_gtp_cmd,
	"show gtp-endpoint [(A.B.C.D|X:X::X:X) [<0-65535>]]",
	SHOW_STR GTP_EP_STR
	"Local IP address\n" "Local UDP Port\n")
{
	struct gtp_endpoint *ep;
	struct addrinfo *ai = NULL;
	const char *ipstr;
	uint16_t port = GTP1U_PORT;

	if (argc > 0) {
		ipstr = argv[0];
		if (argc > 1)
			port = atoi(argv[1]);

		ai = addrinfo_helper(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, ipstr, port, true);
		if (!ai) {
			vty_out(vty, "Error parsing IP/Port%s", VTY_NEWLINE);
			return CMD_WARNING;
		}
	}

	show_ep_hdr(vty);
	pthread_rwlock_rdlock(&g_daemon->rwlock);
	if (argc > 0) {
		ep = _gtp_endpoint_find(g_daemon, (const struct osmo_sockaddr *) ai->ai_addr);
		if (!ep) {
			pthread_rwlock_unlock(&g_daemon->rwlock);
			vty_out(vty, "Cannot find GTP endpoint %s:%s%s", argv[0], argv[1], VTY_NEWLINE);
			freeaddrinfo(ai);
			return CMD_WARNING;
		}
		show_one_ep(vty, ep);
	} else {
		llist_for_each_entry(ep, &g_daemon->gtp_endpoints, list)
			show_one_ep(vty, ep);
	}
	pthread_rwlock_unlock(&g_daemon->rwlock);

	freeaddrinfo(ai);
	return CMD_SUCCESS;
}

DEFUN(gtp_create, gtp_create_cmd,
	"gtp-endpoint create (A.B.C.D|X:X::X:X) [<0-65535>]",
	GTP_EP_STR "Create a new GTP endpoint (UDP socket)\n"
	"Local IP address\n" "Local UDP Port\n")
{
	struct addrinfo *ai;
	struct gtp_endpoint *ep;
	const char *ipstr = argv[0];
	uint16_t port = GTP1U_PORT;

	if (argc > 1)
		port = atoi(argv[1]);

	ai = addrinfo_helper(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, ipstr, port, true);
	if (!ai) {
		vty_out(vty, "Error parsing IP/Port%s", VTY_NEWLINE);
		return CMD_WARNING;
	}

	ep = gtp_endpoint_find_or_create(g_daemon, (const struct osmo_sockaddr *) ai->ai_addr);
	if (!ep) {
		vty_out(vty, "Error creating endpoint%s", VTY_NEWLINE);
		freeaddrinfo(ai);
		return CMD_WARNING;
	}

	freeaddrinfo(ai);
	return CMD_SUCCESS;
}

DEFUN(gtp_destroy, gtp_destroy_cmd,
	"gtp-endpoint destroy (A.B.C.D|X:X::X:X) [<0-65535>]",
	GTP_EP_STR "Destroy a GTP endpoint\n"
	"Local IP address\n" "Local UDP Port\n")
{
	struct addrinfo *ai;
	struct gtp_endpoint *ep;
	const char *ipstr = argv[0];
	uint16_t port = GTP1U_PORT;

	if (argc > 1)
		port = atoi(argv[1]);

	ai = addrinfo_helper(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, ipstr, port, true);
	if (!ai) {
		vty_out(vty, "Error parsing IP/Port%s", VTY_NEWLINE);
		return CMD_WARNING;
	}

	pthread_rwlock_wrlock(&g_daemon->rwlock);
	ep = _gtp_endpoint_find(g_daemon, (const struct osmo_sockaddr *) ai->ai_addr);
	if (!ep) {
		pthread_rwlock_unlock(&g_daemon->rwlock);
		vty_out(vty, "Cannot find to-be-destoryed endpoint%s", VTY_NEWLINE);
		freeaddrinfo(ai);
		return CMD_WARNING;
	}
	_gtp_endpoint_deref_destroy(ep);
	pthread_rwlock_unlock(&g_daemon->rwlock);

	freeaddrinfo(ai);
	return CMD_SUCCESS;
}

static void show_one_tunnel(struct vty *vty, const struct gtp_tunnel *t)
{
	char remote_ip[64], remote_port[16], user_addr_v4[64], user_addr_v6[64];

	getnameinfo(&t->remote_udp.u.sa, sizeof(t->remote_udp.u.sas),
		    remote_ip, sizeof(remote_ip), remote_port, sizeof(remote_port),
		    NI_NUMERICHOST|NI_NUMERICSERV);

	if (t->user_addr_type == GTP1U_EUA_TYPE_IPv4 || t->user_addr_type == GTP1U_EUA_TYPE_IPv4v6) {
		getnameinfo(&t->user_addr_ipv4.u.sa, sizeof(t->user_addr_ipv4.u.sas),
			    user_addr_v4, sizeof(user_addr_v4), NULL, 0,
			    NI_NUMERICHOST|NI_NUMERICSERV);
	} else {
		user_addr_v4[0] = '\0';
	}
	if ((t->user_addr_type == GTP1U_EUA_TYPE_IPv6 || t->user_addr_type == GTP1U_EUA_TYPE_IPv4v6) &&
	    t->user_addr_ipv6_global.u.sa.sa_family == AF_INET6) {
		getnameinfo(&t->user_addr_ipv6_global.u.sa, sizeof(t->user_addr_ipv6_global.u.sas),
			    user_addr_v6, sizeof(user_addr_v6), NULL, 0,
			    NI_NUMERICHOST|NI_NUMERICSERV);
	} else {
		user_addr_v6[0] = '\0';
	}

	vty_out(vty, "%s/%08X - %s:%s/%08X %s(%s) %s%s%s%s",
		t->gtp_ep->name, t->rx_teid,
		remote_ip, remote_port, t->tx_teid,
		t->tun_dev->devname, t->tun_dev->netns_name, user_addr_v4,
		(user_addr_v4[0] != '\0' && user_addr_v6[0] != '\0') ? " " : "",
		user_addr_v6, VTY_NEWLINE);
}

DEFUN(show_tunnel, show_tunnel_cmd,
	"show gtp-tunnel",
	SHOW_STR TUNNEL_STR)
{
	struct gtp_tunnel *t;

	pthread_rwlock_rdlock(&g_daemon->rwlock);
	llist_for_each_entry(t, &g_daemon->gtp_tunnels, list) {
		show_one_tunnel(vty, t);
	}
	pthread_rwlock_unlock(&g_daemon->rwlock);
	return CMD_SUCCESS;
}

#define UECUPS_NODE	(_LAST_OSMOVTY_NODE+1)

static struct cmd_node uecups_node = {
	UECUPS_NODE,
	"%s(config-uecups)# ",
	1,
};

static int config_write_uecups(struct vty *vty)
{
	vty_out(vty, "uecups%s", VTY_NEWLINE);
	vty_out(vty, " local-ip %s%s", g_daemon->cfg.cups_local_ip, VTY_NEWLINE);

	return CMD_SUCCESS;
}

DEFUN(cfg_uecups, cfg_uecups_cmd,
	"uecups",
	"Configure the UE Control/User Plane Socket\n")
{
	vty->node = UECUPS_NODE;
	return CMD_SUCCESS;
}

DEFUN(cfg_uecups_local_ip, cfg_uecups_local_ip_cmd,
	"local-ip A.B.C.D",
	"Set the IP address to which we bind locally\n"
	"IP Address\n")
{
	osmo_talloc_replace_string(g_daemon, &g_daemon->cfg.cups_local_ip, argv[0]);
	return CMD_SUCCESS;
}


int gtpud_vty_init(void)
{
	install_element_ve(&show_tun_cmd);
	install_element(ENABLE_NODE, &tun_create_cmd);
	install_element(ENABLE_NODE, &tun_destroy_cmd);

	install_element_ve(&show_gtp_cmd);
	install_element(ENABLE_NODE, &gtp_create_cmd);
	install_element(ENABLE_NODE, &gtp_destroy_cmd);

	install_element_ve(&show_tunnel_cmd);

	install_element(CONFIG_NODE, &cfg_uecups_cmd);
	install_node(&uecups_node, config_write_uecups);
	install_element(UECUPS_NODE, &cfg_uecups_local_ip_cmd);

	return 0;
}


static const char copyright[] =
	"Copyright (C) 2020 Harald Welte <laforge@gnumonks.org>\r\n"
	"License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl-2.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";

struct vty_app_info g_vty_info = {
	.name		= "osmo-gtpud",
	.version	= PACKAGE_VERSION,
	.copyright	= copyright,
};
