#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include "kernel_ax25.h"
#include "kernel_rose.h"
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netax25/axlib.h>
#include <netax25/axconfig.h>
#include <netax25/nrconfig.h>
#include <netax25/procutils.h>
#include <netax25/mheard.h>

#include "node.h"
#include "sysinfo.h"

struct cmd *Nodecmds = NULL;

void init_nodecmds(void)
{
	add_internal_cmd(&Nodecmds, "?",        1, do_help);
	add_internal_cmd(&Nodecmds, "Bye",      1, do_bye);
	add_internal_cmd(&Nodecmds, "Connect",  1, do_connect);
	add_internal_cmd(&Nodecmds, "Escape",   1, do_escape);
	add_internal_cmd(&Nodecmds, "Finger",   1, do_finger);
	add_internal_cmd(&Nodecmds, "Help",     1, do_help);
	add_internal_cmd(&Nodecmds, "HOst",     2, do_host);
	add_internal_cmd(&Nodecmds, "Info",     1, do_help);
	add_internal_cmd(&Nodecmds, "Links",    1, do_links);
	add_internal_cmd(&Nodecmds, "Mheard",   1, do_mheard);
#ifdef HAVE_NETROM
	add_internal_cmd(&Nodecmds, "NLinks",   2, do_nlinks);
	add_internal_cmd(&Nodecmds, "Nodes",    1, do_nodes);
#endif
	add_internal_cmd(&Nodecmds, "PIng",     2, do_ping);
	add_internal_cmd(&Nodecmds, "Ports",    1, do_ports);
#ifdef HAVE_NETROM
	add_internal_cmd(&Nodecmds, "Routes",   1, do_routes);
#endif
	add_internal_cmd(&Nodecmds, "Status",   1, do_status);
	add_internal_cmd(&Nodecmds, "TAlk",     2, do_talk);
	add_internal_cmd(&Nodecmds, "Telnet",   1, do_connect);
	add_internal_cmd(&Nodecmds, "Users",    1, do_users);
#ifdef HAVE_ZLIB_H
	add_internal_cmd(&Nodecmds, "ZConnect", 1, do_connect);
	add_internal_cmd(&Nodecmds, "ZTelnet",  1, do_connect);
#endif
};

void logout(char *reason)
{
	axio_end_all();
	logout_user();
	ipc_close();
	log(L_LOGIN, "%s @ %s logged out: %s", User.call, User.ul_name, reason);
	free_cmdlist(Nodecmds);
	Nodecmds = NULL;
	exit(0);
}

int do_bye(int argc, char **argv)
{
	node_msg("Goodbye");
	logout("Bye");
	return 0;	/* Keep gcc happy */
}

int do_escape(int argc, char **argv)
{
	int now = 0;

	if (argc > 1) {
		EscChar = get_escape(argv[1]);
		now = 1;
	}
	if (EscChar < -1 || EscChar > 255) {
		node_msg("Invalid escape character: %s", argv[1]);
		return 0;
	}
	if (EscChar == -1) {
		node_msg("The escape mechanism is %sdisabled",
			 now ? "now " : "");
		return 0;
	}
	node_msg("The escape character is %s%s%c",
		 now ? "now " : "",
		 EscChar < 32 ? "CTRL-" : "",
		 EscChar < 32 ? (EscChar + 'A' - 1) : EscChar);
	return 0;
}

struct mheard_list {
	struct mheard_struct	data;
	struct mheard_list	*next;
};

int do_mheard(int argc, char **argv)
{
	FILE *fp;
        struct mheard_struct mh;
        struct mheard_list *list, *new, *tmp, *p;
	char *s, *t, *u;
	long ti;

	if (argc < 2) {
		node_msg("Usage: mheard <port>");
		return 0;
	}
	if (ax25_config_get_dev(argv[1]) == NULL ||
	    (check_perms(PERM_HIDDEN, 0) == -1 && is_hidden(argv[1]))) {
		node_msg("Invalid port");
		return 0;
	}
        if ((fp = fopen(DATA_MHEARD_FILE, "r")) == NULL) {
		node_perror(DATA_MHEARD_FILE, errno);
		return 0;
	}
	list = NULL;
	while (fread(&mh, sizeof(struct mheard_struct), 1, fp) == 1) {
		if (strcmp(argv[1], mh.portname))
			continue;
                if ((new = calloc(1, sizeof(struct mheard_list))) == NULL) {
			node_perror("do_mheard: calloc", errno);
			break;
		}
		new->data = mh;
		if (list == NULL || mh.last_heard > list->data.last_heard) {
                        tmp = list;
                        list = new;
                        new->next = tmp;
		} else {
                        for (p = list; p->next != NULL; p = p->next)
                                if (mh.last_heard > p->next->data.last_heard)
                                        break;
                        tmp = p->next;
                        p->next = new;
                        new->next = tmp;
		}
	}
	fclose(fp);
	node_msg("Heard list for port %s:", argv[1]);
	nputs("Callsign  Frames   Last heard                      Pids\n");
	while (list != NULL) {
		s = ctime(&list->data.last_heard);
		s[19] = 0;
		s += 4;
		t = ax25_ntoa(&list->data.from_call);
		if ((u = strstr(t, "-0")) != NULL)
			*u = '\0';
		nprintf("%-9s %-8ld %s  (", t, list->data.count, s);
		ti = time(NULL) - list->data.last_heard;
		if (ti < 60L)
			nprintf("    %2lds", ti);
		else if (ti < 3600L)
			nprintf("%2ldm %2lds", ti / 60L, ti % 60L);
		else if (ti < 86400L)
			nprintf("%2ldh %2ldm", ti / 3600L, (ti % 3600L) / 60L);
		else
			nprintf("%2ldd %2ldh", ti / 86400L, (ti % 86400L) / 3600L);
		nputs(" ago) ");
		if (list->data.mode & MHEARD_MODE_ARP)
			nprintf(" ARP");
		if (list->data.mode & MHEARD_MODE_FLEXNET)
			nprintf(" FlexNet");
		if (list->data.mode & MHEARD_MODE_IP_DG)
			nprintf(" IP-DG");
		if (list->data.mode & MHEARD_MODE_IP_VC)
			nprintf(" IP-VC");
		if (list->data.mode & MHEARD_MODE_NETROM)
			nprintf(" NET/ROM");
		if (list->data.mode & MHEARD_MODE_ROSE)
			nprintf(" Rose");
		if (list->data.mode & MHEARD_MODE_SEGMENT)
			nprintf(" Segment");
		if (list->data.mode & MHEARD_MODE_TEXNET)
			nprintf(" TexNet");
		if (list->data.mode & MHEARD_MODE_TEXT)
			nprintf(" Text");
		if (list->data.mode & MHEARD_MODE_PSATFT)
			nprintf(" PacsatFT");
		if (list->data.mode & MHEARD_MODE_PSATPB)
			nprintf(" PacsatPB");
		if (list->data.mode & MHEARD_MODE_UNKNOWN)
			nprintf(" Unknown");
		nputs("\n");
		tmp = list;
		list = list->next;
		free(tmp);
	}
	return 0;
}

int do_help(int argc, char **argv)
{
	FILE *fp;
	char fname[80], line[256];
	struct cmd *cmdp;
	int i = 0;

	if (*argv[0] == '?') {				/* "?"		*/
		node_msg("Commands:");
		for (cmdp = Nodecmds; cmdp != NULL; cmdp = cmdp->next) {
			nprintf("%s%s", i ? ", " : "", cmdp->name);
			if (++i == 10) {
				nprintf("\n");
				i = 0;
			}
		}
		if (i) nprintf("\n");
		return 0;
	}
	strcpy(fname, DATA_NODE_HELP_DIR);
	if (*argv[0] == 'i') {				/* "info"	*/
		strcpy(fname, CONF_NODE_INFO_FILE);
		node_msg("%s", VERSION);
	} else if (argc == 1) {				/* "help"	*/
		strcat(fname, "help.hlp");
	} else {					/* "help <cmd>"	*/
		if (strchr(argv[1], '/') == NULL) {
			strlwr(argv[1]);
			strcat(fname, argv[1]);
			strcat(fname, ".hlp");
		}
	}
	if ((fp = fopen(fname, "r")) == NULL) {
		if (*argv[0] != 'i')
			node_msg("No help for command %s", argv[1] ? argv[1] : "help");
		return 0;
	}
	if (*argv[0] != 'i')
		node_msg("Help for command %s", argv[1] ? argv[1] : "help");
	while (fgets(line, 256, fp) != NULL)
		nputs(line);
	nputs("\n-- \n");
	fclose(fp);
	return 0;
}

int do_host(int argc, char **argv)
{
	struct hostent *h;
	struct in_addr addr;
	char **p;

	if (argc < 2) {
		node_msg("Usage: host <hostname>|<ip address>");
		return 0;
	}
	if (inet_aton(argv[1], &addr) != 0)
		h = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
	else
		h = gethostbyname(argv[1]);
	if (h == NULL) {
		node_msg("%s", strherror(h_errno));
		return 0;
	}
	node_msg("Host name information for %s:", argv[1]);
	nprintf("Hostname:    %s\n", h->h_name);
	nputs("Aliases:    ");
	p = h->h_aliases;
	while (*p != NULL) {
		nprintf(" %s", *p);
		p++;
	}
	nputs("\nAddress(es):");
	p = h->h_addr_list;
	while (*p != NULL) {
		addr.s_addr = ((struct in_addr *)(*p))->s_addr;
		nprintf(" %s", inet_ntoa(addr));
		p++;
	}
	nputs("\n");
	return 0;
}

int do_ports(int argc, char **argv)
{
	char *cp = NULL;

	node_msg("Ports:\nPort   Description");
	while ((cp = ax25_config_get_next(cp)) != NULL) {
		if (is_hidden(cp) && check_perms(PERM_HIDDEN, 0L) == -1)
			continue;
		nprintf("%-6s %s\n", cp, ax25_config_get_desc(cp));
	}
	return 0;
}

int do_links(int argc, char **argv)
{
	struct proc_ax25 *p, *list;
	char *cp;

	node_msg("AX.25 Link Status:");
	if ((list = read_proc_ax25()) == NULL) {
		if (errno)
			node_perror("do_links: read_proc_ax25:", errno);
		return 0;
	}
	nprintf("Port   Remote    Local     State        Unack T1      Retr  Rtt Snd-Q Rcv-Q\n");
	for (p = list; p != NULL; p = p->next) {
		if (argc > 1 && strcasecmp(argv[1], "*") && strcasecmp(p->dest_addr, argv[1]) && strcasecmp(p->src_addr, argv[1]))
			continue;
		if ((argc < 2) && !strcmp(p->dest_addr, "*"))
			continue;
		if (strcmp(p->dev, "???") != 0) {
			cp = ax25_config_get_name(p->dev);
			if (is_hidden(cp) && check_perms(PERM_HIDDEN, 0L) == -1)
				continue;
		} else
			cp = "*";
		nprintf("%-6s ", cp);
		nprintf("%-9s %-9s ", p->dest_addr, p->src_addr);
#if 0
		nprintf("%-9s", p->dest_addr);
		for (i = 0; i < p->ndigi; i++) {
			if (i == 0)
				nprintf(" via ");
			else
				nprintf(",");
			nprintf("%s", p->digi_addr[i]);
		}
		nprintf(" %-9s ", p->src_addr);
#endif
		if (!strcmp(p->dest_addr, "*")) {
			nprintf("Listening\n");
			continue;
		}
		switch (p->st) {
		case 0:
			nputs("Disconnected ");
			break;
		case 1:
			nputs("Conn pending ");
			break;
		case 2:
			nputs("Disc pending ");
			break;
		case 3:
			nputs("Connected    ");
			break;
		case 4:
			nputs("Recovery     ");
			break;
		default:
			nputs("Unknown      ");
			break;
		}
		nprintf("%02d/%02d %03d/%03d %02d/%02d %-3d %-5d %-5d\n",
			p->vs < p->va ? p->vs - p->va + 8 : p->vs - p->va,
			p->window,
			p->t1timer, p->t1,
			p->n2count, p->n2,
			p->rtt,
			p->sndq, p->rcvq);
	}
	free_proc_ax25(list);
	return 0;
}

#ifdef HAVE_NETROM
int do_nlinks(int argc, char **argv)
{
	struct proc_nr *p, *list;
	char *cp;

	node_msg("NET/ROM Link Status:");
	if ((list = read_proc_nr()) == NULL) {
		if (errno)
			node_perror("do_links: read_proc_nr:", errno);
		return 0;
	}
	nprintf("User      Remote    Local     State        My id Ur id Unack T1      N2  Snd-Q\n");
	for (p = list; p != NULL; p = p->next) {
		cp = nr_config_get_name(p->dev);
		if (is_hidden(cp) && check_perms(PERM_HIDDEN, 0L) == -1)
			continue;
		nprintf("%-9s %-9s %-9s ", p->user_addr, p->dest_node, p->src_node);
		if (!strcmp(p->user_addr, "*")) {
			nprintf("Listening\n");
			continue;
		}
		switch (p->st) {
		case 0:
			nputs("Disconnected ");
			break;
		case 1:
			nputs("Conn pending ");
			break;
		case 2:
			nputs("Disc pending ");
			break;
		case 3:
			nputs("Connected    ");
			break;
		default:
			nputs("Unknown      ");
			break;
		}
		nprintf("%s %s ", p->my_circuit, p->ur_circuit);
		nprintf("%02d/%02d %03d/%03d %d/%d %-5d\n",
			p->vs < p->va ? p->vs - p->va + 256 : p->vs - p->va,
			p->window,
			p->t1timer, p->t1,
			p->n2count, p->n2,
			p->sndq);
	}
	free_proc_nr(list);
	return 0;
}

int do_routes(int argc, char **argv)
{
	struct proc_nr_neigh *p, *list;
	struct proc_ax25 *ap;
	char *cp;
	int link;

	node_msg("Routes:\nLink Port   Callsign  Quality Destinations Lock");
	if ((list = read_proc_nr_neigh()) == NULL) {
		if (errno)
			node_perror("do_routes: read_proc_nr_neigh", errno);
		return 0;
	}
	for (p = list; p != NULL; p = p->next) {
		cp = ax25_config_get_name(p->dev);
		if (is_hidden(cp) && check_perms(PERM_HIDDEN, 0L) == -1)
			continue;
		if ((ap = find_link(nr_config_get_addr(NULL), p->call, p->dev)) != NULL && ap->st >= 3)
			link = 1;
		else
			link = 0;
		nprintf("%c    %-6s %-9s %-7d %-12d %c\n",
			link == 1 ? '>' : ' ',
			cp,
			p->call,
			p->qual,
			p->cnt,
			p->lock == 1 ? '!' : ' ');
	}
	free_proc_nr_neigh(list);
	return 0;
}

int do_nodes(int argc, char **argv)
{
	struct proc_nr_nodes *p, *list;
	struct proc_nr_neigh *np, *nlist;
	int i = 0;

	if ((list = read_proc_nr_nodes()) == NULL) {
		if (errno)
			node_perror("do_nodes: read_proc_nr_nodes", errno);
		else
			node_msg("No known nodes");
		return 0;
	}
	/* "nodes" */
	if (argc == 1) {
		node_msg("Nodes:");
		for (p = list; p != NULL; p = p->next) {
			nprintf("%-16.16s %c",
				print_node(p->alias, p->call),
				(++i % 4) ? ' ' : '\n');
		}
		if ((i % 4) != 0) nprintf("\n");
		free_proc_nr_nodes(list);
		return 0;
	}
	if ((nlist = read_proc_nr_neigh()) == NULL) {
		node_perror("do_nodes: read_proc_nr_neigh", errno);
		return 0;
	}
	/* "nodes *" */
	if (*argv[1] == '*') {
		node_msg("Nodes:");
		nprintf("Node              Quality Obsolescence Port   Neighbour\n");
		for (p = list; p != NULL; p = p->next) {
			nprintf("%-16.16s  ", print_node(p->alias, p->call));
			/*
			 * p->n == 0 indicates a local node with no routes.
			 */
			if (p->n > 0 && (np = find_neigh(p->addr1, nlist)) != NULL) {
				nprintf("%-7d %-12d %-6s %s",
					p->qual1, p->obs1,
					ax25_config_get_name(np->dev),
					np->call);
			}
			nprintf("\n");
			if (p->n > 1 && (np = find_neigh(p->addr2, nlist)) != NULL) {
				nprintf("                  ");
				nprintf("%-7d %-12d %-6s %s\n",
					p->qual2, p->obs2,
					ax25_config_get_name(np->dev),
					np->call);
			}
			if (p->n > 2 && (np = find_neigh(p->addr3, nlist)) != NULL) {
				nprintf("                  ");
				nprintf("%-7d %-12d %-6s %s\n",
					p->qual3, p->obs3,
					ax25_config_get_name(np->dev),
					np->call);
			}
		}
		free_proc_nr_nodes(list);
		free_proc_nr_neigh(nlist);
		return 0;
	}
	/* "nodes <node>" */
	p = find_node(argv[1], list);
	if (p != NULL && p->n == 0) {
		node_msg("%s is a local node", print_node(p->alias, p->call));
	} else if (p != NULL) {
		node_msg("Routes to: %s", print_node(p->alias, p->call));
		nprintf("Which Quality Obsolescence Port   Neighbour\n");
		if (p->n > 0 && (np = find_neigh(p->addr1, nlist)) != NULL) {
			nprintf("%c     %-7d %-12d %-6s %s\n",
				p->w == 1 ? '>' : ' ',
				p->qual1, p->obs1,
				ax25_config_get_name(np->dev),
				np->call);
		}
		if (p->n > 1 && (np = find_neigh(p->addr2, nlist)) != NULL) {
			nprintf("%c     %-7d %-12d %-6s %s\n",
				p->w == 2 ? '>' : ' ',
				p->qual2, p->obs2,
				ax25_config_get_name(np->dev),
				np->call);
		}
		if (p->n > 1 && (np = find_neigh(p->addr3, nlist)) != NULL) {
			nprintf("%c     %-7d %-12d %-6s %s\n",
				p->w == 3 ? '>' : ' ',
				p->qual3, p->obs3,
				ax25_config_get_name(np->dev),
				np->call);
		}
	} else {
		node_msg("No such node");
	}
	free_proc_nr_nodes(list);
	free_proc_nr_neigh(nlist);
	return 0;
}
#endif /* HAVE_NETROM */

/*
 * by Heikki Hannikainen <hessu@pspt.fi> 
 * The following was mostly learnt from the procps package and the
 * gnu sh-utils (mainly uname).
 */

int do_status(int argc, char **argv)
{
	int upminutes, uphours, updays;
	double uptime_secs;
	double av[3];
	struct utsname name;
	time_t t;
#ifdef HAVE_NETROM
        struct proc_nr_nodes *nop, *nolist;
        struct proc_nr_neigh *nep, *nelist;
	int n;
#endif

	node_msg("Status:");
	time(&t);
	nprintf("System time:       %s", ctime(&t));
	if (uname(&name) == -1)
		nprintf("Cannot get system name\n");
	else {
		nprintf("Hostname:          %s\n", name.nodename);
		nprintf("Operating system:  %s %s (%s)\n",
			name.sysname, name.release, name.machine);
	}
	/* read and calculate the amount of uptime and format it nicely */
	uptime(&uptime_secs, NULL);
	updays = (int) uptime_secs / (60*60*24);
	upminutes = (int) uptime_secs / 60;
	uphours = upminutes / 60;
	uphours = uphours % 24;
	upminutes = upminutes % 60;
	nprintf("Uptime:            ");
  	if (updays)
		nprintf("%d day%s, ", updays, (updays != 1) ? "s" : "");
	if(uphours)
		nprintf("%d hour%s ", uphours, (uphours != 1) ? "s" : "");
	nprintf("%d minute%s\n", upminutes, (upminutes != 1) ? "s" : "");
	loadavg(&av[0], &av[1], &av[2]);
  	nprintf("Load average:      %.2f, %.2f, %.2f\n", av[0], av[1], av[2]);
	if (load_meminfo()) {
		nputs("Memory:            ");
		nprintf("%5d KB available, %5d KB used, %5d KB free\n",
			meminfo("memtotal"),
			meminfo("memtotal") - meminfo("memfree") -
			meminfo("buffers") - meminfo("cached"),
			meminfo("memfree") + meminfo("buffers") +
			meminfo("cached"));
		nputs("Swap:              ");
		nprintf("%5d KB available, %5d KB used, %5d KB free\n",
			meminfo("swaptotal"),
			meminfo("swaptotal") - meminfo("swapfree"),
			meminfo("swapfree"));
	}
#ifdef HAVE_NETROM
	if ((nolist = read_proc_nr_nodes()) == NULL && errno != 0)
		node_perror("do_status: read_proc_nr_nodes", errno);
	n = 0;
	for (nop = nolist; nop != NULL; nop = nop->next)
		n++;
	free_proc_nr_nodes(nolist);
	nprintf("Nodes:             %d\n", n);
	if ((nelist = read_proc_nr_neigh()) == NULL && errno != 0)
		node_perror("do_status: read_proc_nr_neigh", errno);
	n = 0;
	for (nep = nelist; nep != NULL; nep = nep->next)
		n++;
	free_proc_nr_neigh(nelist);
	nprintf("Routes:            %d\n", n);
#endif
	return 0;
}
