#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "ax25.h"
#include "axutils.h"
#include "axconfig.h"
#include "procinfo.h"
#include "daemon.h"

#define DEFAULT_POLL_TIME 600
#define MINIMUM_POLL_TIME 300
#define FLEXD_CONF_FILE "/etc/ax25/flexd.conf"
#define FLEXD_TEMP_PATH "/var/ax25/flex/"

int poll_time=DEFAULT_POLL_TIME;
char flexgate[10]="\0";
char mycall[10]="\0";
struct ax_routes *gw;
int s;

void read_conf(void)
{
  FILE *fp, *fgt;
  char buf[1024], line[1024], *cp;
  int i=0,k;
  char digipath[AX25_MAX_DIGIS*10];

  if ((fp=fopen(FLEXD_CONF_FILE, "r")) == NULL) {
    fprintf(stderr, "flexd config: Cannot open config file: %s\n", FLEXD_CONF_FILE);
    exit(1);
  }  
  if ((fgt=fopen(FLEX_GT_FILE, "w")) == NULL) {
    fprintf(stderr, "flexd config: Cannot open flexnet gateways file: %s\n", FLEX_GT_FILE);
    fclose(fp);
    exit(1);
  }

  fputs("addr  callsign  dev  digipeaters\n", fgt);
  
  while(fgets(buf, sizeof(buf), fp)) {
    if(*buf=='#' || *buf==' ') continue; /* comment line/empty line */
    cp=strchr(buf, '#');
    if (cp) *cp='\0';
    cp=strtok(buf, " \t\n\r");
    if(cp==NULL) continue; /* empty line */

    if(strcasecmp(cp,"pollinterval")==0) { /* set poll interval */
      cp=strtok(NULL, " \t\n\r");
      if(cp==NULL) {
	fprintf(stderr, "flexd config: PollInterval needs an argument\n");
	fclose(fp);
	fclose(fgt);
	exit(1);        
      }
      poll_time=safe_atoi(cp);
      if (poll_time<MINIMUM_POLL_TIME) poll_time=MINIMUM_POLL_TIME;
    }
    if(strcasecmp(cp,"mycall")==0) { /* set connect call for download */
      cp=strtok(NULL, " \t\n\r");
      if(cp==NULL) {
	fprintf(stderr, "flexd config: MyCall needs an argument\n");
	fclose(fp);
	fclose(fgt);
	exit(1);        
      }
      safe_strncpy(mycall, cp, 9);
    }
    if(strcasecmp(cp,"flexgate")==0) { /* set flexnet gateway */
      cp=strtok(NULL, " \t\n\r");
      if(cp==NULL) {
	fprintf(stderr, "flexd config: FlexGate needs an argument\n");
	fclose(fp);
	fclose(fgt);
	exit(1);        
      }      
      safe_strncpy(flexgate, cp, 9);
      gw=find_route(flexgate, NULL);
      if (gw==NULL) {
	fprintf(stderr, "flexd config: FlexGate %s not found in route file: %s\n", flexgate, AX_ROUTES_FILE);
	fclose(fp);
	fclose(fgt);
	exit(1);
      } else {
	*digipath='\0';
	for(k=0;k<AX25_MAX_DIGIS;k++) {
	  if (gw->digis[k]==NULL) break;
	  strcat(digipath," ");
	  strcat(digipath, gw->digis[k]);
	}
	sprintf(line, "%05d %-8s %4s %s\n", i++, gw->dest_call, ax25_config_get_dev(gw->dev), digipath);
	fputs(line, fgt);
      }
    }
  }	 

  fclose(fgt);
  fclose(fp);
}

int download_dest(char *gateway, char *fname)
{
  FILE *tmp;
  char buffer[1024], port[14], path[AX25_MAX_DIGIS*10];
  char *addr, *commands[10], *dlist[9]; /* Destination + 8 digipeaters */
  fd_set read_fd;
  int n, addrlen, cmd_send=0, cmd_ack=0, c, k;
  struct full_sockaddr_ax25 axbind, axconnect;

  gw=find_route(gateway, NULL);
  if (gw==NULL) {
    fprintf(stderr, "flexd connect: FlexGate %s not found in route file: %s\n", gateway, AX_ROUTES_FILE);
    return 1;
  } else {
    *path='\0';
    for(k=0;k<AX25_MAX_DIGIS;k++) {
      if (gw->digis[k][0]=='\0') dlist[k+1]=NULL;
      else dlist[k+1]=gw->digis[k];
    }
    dlist[0]=gw->dest_call;
    strcpy(port,gw->dev);
  }

  if ((addr = ax25_config_get_addr(port)) == NULL) {
    sprintf(buffer, "flexd connect: invalid AX.25 port name - %s\r\n", port);
    write(STDOUT_FILENO, buffer, strlen(buffer));
    return 1;
  }
  
  /*
   * Open the socket into the kernel.
   */
  if ((s = socket(AF_AX25, SOCK_SEQPACKET, 0)) < 0) {
    sprintf(buffer, "flexd connect: cannot open AX.25 socket, %s\r\n", strerror(errno));
    write(STDOUT_FILENO, buffer, strlen(buffer));
    return 1;
  }
  
  /*
   * Set our AX.25 callsign and AX.25 port callsign accordingly.
   */
  if (*mycall=='\0') sprintf(buffer, "%s %s", addr, addr);
  else sprintf(buffer, "%s %s", mycall, addr);
  convert_call(buffer, &axbind);
  axbind.fsa_ax25.sax25_family = AF_AX25;
  addrlen=sizeof(struct full_sockaddr_ax25);
  
  if (bind(s, (struct sockaddr *)&axbind, addrlen) != 0) {
    sprintf(buffer, "flexd connect: cannot bind AX.25 socket, %s\r\n", strerror(errno));
    write(STDOUT_FILENO, buffer, strlen(buffer));
    close(s);
    return 1;
  }
  
  /*
   * Lets try and connect to the far end.
   */
  addrlen=sizeof(struct full_sockaddr_ax25);
  axconnect.fsa_ax25.sax25_family = AF_AX25;
  
  if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
    sprintf(buffer, "flexd connect: fcntl on socket: %s\r\n", strerror(errno));
    write(STDOUT_FILENO, buffer, strlen(buffer));
    close(s);
    return 1;
  }

  if (convert_call_arglist(dlist, &axconnect) == -1) {
    sprintf(buffer, "flexd connect: invalid destination callsign or digipeater\r\n");
    write(STDOUT_FILENO, buffer, strlen(buffer));
    close(s);
    return 1;
  }
  
  if (connect(s, (struct sockaddr *)&axconnect, addrlen) == -1 && errno != EINPROGRESS) {
    switch (errno) {
    case ECONNREFUSED:
      strcpy(buffer, "*** Connection refused - aborting\r\n");
      break;
    case ENETUNREACH:
      strcpy(buffer, "*** No known route - aborting\r\n");
      break;
    case EINTR:
      strcpy(buffer, "*** Connection timed out - aborting\r\n");
      break;
    default:
      sprintf(buffer, "*** Cannot connect, %s\r\n", strerror(errno));
      break;
    }
    
    write(STDOUT_FILENO, buffer, strlen(buffer));
    close(s);
    return 1;
  }
  
  while (1) {
    FD_ZERO(&read_fd);
    FD_SET(s, &read_fd);
    if (select(s + 1, &read_fd, NULL, 0, 0) == -1) {
      break;
    }
    if (FD_ISSET(s, &read_fd)) {
      int ret, retlen;
      char *cp;
      
      /* See if we got connected or if this was an error */
      getsockopt(s, SOL_SOCKET, SO_ERROR, &ret, &retlen);
      if (ret != 0) {
	cp = strdup(strerror(ret));
	strlwr(cp);
	sprintf(buffer, "flexd connect: Failure with %s: %sr\r\n", gateway, cp);
	write(STDOUT_FILENO, buffer, strlen(buffer));
	free(cp);
	close(s);
	return 1;
      }
      break;
    }
  }
  
  commands[0]="d\n";
  commands[1]="q\n";
  commands[2]=NULL;
  
  /*
   * Loop until one end of the connection goes away.
   */

  if ((tmp=fopen(fname, "w")) == NULL) {
    fprintf(stderr, "flexd connect: Cannot open temporary file: %s\n", fname);
    close(s);
    return 1;
  }
  
  for (;;) {
    FD_ZERO(&read_fd);
    FD_SET(s, &read_fd);
    
    if (select(s + 1, &read_fd, NULL, NULL, NULL)==-1) {
      break;
    }
    
    if (FD_ISSET(s, &read_fd)) {
      if ((n = read(s, buffer, 512)) == -1) break;
      for(c=0;c<n;c++) { 
	if (buffer[c]=='\r') buffer[c]='\n';
	if (buffer[c]=='=' && c<n-1 && buffer[c+1]=='>') {
	  cmd_ack++;
	}
      }
      fwrite(buffer, sizeof(char), n, tmp);
    }
 
    if (cmd_ack!=0) {
      if (commands[cmd_send]!=NULL) {
	write(s, commands[cmd_send], 3);
	cmd_send++;
      }
      cmd_ack=0;
    }
  }
  
  close(s);

  fclose(tmp);
  return 0;
}

int parse_dest(char *gateway, char *fname)
{
  FILE *fdst, *tmp;
  char *call, *ssid, *rtt, *cp, buf[1024], line[1024];
  int i=0;

  if ((tmp=fopen(fname, "r")) == NULL) {
    fprintf(stderr, "flexd update: Cannot open temporary file: %s\n", fname);
    return 1;
  }
  
  if ((fdst=fopen(FLEX_DST_FILE, "w")) == NULL) {
    fprintf(stderr, "flexd update: Cannot open flexnet destinations file: %s\n", FLEX_DST_FILE);
    fclose(tmp);
    return 1;
  }
  
  fputs("callsign  ssid     rtt  gateway\n", fdst);

  while(fgets(buf, sizeof(buf), tmp)) {
    cp=strtok(buf, " \t\n\r");
    if(cp==NULL || i++<2) continue; /* empty line/connect text */
    if(*cp=='#' || *cp=='=' || *cp==' ' || *cp=='*' || *cp=='-') continue; /* comment line/system prompt */
    if(strncmp(cp,"73!",3)==0) continue; /* End greeting */

    /* CALL SSID-ESID RTT */
    do {
      call=cp;
      if (call==NULL) break;
      ssid=strtok(NULL, " \t\n\r");
      if (ssid==NULL) break;
      rtt=strtok(NULL, " \t\n\r");
      if (rtt==NULL) break;
      sprintf(line, "%-8s  %-5s %6d    %05d\n", call, ssid, safe_atoi(rtt), 0);
      fputs(line, fdst);
      cp=strtok(NULL, " \t\n\r");
    } while(cp!=NULL);
  }	 

  fclose(fdst);
  fclose(tmp);

  return 0;
}

int update_flex(void)
{
  char fname[80];

  sprintf(fname, "%s/.session.%s", FLEXD_TEMP_PATH, flexgate);

  if (download_dest(flexgate, fname)==0) parse_dest(flexgate, fname);
  remove(fname);

  return 0;
}

void hup_handler(int sig)
{
  signal(SIGHUP, SIG_IGN);
  
  read_conf();
  
  signal(SIGHUP, hup_handler); /* Restore hangup handler */
}

void alarm_handler(int sig)
{
  signal(SIGALRM, SIG_IGN);

  update_flex();

  signal(SIGALRM, alarm_handler); /* Restore alarm handler */
  alarm(poll_time); 
}

int main(int argc, char *argv[])
{       
  signal(SIGPIPE, SIG_IGN);

  if (ax25_config_load_ports() == 0) {
    fprintf(stderr, "flexd error: No AX25 port data configured\n");
    return 1;
  }

  read_conf();

  if (!daemon_start(TRUE)) {
    fprintf(stderr, "flexd: cannot become a daemon\n");
    return 1;
  }

  update_flex();
  
  signal(SIGHUP, hup_handler);
  signal(SIGALRM, alarm_handler);
  alarm(poll_time); 
  
  for(;;) pause();

  return 0;
}

