#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netax25/ax25.h>
#include <netrose/rose.h>
//#include <linux/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#include "config.h"
#include "node.h"
#include "io.h"
#include "axlib.h"
#include "axconfig.h"
#include "nrconfig.h"
#include "procutils.h"
#include "mheard.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);
	add_internal_cmd(&Nodecmds, "Nodes",    1, do_nodes);
	add_internal_cmd(&Nodecmds, "PIng",     2, do_ping);
	add_internal_cmd(&Nodecmds, "Ports",    1, do_ports);
	add_internal_cmd(&Nodecmds, "Routes",   1, do_routes);
	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, user_list);
};

void logout(char *reason)
{
	end_io(User.fd);
	close(User.fd);
	logout_user();
	ipc_close();
	log(LOGLVL_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)
{
	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]);
	tprintf("Callsign  Pkts rcvd Last heard\n");
	while (list != NULL) {
		s = ctime(&list->data.last_heard);
		s[19] = '\0';
		t = ax25_ntoa(&list->data.from_call);
		if ((u = strstr(t, "-0")) != NULL)
			*u = '\0';
		tprintf("%-9s %-9ld %s  (", t, list->data.count, s);
		ti = time(NULL) - list->data.last_heard;
		if (ti < 60L)
			tprintf("        %3ld sec", ti);
		else if (ti < 3600L)
			tprintf("%3ld min %3ld sec", ti / 60L, ti % 60L);
		else
			tprintf("%3ld  hr %3ld min", ti / 3600L, (ti % 3600L) / 60L);
		tprintf(" ago)\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) {
			tprintf("%s%s", i ? ", " : "", cmdp->name);
			if (++i == 10) {
				tprintf("\n");
				i = 0;
			}
		}
		if (i) tprintf("\n");
		return 0;
	}
	strcpy(fname, DATA_NODE_HELP_DIR);
	if (*argv[0] == 'i') {				/* "info"	*/
		strcpy(fname, CONF_NODE_INFO_FILE);
		node_msg("%s (%s)", VERSIONNODE);
	} 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)
		tputs(line);
	tputs("\n-- \n");
	fclose(fp);
	return 0;
}

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

	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) {
		switch (h_errno) {
		case HOST_NOT_FOUND:
			cp = "Unknown host";
			break;
		case TRY_AGAIN:
			cp = "Temporary name server error";
			break;
		case NO_RECOVERY:
			cp = "Non-recoverable name server error";
			break;
		case NO_ADDRESS:
			cp = "No address";
			break;
		default:
			cp = "Unknown error";
			break;
		}
		node_msg("%s", cp);
		return 0;
	}
	node_msg("Host name information for %s:", argv[1]);
	tprintf("Hostname:    %s\n", h->h_name);
	tputs("Aliases:    ");
	p = h->h_aliases;
	while (*p != NULL) {
		tprintf(" %s", *p);
		p++;
	}
	tputs("\nAddress(es):");
	p = h->h_addr_list;
	while (*p != NULL) {
		addr.s_addr = ((struct in_addr *)(*p))->s_addr;
		tprintf(" %s", inet_ntoa(addr));
		p++;
	}
	tputs("\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;
		tprintf("%-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;
	}
	tprintf("Port   Dest addr Src addr  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;
		cp = ax25_config_get_name(p->dev);
		if (is_hidden(cp) && check_perms(PERM_HIDDEN, 0L) == -1)
			continue;
		tprintf("%-6s %-9s %-9s ", cp, p->dest_addr, p->src_addr);
		if (!strcmp(p->dest_addr, "*")) {
			tprintf("Listening\n");
			continue;
		}
		switch (p->st) {
		case 0:
			cp = "Disconnected";
			break;
		case 1:
			cp = "Conn pending";
			break;
		case 2:
			cp = "Disc pending";
			break;
		case 3:
			cp = "Connected   ";
			break;
		case 4:
			cp = "Recovery    ";
			break;
		default:
			cp = "Unknown     ";
			break;
		}
		tprintf("%s %02d/%02d %03d/%03d %02d/%02d %-3d %-5d %-5d\n",
			cp,
			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;
}

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;
		tprintf("%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) {
			tprintf("%-16.16s %c",
				print_node(p->alias, p->call),
				(++i % 4) ? ' ' : '\n');
		}
		if ((i % 4) != 0) tprintf("\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:");
		tprintf("Node              Quality Obsolescence Port   Neighbour\n");
		for (p = list; p != NULL; p = p->next) {
			tprintf("%-16.16s  ", print_node(p->alias, p->call));
			if ((np = find_neigh(p->addr1, nlist)) != NULL) {
				tprintf("%-7d %-12d %-6s %s\n",
					p->qual1, p->obs1,
					ax25_config_get_name(np->dev),
					np->call);
			}
			if (p->n > 1 && (np = find_neigh(p->addr2, nlist)) != NULL) {
				tprintf("                  ");
				tprintf("%-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) {
				tprintf("                  ");
				tprintf("%-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) {
		node_msg("Routes to: %s", print_node(p->alias, p->call));
		tprintf("Which Quality Obsolescence Port   Neighbour\n");
		if ((np = find_neigh(p->addr1, nlist)) != NULL) {
			tprintf("%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) {
			tprintf("%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) {
			tprintf("%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;
}

/*
 * 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, idle_secs;
	double av[3];
	unsigned **mem;
	struct utsname name;
	time_t t;
        struct proc_nr_nodes *nop, *nolist;
        struct proc_nr_neigh *nep, *nelist;
	int n;

	node_msg("Status:");
	time(&t);
	tprintf("System time:       %s", ctime(&t));
	if (uname(&name) == -1)
		tprintf("Cannot get system name\n");
	else {
		tprintf("Hostname:          %s\n", name.nodename);
#ifndef SECURITY		
		tprintf("Operating system:  %s %s (%s)\n", name.sysname,
			name.release, name.machine);
#endif	
	}
	/* read and calculate the amount of uptime and format it nicely */
	uptime(&uptime_secs, &idle_secs);
	updays = (int) uptime_secs / (60*60*24);
	upminutes = (int) uptime_secs / 60;
	uphours = upminutes / 60;
	uphours = uphours % 24;
	upminutes = upminutes % 60;
	tprintf("Uptime:            ");
  	if (updays)
		tprintf("%d day%s, ", updays, (updays != 1) ? "s" : "");
	if(uphours)
		tprintf("%d hour%s ", uphours, (uphours != 1) ? "s" : "");
	tprintf("%d minute%s\n", upminutes, (upminutes != 1) ? "s" : "");
	loadavg(&av[0], &av[1], &av[2]);
  	tprintf("Load average:      %.2f, %.2f, %.2f\n", av[0], av[1], av[2]);
	if (!(mem = meminfo()) || mem[meminfo_main][meminfo_total] == 0) {
		/* cannot normalize mem usage */
		tprintf("Cannot get memory information!\n");
	} else {
		tprintf("Memory:            %5d KB available, %5d KB used, %5d KB free\n",
			mem[meminfo_main][meminfo_total]    >> 10,
			(mem[meminfo_main][meminfo_used] -
		 	mem[meminfo_main][meminfo_buffers] -
			mem[meminfo_total][meminfo_cached]) >> 10,
			(mem[meminfo_main][meminfo_free] +
		 	mem[meminfo_main][meminfo_buffers] +
			mem[meminfo_total][meminfo_cached]) >> 10);
		tprintf("Swap:              %5d KB available, %5d KB used, %5d KB free\n",
			mem[meminfo_swap][meminfo_total]   >> 10,
			mem[meminfo_swap][meminfo_used]    >> 10,
			mem[meminfo_swap][meminfo_free]    >> 10);
	}
	tprintf("Users:             %d\n", user_count());
	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);
	tprintf("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);
	tprintf("Routes:            %d\n", n);
	return 0;
}
