#include <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <linux/ax25.h>
#include <linux/netrom.h>
#include "config.h"
#include "call.h"

static int backoff  = -1;
static int ax25mode = -1;
static int debug_setting  = FALSE;
static int netrom_setting = FALSE;
static int window_setting = 0;
static int port_setting   = 0;

int interrupted = FALSE;
int paclen      = 120;
int fd;

void convert_cr_lf(char *buf, int len)
{
	while (len--)
	{
		if (*buf == '\r')
			*buf = '\n';
		buf++;
	}
}

void convert_lf_cr(char *buf, int len)
{
	while (len--)
	{
		if (*buf == '\n')
			*buf = '\r';
		buf++;
	}
}

static int nr_convert_call(char *address, struct full_sockaddr_ax25 *addr)
{
	char buffer[100], *call, *alias;
	FILE *fp;
	int addrlen;

	for (call = address; *call != '\0'; call++)
		*call = toupper(*call);
	
	if ((fp = fopen("/proc/net/nr_nodes", "r")) == NULL) {
		printf("error: NET/ROM not included in the kernel\n");
		return -1;
	}

	while (fgets(buffer, 100, fp) != NULL) {
		call  = strtok(buffer, " \t\n\r");
		alias = strtok(NULL, " \t\n\r");
		
		if (strcmp(address, call) == 0 || strcmp(address, alias) == 0) {
			addrlen = convert_call(call, addr);
			fclose(fp);
			return addrlen;
		}
	}

	fclose(fp);

	printf("error: NET/ROM callsign or alias not found\n");

	return -1;
}

static int connect_to(char *address[])
{
	int fd;
  	int addrlen;
  	struct full_sockaddr_ax25 addr;
  	int one = debug_setting;

	if (netrom_setting) {
	  	if ((fd = socket(AF_NETROM, SOCK_SEQPACKET, 0)) < 0) {
	  		perror("socket");
			return(-1);
		}

	  	addrlen = convert_call(nr_config_get_addr(port_setting), &addr);
	} else {
	  	if ((fd = socket(AF_AX25, SOCK_SEQPACKET, 0)) < 0) {
	  		perror("socket");
			return(-1);
		}

	  	addrlen = convert_call(ax25_config_get_addr(port_setting), &addr);

	  	if (window_setting == 0)
	  		window_setting = ax25_config_get_window(port_setting);
  		
	  	if (setsockopt(fd, SOL_AX25, AX25_WINDOW, &window_setting, sizeof(window_setting)) == -1) {
	 		perror("AX25_WINDOW");
	  		close(fd);
	 		return(-1);
		}

		if (backoff != -1) {
		  	if (setsockopt(fd, SOL_AX25, AX25_BACKOFF, &backoff, sizeof(backoff)) == -1) {
		 		perror("AX25_BACKOFF");
		  		close(fd);
		 		return(-1);
			}
		}

		if (ax25mode != -1) {
		  	if (setsockopt(fd, SOL_AX25, AX25_EXTSEQ, &ax25mode, sizeof(ax25mode)) == -1) {
		 		perror("AX25_EXTSEQ");
		  		close(fd);
		 		return(-1);
			}
		}
	}

  	if (debug_setting && setsockopt(fd, SOL_SOCKET, SO_DEBUG, &one, sizeof(one)) == -1) {
  		perror("SO_DEBUG");
  		close(fd);
  		return(-1);
  	}

  	if (bind(fd, (struct sockaddr *)&addr, addrlen) == -1) {
  		perror("bind");
  		close(fd);
  		return(-1);
  	}

	if (netrom_setting) {
		if ((addrlen = nr_convert_call(address[0], &addr)) == -1) {
			close(fd);
			return(-1);
		}
	} else {
	  	if ((addrlen = convert_call_arglist(address, &addr)) == -1) {
	  		close(fd);
	  		return(-1);
	  	}
	}

	printf("Trying...\r");
	fflush(stdout);
  
  	if (connect(fd, (struct sockaddr *)&addr, addrlen)) {
  		printf("\n");
		perror("connect");
		close(fd);
		return(-1);
	}
  
  	printf("*** Connected to %s\n", address[0]);

  	return(fd);
}

void cmd_intr(int sig)
{
	signal(SIGQUIT, cmd_intr);
	interrupted = TRUE;
}

void cmd_call(char *call[])
{
	fd_set sock_read;
	fd_set sock_write;
	char buf[512];
	int bytes;
	int logfile    = -1;
	int uploadfile = -1;
 	long uplsize=0;
 	long uplpos=0;
 	char uplbuf[512];	/* Upload buffer */
 	int upldp=0;
 	int upllen=0;
 	char *c, *t;

	if ((fd = connect_to(call)) == -1)
		return;

	interrupted = FALSE;
	signal(SIGQUIT, cmd_intr);
	signal(SIGINT, SIG_IGN);
	signal(SIGTSTP, SIG_IGN);
	
	fcntl(fd, F_SETFL, O_NDELAY);
	fcntl(STDIN_FILENO, F_SETFL, O_NDELAY);
	
	while (TRUE)
	{
		FD_ZERO(&sock_read);
		FD_SET(STDIN_FILENO, &sock_read);
		FD_SET(fd, &sock_read);
		FD_ZERO(&sock_write);
		
		if (uploadfile != -1)
			FD_SET(fd, &sock_write);
		
		if (select(fd + 1, &sock_read, &sock_write, NULL, NULL) == -1)
		{
			if (!interrupted && errno == EAGAIN)
				continue;
			if (!interrupted)
				perror("select");
			break;
		}
		
		while ((bytes = read(fd, buf, 511)) > 0)
		{
			convert_cr_lf(buf, bytes);
			
			if (logfile != -1)
			{
				if (write(logfile, buf, bytes) != bytes)
				{
					close(logfile);
					logfile = -1;
					printf("\nError while writing log. Log closed.\n");
				}
			}

			write(STDOUT_FILENO, buf, bytes);
		}
		
		if (bytes == -1 && errno != EWOULDBLOCK && errno != EAGAIN)
		{
			if (errno != ENOTCONN)
				perror("read");
			break;
		}
		
		bytes = read(STDIN_FILENO, buf, (paclen > 255) ? 255 : paclen);
		
		if (bytes > 1 && *buf == '~')
		{
			buf[bytes] = 0;
			
			switch (buf[1])
			{
				case '.':
					bytes = 0;
					interrupted = TRUE;
					break;
				case '!':
					if (buf[2] != '\0' && buf[2] != '\n')
					{
						c = buf + 2;
						if ((t = strchr(c, '\n')) != NULL)
							*t = '\0';
					}
					else
					{
						if ((c = getenv("SHELL")) == NULL)
							c = "/bin/sh";
					}
					
					fcntl(STDIN_FILENO, F_SETFL, 0);
					printf("\n[Spawning subshell]\n");
					system(c);
					printf("\n[Returned to connect]\n");
					fcntl(STDIN_FILENO, F_SETFL, O_NDELAY);
					continue;
				case 'z':
				case 'Z':
				case 'Z' - 64:
					fcntl(STDIN_FILENO, F_SETFL, 0);
					kill(getpid(), SIGSTOP);
					printf("\n[Resumed]\n");
					fcntl(STDIN_FILENO, F_SETFL, O_NDELAY);
					continue;
				case '?':
				case 'h':
				case 'H':
					printf("\nTilde escapes:\n.  close\n!  shell\nZ  suspend\ns Stop upload\no  Open log\nc  Close log\nu  Upload\nyd  YAPP Download\nyu  YAPP Upload\n");
					continue;
				case 'S':
				case 's':
					if (uploadfile != -1)
					{
						printf("\n[Upload file closed]\n");
						close(uploadfile);
						uploadfile = -1;
					}
					else
					{
						printf("\n[No upload in progress]\n");
					}
					continue;
				case 'u':
				case 'U':
					if (uploadfile != -1)
					{
						printf("\n[Already uploading]\n");
						continue;
					}
					
					if ((t = strchr(buf, '\n')) != NULL)
						*t = '\0';
					t = buf + 2;
					while (*t != '\0' && isspace(*t))
						t++;
					if (*t == '\0')
					{
						printf("\n[Upload requires a filename - eg ~u hello.txt]\n");
						continue;
					}
					uploadfile = open(t, O_RDONLY);
					if (uploadfile == -1)
					{
						printf("\n[Unable to open upload file]\n");
						continue;
					}
					if (lseek(uploadfile, 0L, SEEK_END) !=-1)
						uplsize = lseek(uploadfile, 0L, SEEK_CUR);
					else
						uplsize = 0;
					lseek(uploadfile, 0L, SEEK_SET);
					uplpos  = 0;
					upldp   = -1;
					upllen  = 0;
					if (uplsize != -1)
						printf("\n[Uploading %ld bytes from %s]\n", uplsize, t);
					else
						printf("\n[Uploading from %s]\n", t);
					continue;
				case 'O':
				case 'o':
					if ((t=strchr(buf, '\n')) != NULL)
						*t = '\0';
					if (logfile != -1)
					{
						close(logfile);
						logfile = -1;
					}	
					t = buf + 2;
					while (*t != '\0' && isspace(*t))
						t++;
					if (*t == '\0')
						t = "logfile.txt";
					if ((logfile = open(t, O_RDWR | O_APPEND | O_CREAT, 0666)) == -1)
						printf("\n[Unable to open %s]\n", buf + 2);
					continue;
				case 'C':
				case 'c':
					if (logfile != -1)
					{
						close(logfile);
						logfile = -1;
					}
					else
					{
						printf("\n[Log file not open]\n");
					}
					continue;
				case 'Y':
				case 'y':
					cmd_yapp(buf + 2, bytes - 2);
					continue;
				case '~':
					bytes--;
					memmove(buf, buf + 1, strlen(buf));
					break;
				default:
					printf("\nUnknown '~' escape. Type ~h for a list.\n");
					continue;
			}
		}
					
		if (bytes == 0 || (bytes == -1 && errno != EWOULDBLOCK && errno != EAGAIN))
		{
			if (!interrupted)
				perror("input");
			break;
		}
		
		if (bytes > 0)
		{
			if (uploadfile != -1)
			{
				printf("\n[Ignored. Type ~s to stop upload]\n");
				continue;
			}

			convert_lf_cr(buf, bytes);
			
			if (write(fd, buf, bytes) == -1)
			{
				perror("write");
				break;	
			}
		}

		if (uploadfile != -1)
		{
			if (uplsize == 0)
			{
				close(uploadfile);
				uploadfile = -1;
				printf("\n[Upload complete: 0 bytes\n");
				continue;
			}
			
			if (upldp == -1)
			{
				upllen = read(uploadfile, uplbuf, (paclen > 511) ? 511 : paclen);
				
				if (upllen == 0)
				{
					close(uploadfile);
					uploadfile = -1;
					printf("\n[Upload complete: %ld bytes]\n", uplpos);
					continue;
				}
				
				if (upllen == -1)
				{
					close(uploadfile);
					uploadfile = -1;
					printf("\n[Error reading upload file: upload aborted at %ld bytes]\n", uplpos);
					continue;
				}

				convert_lf_cr(uplbuf, upllen);

				upldp = 0;
			}
			
			bytes = write(fd, uplbuf + upldp, upllen - upldp);

			if ((bytes == 0 || bytes == -1) && errno != EWOULDBLOCK && errno != EAGAIN)
			{
				printf("\n[Write error during upload. Connection lost]\n");
				perror("write");
				break;
			}
			
/*			if (uplpos / 1024 != (uplpos + bytes) / 1024)
			{ */
				printf("\r%ld bytes sent    ", uplpos + bytes);
/*			} */

			uplpos += bytes;
			upldp  += bytes;

			if (upldp >= upllen)
				upldp = -1;
		}	
	}

	close(fd);

	if (logfile != -1)
	{
		close(logfile);
		logfile = -1;
	}
	
	fcntl(STDIN_FILENO, F_SETFL, 0);
	
	signal(SIGQUIT, SIG_IGN);
	signal(SIGINT, SIG_DFL);
	
	printf("*** Cleared\n");
}
	
	
int main(int argc, char **argv)
{
	int p;

	while ((p = getopt(argc, argv, "b:dm:np:w:")) != -1) {
		switch (p) {
			case 'b':
				if (*optarg != 'e' && *optarg != 'l') {
					fprintf(stderr, "call: invalid argument for option '-b'\n");
					return 1;
				}
				backoff = *optarg == 'e';
				break;
			case 'd':
				debug_setting = TRUE;
				break;
			case 'm':
				if (*optarg != 's' && *optarg != 'e') {
					fprintf(stderr, "call: invalid argument for option '-m'\n");
					return 1;
				}
				ax25mode = *optarg == 'e';
				break;
			case 'n':
				netrom_setting = TRUE;
				break;
			case 'p':
				if ((port_setting = atoi(optarg)) == 0) {
					fprintf(stderr, "call: option '-p' requires numeric argument\n");
					return 1;
				}
				port_setting--;
				break;
			case 'w':
				if ((window_setting = atoi(optarg)) == 0) {
					fprintf(stderr, "call: option '-w' requires numeric argument\n");
					return 1;
				}
				if (ax25mode) {
					if (window_setting < 1 || window_setting > 63) {
						fprintf(stderr, "call: window must be between 1 and 63 frames\n");
						return 1;
					}
				} else {
					if (window_setting < 1 || window_setting > 7) {
						fprintf(stderr, "call: window must be between 1 and 7 frames\n");
						return 1;
					}
				}
				break;
			case '?':
			case ':':
				fprintf(stderr, "usage: call [-b l|e] [-d] [-m s|e] [-n] [-p port] [-w window] callsign [[via] digpieaters...]\n");
				return 1;
		}
	}

	if (optind == argc) {
		fprintf(stderr, "usage: call [-b l|e] [-d] [-m s|e] [-n] [-p port] [-w window] callsign [[via] digpieaters...]\n");
		return 1;
	}
	
	if (netrom_setting) {
		if (nr_config_load_ports() == -1) {
			fprintf(stderr, "call: no NET/ROM port data configured\n");
			return(1);
		}

		if (port_setting < 0 || port_setting >= nr_config_num_ports()) {
			fprintf(stderr, "call: invalid port setting\n");
			return(1);
		}
		
		paclen = nr_config_get_paclen(port_setting);
	} else {
		if (ax25_config_load_ports() == -1) {
			fprintf(stderr, "call: no AX.25 port data configured\n");
			return(1);
		}

		if (port_setting < 0 || port_setting >= ax25_config_num_ports()) {
			fprintf(stderr, "call: invalid port setting\n");
			return(1);
		}

		paclen = ax25_config_get_paclen(port_setting);
	}

	printf("GW4PTS AX.25 Connect v1.10\n");
	
	cmd_call(argv + optind);

	return(0);
}
