#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 <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#include "ax25.h"
#include "rose.h"
#include "config.h"
#include "node.h"
#include "io.h"
#include "axutils.h"
#include "axconfig.h"
#include "nrconfig.h"
#include "procutils.h"
#include "version.h"
#include "mheard.h"
#include "sysinfo.h"
#include "procinfo.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, "Dest",     1, do_dest);
  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",   2, 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, "MSessions", 2, do_sessions);
  add_internal_cmd(&Nodecmds, "Send", 	  1, SendMessage);
  add_internal_cmd(&Nodecmds, "!",   	  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 node_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)
{
  node_logout("Bye");
  return 0;	/* Keep gcc happy */
}

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

  tputs("\n");
  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;
}

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

  tputs("\n");
  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);
    tprintf("%s - %s\n",VERSION,COMPILING);
  } else if (argc == 1) {				/* "help"	*/
    strcat(fname, "help.hlp");
  } else {					/* "help <cmd>"	*/
    if (strchr(argv[1], '/') == NULL) {
      strlwr(argv[1]);
      if ((strcmp(argv[1],"!"))==0)  strcat(fname,"status");
      else 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");
  fclose(fp);
  return 0;
}

int do_host(int argc, char **argv)
{
  struct hostent *h;
  struct in_addr addr;
  char **p, *cp;
  
  tputs("\n");
  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)
{
  struct proc_ax25 *ax, *ax_list;
  struct proc_dev *dev, *dev_list;
  char *cp = NULL;
  int n, tx, rx;
  
  ax_list=read_proc_ax25();
  dev_list=read_proc_dev();
  
  tputs("\n");
  node_msg("Ports:");
  tprintf("Port    Description                                 QSO  RX packets  TX packets\n");
  tprintf("------- ------------------------------------------ ---- ----------- -----------\n");
  while ((cp = ax25_config_get_next(cp)) != NULL) {
    n=0;
    if (ax_list) for (ax=ax_list;ax!=NULL;ax=ax->next) {
      if (strcmp(ax25_config_get_name(ax->dev), cp)==0 && strcmp(ax->dest_addr, "*")!=0) n++;
    }
    tx=0; rx=0;
    if (dev_list) for (dev=dev_list;dev!=NULL;dev=dev->next) {
      if (strcmp(dev->interface, ax25_config_get_dev(cp))==0) {
	tx=dev->tx_packets;
	rx=dev->rx_packets;
      }
    }
    if (is_hidden(cp) && check_perms(PERM_HIDDEN, 0L) == -1)
			continue;
    tprintf("%-7s %-42s %4d %11d %11d\n", cp, ax25_config_get_desc(cp), n, rx, tx);
  }

  free_proc_ax25(ax_list);
  free_proc_dev(dev_list);

  return 0;
}

int do_sessions(int argc, char **argv)
{
  struct proc_ax25 *ax_p, *ax_list;
  struct proc_nr *nr_p, *nr_list;
  char *cp;

  tputs("\n");
  if ((ax_list = read_proc_ax25()) == NULL) {
    if (errno) node_perror("sessions: read_proc_ax25:", errno);
    else tprintf ("No such AX25 sessions actives.\n");
    
  } else {
    node_msg("AX25 Sessions:");
    tprintf("Port    Dest addr Src addr  State        Unack T1      Retr   Rtt Snd-Q Rcv-Q\n");
    tprintf("------- --------- --------- ------------ ----- ------- ------ --- ----- -----\n");
    for (ax_p = ax_list; ax_p != NULL; ax_p = ax_p->next) {
      if (argc > 1 && strcasecmp(argv[1], "*") && strcasecmp(ax_p->dest_addr, argv[1]) && 
	  strcasecmp(ax_p->src_addr, argv[1])) continue;
      if ((argc < 2) && !strcmp(ax_p->dest_addr, "*"))
	continue;
      cp = ax25_config_get_name(ax_p->dev);
      tprintf("%-7s %-9s %-9s ", cp, ax_p->dest_addr, ax_p->src_addr);
      if (!strcmp(ax_p->dest_addr, "*")) {
	tprintf("Listening\n");
	continue;
      }
      switch (ax_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/%03d %3d %5ld %5ld\n",
	     cp,
	     ax_p->vs < ax_p->va ? ax_p->vs - ax_p->va + 8 : ax_p->vs - ax_p->va,
	     ax_p->window,
	     ax_p->t1timer, ax_p->t1,
	     ax_p->n2count, ax_p->n2,
	     ax_p->rtt,
	     ax_p->sndq, ax_p->rcvq);
    }
    free_proc_ax25(ax_list);
    tputs("\n");
  }

  if ((nr_list = read_proc_nr()) == NULL) {
    if (errno) node_perror("sessions: read_proc_nr", errno);
    else tprintf ("No such NET/ROM sessions actives.\n");
  } else {
    node_msg("NET/ROM Sessions:");
    tprintf("User addr Dest node Src node  State        Unack T1      Retr   Rtt Snd-Q Rcv-Q\n");
    tprintf("--------- --------- --------- ------------ ----- ------- ------ --- ----- -----\n");
    for (nr_p = nr_list; nr_p != NULL; nr_p = nr_p->next) {
      if (argc > 1 && strcasecmp(argv[1], "*") && strcasecmp(nr_p->user_addr, argv[1]) && 
	  strcasecmp(nr_p->dest_node, argv[1]) && strcasecmp(nr_p->src_node, argv[1])) continue;
      if ((argc < 2) && !strcmp(nr_p->user_addr, "*")) continue;
      cp = nr_config_get_name(nr_p->dev);
      tprintf("%-9s %-9s %-9s ", nr_p->user_addr, nr_p->dest_node, nr_p->src_node);
      if (!strcmp(nr_p->user_addr, "*")) {
	tprintf("Listening\n");
	continue;
      }
      switch (nr_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/%03d %3d %5ld %5ld\n",
	     cp,
	     nr_p->vs < nr_p->va ? nr_p->vs - nr_p->va + 8 : nr_p->vs - nr_p->va,
	     nr_p->window,
	     nr_p->t1timer, nr_p->t1,
	     nr_p->n2count, nr_p->n2,
	     nr_p->rtt,
	     nr_p->sndq, nr_p->rcvq);
    }
    free_proc_nr(nr_list);
  }

  return 0;
}

int do_routes(int argc, char **argv)
{
  struct proc_nr *nr, *nr_list;
  struct proc_nr_neigh *nrh, *nrh_list;
  struct proc_nr_nodes *nrn, *nrn_list;
  struct proc_ax25 *ap;
  char *cp, portcall[10];
  int link, n;

  tputs("\n");
  nr_list=read_proc_nr();
  nrn_list=read_proc_nr_nodes();
  if ((nrh_list = read_proc_nr_neigh()) == NULL) {
    if (errno) node_perror("do_routes: read_proc_nr_neigh", errno);
    else tprintf ("No such routes.\n");
    return 0;
  }

  node_msg("Routes:");
  tprintf("Link Port    Callsign  Quality Destinations Lock  QSO\n");
  tprintf("---- ------- --------- ------- ------------ ---- ----\n");
  strcpy(portcall,nr_config_get_addr(nr_config_get_next(NULL)));
  if (strchr(portcall, '-')==NULL) strcat(portcall, "-0");
  for (nrh = nrh_list; nrh != NULL; nrh = nrh->next) {
    link=0; n=0;
    if ((ap = find_link(portcall, nrh->call, nrh->dev)) != NULL && ap->st >= 3) link = 1;
    if ((ap = find_link(nrh->call, portcall, nrh->dev)) != NULL && ap->st >= 3) link = 2;
    cp = ax25_config_get_name(nrh->dev);

    if (nr_list) for (nr=nr_list;nr!=NULL;nr=nr->next) {
      if (strcmp(nr->dest_node, nrh->call)==0) {
	n++;
      } else {
	if (nrn_list) for(nrn=nrn_list;nrn!=NULL;nrn=nrn->next) {
	  if (strcmp(nrn->call, nr->dest_node)==0) {
	    switch(nrn->w) {
	    case 1: if (nrn->addr1==nrh->addr) n++; break;
	    case 2: if (nrn->addr2==nrh->addr) n++; break;
	    case 3: if (nrn->addr3==nrh->addr) n++; break;
	    }
	  }
	}
      }
    }
    
    tprintf("%c    %-7s %-9s %7d %12d    %c %4d\n",
	   link == 0 ? ' ' : '>',
	   cp,
	   nrh->call,
	   nrh->qual,
	   nrh->cnt,
	   nrh->lock == 1 ? '!' : ' ',
	   n);
  }

  free_proc_nr_neigh(nrh_list);
  free_proc_nr_nodes(nrn_list);
  free_proc_nr(nr_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;

  tputs("\n");
  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("NET/ROM Nodes:");
    for (p = list; p != NULL; p = p->next) {
      tprintf("%-16.16s %c",print_node(p->call, p->alias),(++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("NET/ROM Nodes:");
    tprintf("Node              Quality Obsolescence Port    Neighbour\n");
    tprintf("----------------- ------- ------------ ------- ----------\n");
    for (p = list; p != NULL; p = p->next) {
      tprintf("%-16.16s  ", print_node(p->call, p->alias));
      if ((np = find_neigh(p->addr1, nlist)) != NULL) {
	tprintf("%7d %12d %-7s %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 %-7s %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 %-7s %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->call, p->alias));
    tprintf("Which Quality Obsolescence Port    Neighbour\n");
    tprintf("----- ------- ------------ ------- ----------\n");
    if ((np = find_neigh(p->addr1, nlist)) != NULL) {
      tprintf("%c     %7d %12d %-7s %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 %-7s %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 %-7s %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;
  struct flex_dst *fd, *fd_list;
  struct ax_routes *ar, *ar_list;
  struct proc_ax25 *ax, *ax_list;
  struct proc_nr *nr, *nr_list;
  int n, r, na, nn, nl, nd;
  int ma, mu, mf, sa, su, sf;
  
  tputs("\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);
    tprintf("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, &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]);
  tprintf("Users:             %d node, %d system\n", user_count(), system_user_count());

  if (!(mem = meminfo()) || mem[meminfo_main][meminfo_total] == 0) {
    /* cannot normalize mem usage */
    tprintf("Cannot get memory information!\n");
  } else {
    ma=mem[meminfo_main][meminfo_total] >> 10;
    mu=(mem[meminfo_main][meminfo_used]-mem[meminfo_main][meminfo_buffers]-
	mem[meminfo_total][meminfo_cached]) >> 10;
    mf=(mem[meminfo_main][meminfo_free]+mem[meminfo_main][meminfo_buffers]+
	mem[meminfo_total][meminfo_cached]) >> 10;

    tprintf("\n");
    tprintf("                   Available  Used       Free       perc. Used\n");
    tprintf("------------------ ---------- ---------- ---------- ----------\n");
    tprintf("Memory:            %-7d kB %-7d kB %-7d kB %3d %%\n",ma,mu,mf,(mu*100)/ma);

if (!(!(mem = meminfo()) || mem[meminfo_swap][meminfo_total] == 0)) {
    sa=mem[meminfo_swap][meminfo_total]   >> 10;
    su=mem[meminfo_swap][meminfo_used]    >> 10;
    sf=mem[meminfo_swap][meminfo_free]    >> 10;
    tprintf("Swap:              %-7d kB %-7d kB %-7d kB %3d %%\n",sa,su,sf,(su*100)/sa);   
    }
    else tprintf("Swap:		   No swap memory.\n"); 
    }
    if ((nolist = read_proc_nr_nodes()) == NULL && errno != 0)
    tprintf("status: read_proc_nr_nodes");
  n = 0;
  for (nop = nolist; nop != NULL; nop = nop->next)
    n++;
  free_proc_nr_nodes(nolist);
  if ((nelist = read_proc_nr_neigh()) == NULL && errno != 0)
    tprintf("status: read_proc_nr_neigh");
  r = 0;
  for (nep = nelist; nep != NULL; nep = nep->next)
    r++;
  free_proc_nr_neigh(nelist);
  na=0;
  ax_list=read_proc_ax25();
  if (ax_list) for (ax=ax_list;ax!=NULL;ax=ax->next) {
    if (strcmp(ax->dest_addr, "*")==0) continue;
    na++;
  }
  free_proc_ax25(ax_list);
  nn=0;
  nr_list=read_proc_nr();
  if (nr_list) for (nr=nr_list;nr!=NULL;nr=nr->next) {
    if (strcmp(nr->dest_node, "*")==0) continue;
    nn++;
  }
  free_proc_nr(nr_list);
  nl=0;
  ar_list=read_ax_routes();
  if (ar_list) for (ar=ar_list;ar!=NULL;ar=ar->next) {
    nl++;
  }
  free_ax_routes(ar_list);
  nd=0;
  fd_list=read_flex_dst();
  if (fd_list) for (fd=fd_list;fd!=NULL;fd=fd->next) {
    nd++;
  }
  free_flex_dst(fd_list);

  tprintf("\n");
  tprintf("                   Sessions   Dest/Nodes Links/Routes\n");
  tprintf("------------------ ---------- ---------- ------------\n");
  tprintf("AX25:              %-10d %-10d %-10d\n",na,nd,nl);
  tprintf("NET/ROM:           %-10d %-10d %-10d\n",nn,n,r);

  return 0;
}
