/* see LICENSE for license details */
#include <sys/ioctl.h>

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

#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>

#ifdef __linux__
 #include <asm/termbits.h>
 #include <asm/ioctls.h>
#else /* needed for termios2, which is itself needed for non-standard bauds */
 #include <termios.h>
#endif

#define CR '\r'
#define LF '\n'
#define BS '\b'
#define DEL '\x7f'
#define EOT '\x4'
#define ESC '\x1b'

#define IXONXOFF (IXON | IXOFF)

#if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW)
 #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW)
#endif

#ifdef __linux__
 #define TERMIOS_STRUCT termios2
#else
 #define TERMIOS_STRUCT termios
#endif

typedef struct {
	char find;
	char input;
	int offset; /* can only be a 1 or a -1 */
} Args; 

typedef struct {
	ssize_t sizebm;
	ssize_t sizesm;
} Sizes;

typedef struct {
	int crinlf;
	int crtolf;
	int lfincr;
	int lftocr;
	int nocr;
	int nolf;
} CRLFOpt;

/* including here to access CRLFOpt */
#include "config.h"

static int gettermattr(int dv, struct TERMIOS_STRUCT *optst);
static int settermattr(int dv, const struct TERMIOS_STRUCT *optst);
static void settermspd(unsigned int lispeed, unsigned int lospeed, struct TERMIOS_STRUCT *optst);
static int openport(void);
static unsigned int strtoui(const char *str, const char *msg, unsigned int min);
static int troptions(CRLFOpt *options, const char *str);
static int setroptions(void);
static inline unsigned int lsbmask(unsigned int n);
static inline int termchck(const struct TERMIOS_STRUCT *term);
static void interchck(void);
static void cechck(void);
static void *writeport(void *);
static void *readport(void *);
static void getcmd(int escape);
static inline void replacechar(char *buff, ssize_t size, const Args *args);
static inline ssize_t addchar(char *buff, ssize_t size, int *scratch, const Sizes *msizes, const Args *args);
static inline ssize_t rmchar(char *buff, ssize_t sread, int *scratch, const Args *args);
static void sighandl(int signo);
static void die(int code, const char *msg, ...);

static struct TERMIOS_STRUCT cntrl, /* device struct */
                             origterm = { 0 }, /* controlling terminal structs */
                             newterm = { 0 };

static Args bsargs = {DEL, DEL, 0};
static const Args nocrargs = {CR, 0, 0},
                  nolfargs = {LF, '\0', 0}, /* null-terminator is for use inside `getcmd()` */
                  lfincrargs = {CR, LF, 1},
                  crinlfargs = {LF, CR, 0};

static struct timespec wts;
static const unsigned int uintmax = ~(unsigned int)0; /* unsigned -1 used to return an error */
static int interactive = 0;
static char *writebuff = NULL;
static char *readbuff = NULL;
static int *scratchr = NULL;
static int *scratchw = NULL;
static int fd = -1;
static long rwc = 0;

int
gettermattr(int dv, struct TERMIOS_STRUCT *optst)
{
	 #ifdef __linux__
	  return ioctl(dv, TCGETS2, optst);
	 #else
	  return tcgetattr(dv, optst);
	 #endif
}

int
settermattr(int dv, const struct TERMIOS_STRUCT *optst)
{
	#ifdef __linux__
	 return ioctl(dv, TCSETS2, optst);
	#else
	 return tcsetattr(dv, TCSANOW, optst);
	#endif
}

void
settermspd(unsigned int lispeed, unsigned int lospeed, struct TERMIOS_STRUCT *optst)
{
	#ifdef __linux__
	 optst->c_cflag &= ~CBAUD;
	 optst->c_cflag |= BOTHER;
	 optst->c_ispeed = ispeed;
	 optst->c_ospeed = ospeed;
	#else
	 cfsetispeed(optst, ispeed);
	 cfsetospeed(optst, ospeed);
	#endif
}

int
openport(void)
{
	int flags;
	struct flock fl = { 0 };

	if (verbose) fprintf(stderr, "opening \"%s\"\n", line);

	fd = open(line, (O_RDWR | O_NOCTTY | O_NDELAY));
	if (fd == -1) {
		perror("error opening device");
		exit(1);
	}

	if (verbose) fprintf(stderr, "checking if \"%s\" is a TTY\n", line);

	if (!isatty(fd))
		die(2, "device \"%s\" is not a TTY\n", line);

	if (blocking) {
		flags = fcntl(fd, F_GETFL, 0);
		/* opened to check with non-blocking mode, now set to blocking */
		flags &= ~(O_NONBLOCK | O_NDELAY); 

		if (fcntl(fd, F_SETFL, flags) == -1) {
			perror("fcntl");
			die(1, "exiting now\n");
		}
	}

	if (gettermattr(fd, &cntrl) == -1)
		 die(1, "failed to get device attributes\n");

	if (exclusive) {
		fl.l_type = F_WRLCK;
		fl.l_whence = SEEK_SET;
		if (fcntl(fd, F_SETLK, &fl) == -1) {
			perror("fcntl");
			die(1, "failed to lock the device, exiting now\n");
		}
	}

	/*
	 * Baud
	 */
	if (verbose) fprintf(stderr, "setting baudrate [RX:%u | TX:%u]\n", ispeed, ospeed);

	settermspd(ispeed, ospeed, &cntrl);
	if (settermattr(fd, &cntrl) == -1)
	  	 die(2,"failed to set baudrate [RX:%u | TX:%u]\n",  ispeed, ospeed);
	
	/*
	 * Parity
	 */
	cntrl.c_lflag = 0;
	cntrl.c_iflag &= ~(ISTRIP | BRKINT);
	cntrl.c_cflag &= ~(PARENB | PARODD);

	if (verbose) fprintf(stderr, "setting parity [even: %d, odd: %d]\n",\
		       	parity & 1, (parity & 2) >> 1);

	if (parity == 1)
		cntrl.c_cflag |= PARENB;
	if (parity == 2)
		cntrl.c_cflag |= PARENB | PARODD;

	if (settermattr(fd, &cntrl) == -1)
		die(2, "failed to set parity [even: %d, odd: %d]\n",\
			       	parity & 1, (parity & 2) >> 1);

	/*
	 * Flow control
	 */
	if (verbose) {
		/* it's so ugly and beautiful at the same time */
		int t = (((hard & 1) << 1) & (hard & 2)) >> 1;
		fprintf(stderr, "setting flow control [XON/XOFF: %d, RTS/CTS: %d, DTR/DSR: %d, DCD: %d]\n",\
			soft, t ^ ((hard & 1) << 1) >> 1, t ^ (hard & 2) >> 1, t);
	}

	cntrl.c_cflag |= CLOCAL;
	cntrl.c_iflag &= ~IXONXOFF;

	if (soft)
		cntrl.c_iflag |= IXONXOFF;

	if (hard == 1) {
		cntrl.c_cflag |= CRTSCTS;
	} else if (hard == 2) {
		#ifndef CDTRDSR
		 fprintf(stderr, "DTR/DSR flow control is not supported on this platform\n"
				 "enabling this option does nothing!\n");
		#else
		 cntrl.c_lflag |= CDTRDSR;
		#endif
	} else if (hard == 3) {
		cntrl.c_cflag &= ~CLOCAL;
		#ifdef CCAR_OFLOW
		 cntrl.c_lflag |= CCAR_OFLOW;
		#endif
	}

	if (settermattr(fd, &cntrl) == -1)
		die(2, "failed to set flow control\n");

	/*
	 * CR and LF
	 */
	cntrl.c_iflag &= ~(ICRNL | INLCR);
	cntrl.c_oflag &= ~(OCRNL | ONLRET | ONLCR);

	if (setroptions())
		die(2, "failed to set cr-lf translation options\n");

	/*
	 * Data bits
	 */
	if (verbose) fprintf(stderr, "setting data bits [%c]\n", datab);

	tcflag_t db = CS8;
	switch (datab) {
		case '5':
			db = CS5; break;
		case '6':
			db = CS6; break;
		case '7':
			db = CS7; break;
		default:
			break;
	}
	cntrl.c_cflag &= ~CSIZE;
	cntrl.c_cflag |= db;

	if (settermattr(fd, &cntrl) == -1)
		die(2, "failed to set data bits [%c]\n", datab);
	
	/*
	 * Stop bits
	 */
	if (verbose) fprintf(stderr, "setting stop bits [%d]\n", stopb+1);

	if (stopb)
		cntrl.c_cflag |= CSTOPB;
	else
		cntrl.c_cflag &= ~CSTOPB;

	if (settermattr(fd, &cntrl) == -1)
		 die(1, "failed to set stop bits [%d]\n", stopb+1);

	cntrl.c_cc[VMIN] = 1;
	cntrl.c_cc[VTIME] = 0;
	
	/* flush both hardware buffers */
	#ifdef __linux__
	 ioctl(fd, TCFLSH, TCIOFLUSH);
	#else
	 tcflush(fd, TCIOFLUSH);
	#endif
	return(fd);
}

unsigned int
strtoui(const char *str, const char *msg, unsigned int min)
{	
	long t;
	char *endptr;

	t = strtol(str, &endptr, 10);
	if (errno != 0 || *endptr != '\0' || t < min || t > uintmax) {
		fprintf(stderr, msg, str);
		return uintmax;
	}
	/* 
	 * conversion like this results in 'undefined behavior' according to C spec,
	 * but almost all compilers will just truncate the value so it's OK
	 */
	return (unsigned int)t;
}

int
troptions(CRLFOpt *options, const char *str)
{
	switch (str[0]) {
	case 'l':
		if (!strcmp(str, "lf-in-cr")) {
			options->lfincr ^= 1; return 0;
		} else if (!strcmp(str, "lf-to-cr")) {
			options->lftocr ^= 1; return 0;
		} else {
			goto trerr;
		}
		break;
	case 'c':
		if (!strcmp(str, "cr-in-lf")) {
			options->crinlf ^= 1; return 0;
		} else if (!strcmp(str, "cr-to-lf")) {
			options->crtolf ^= 1; return 0;
		} else {
			goto trerr;
		}
		break;
	case 'n':
		if (!strcmp(str, "no-lf")) {
			options->nolf ^= 1; return 0;
		} else if (!strcmp(str, "no-cr")) {
			options->nocr ^= 1; return 0;
		} else {
			goto trerr;
		}
		break;
	default:
	trerr:
		fprintf(stderr, "invalid translation option: %s\n", str);
		return 1;
	}
}

int
setroptions(void)
{
	cntrl.c_iflag ^= (ICRNL  & lsbmask(itropts.crtolf));
	cntrl.c_iflag ^= (INLCR  & lsbmask(itropts.lftocr));
	cntrl.c_oflag ^= (OCRNL  & lsbmask(tropts.crtolf));
	cntrl.c_oflag ^= (ONLRET & lsbmask(tropts.lftocr));
	cntrl.c_oflag ^= (ONLCR  & lsbmask(tropts.crinlf));

	return settermattr(fd, &cntrl);
}

inline unsigned int
lsbmask(unsigned int n)
{
	/* if n == 1, n fills with all 1s, 0 stays the same */ 
	for (int i = (sizeof(int) * CHAR_BIT); i > 0; i--)
		n |= (n << 1);
	return n;
}

inline int
termchck(const struct TERMIOS_STRUCT *term)
{
	for (size_t i = 0; i < sizeof(struct TERMIOS_STRUCT); i++)
	 	if (((char*)term)[i] != 0)
			return 1;

	return 0;
}

void *
writeport(void *)
{
	Sizes msizes;
	struct timespec ts;

	ts.tv_sec = swritedelay;
	ts.tv_nsec = nswritedelay;
	wts.tv_sec = 0;
	wts.tv_nsec = chardelay;
	msizes.sizesm = scratchwsz;
	msizes.sizebm = wbuffsz;

	writebuff = malloc(wbuffsz * sizeof(char));
	scratchw = malloc(scratchwsz * sizeof(int));
	
	if (writebuff == NULL)
		die(1, "buffer allocation failed\n");
	if (scratchw == NULL)
		die(1, "scratch buffer allocation failed\n");
	
	int escape = 0;

	for (;;) {
		ssize_t inln = read(STDIN_FILENO, writebuff, wbuffsz - 1);
		if (inln > 0) {	
			if (escape) {
				getcmd(escape);
				escape = 0;
			}
			if (writebuff[0] == escapechar) {
				if (inln > 1)
					getcmd(escape);
				else
					escape = 1; 
			}

			if (bsargs.find != bsargs.input)
				replacechar(writebuff, inln, &bsargs);
			if (tropts.nocr)
				inln = rmchar(writebuff, inln, scratchw, &nocrargs);
			if (tropts.nolf)
				inln = rmchar(writebuff, inln, scratchw, &nolfargs);
			if (tropts.lfincr)
				inln = addchar(writebuff, inln, scratchw, &msizes, &lfincrargs);
			
			if (inln > 1) {
				for (int i = 0; i <= inln; i++) {
					rwc -= write(fd, &writebuff[i], 1);
					nanosleep(&wts, NULL);
				}
			} else {
				rwc -= write(fd, writebuff, 1);
			}
		} else if (inln == 0 && !interactive) {
			break;
		}
		nanosleep(&ts, NULL);
	}

	die(0, "[EOF]\n");

	/* unreachable */
	return NULL;
}

void *
readport(void *unused)
{
	Sizes msizes;
	struct timespec ts;

	ts.tv_sec = sreaddelay;
	ts.tv_nsec = nsreaddelay;
	msizes.sizesm = scratchrsz;
	msizes.sizebm = rbuffsz;

	readbuff = malloc(rbuffsz * sizeof(char));
	scratchr = malloc(scratchrsz * sizeof(int));
	
	if (readbuff == NULL)
		die(1, "buffer allocation failed\n");
	if (scratchr == NULL)
		die(1, "scratch buffer allocation failed\n");
	
	interchck();

	/* disable stdout buffering */
	setvbuf(stdout, NULL, _IONBF, 0);

	for (;;) {
		ssize_t outln = read(fd, readbuff, rbuffsz - 1);
		if (outln > 0) {
			if (itropts.nocr)
				outln = rmchar(readbuff, outln, scratchr, &nocrargs);
			if (itropts.nolf)
				outln = rmchar(readbuff, outln, scratchr, &nolfargs);
			if (itropts.crinlf)
				outln = addchar(readbuff, outln, scratchr, &msizes, &crinlfargs);
			if (itropts.lfincr)
				outln = addchar(readbuff, outln, scratchr, &msizes, &lfincrargs);

			rwc += write(STDOUT_FILENO, readbuff, outln);
		}
		nanosleep(&ts, NULL);
	}

	/* unreachable */
	return NULL;
}

inline void
replacechar(char *buff, ssize_t size, const Args *args)
{
	for (int i = 0; i < size; i++)
		if (buff[i] == args->find)
			buff[i] = args->input;
}

inline ssize_t
addchar(char *buff, ssize_t size, int *scratch, const Sizes *sizes, const Args *args)
{
	int to, c;
	c = 0;
	/* turns negative numbers into 1, positive into 0 */
	to = ((args->offset >> ((sizeof(int) * CHAR_BIT) - 1)) & 1);

	for (int i = 0; i < size; i++) {
		if (buff[i] == args->find) {
			scratch[c] = i;
			c++;
		}
		if (c >= sizes->sizesm)
			break;
	}
	if (!c)
		return(size);
	if (size == 1 && c == 1)  {
		buff[to ^ 1] = args->input;
		buff[to] = args->find;
		return 2;
	}
	if ((size + c) > sizes->sizebm)
		c = sizes->sizebm - size;

	for (int i = c - 1; i >= 0; i--) {
		int t = scratch[i];
		memmove(&buff[t] + 1, &buff[t], (size + c) - t);
		buff[t + (to ^ 1)] = args->input;
	}
	printf("%s\n", buff);
	return (size + c);
}

inline ssize_t
rmchar(char *buff, ssize_t size, int *scratch, const Args *args)
{
	int c = 0;
	for (int i = 0; i < size; i++) {
		if (buff[i] == args->find) {
			scratch[c] = i;
			c++;
		}
	}
	if (!c)
		return(size);
	if (size == 1 && c == 1)
		return 0;

	for (int i = c; i >= 0; i--) {
		int t = scratch[i];
		memmove(&buff[t], &buff[t] + 1, (size + c) - t);
	}
	return(size - c);
}

void
interchck(void)
{
	if (interactive) {
		if (gettermattr(STDIN_FILENO, &origterm) < 0 )
			die(1, "failed to get terminal attributes\n");

		newterm = origterm;

		if (!canonical)
			newterm.c_lflag &= ~ECHO;
		if (!half)
			newterm.c_lflag &= ~ICANON;

		if (soft)
			newterm.c_iflag |= IXONXOFF;

		newterm.c_iflag |= INLCR;
		newterm.c_cc[VMIN] = minchars;
		newterm.c_cc[VTIME] = 0;
		newterm.c_cc[VINTR] = newterm.c_cc[VSUSP]\
			= newterm.c_cc[VQUIT] = newterm.c_cc[VLNEXT]\
		 	= newterm.c_cc[VDISCARD] = _POSIX_VDISABLE;
				
		#ifdef VDSUSP
		 newterm.c_cc[VDSUSP] = _POSIX_VDISABLE;
	 	#endif

		if (backspace)
			bsargs.input = BS;
		else
			bsargs.input = DEL;

		bsargs.find = origterm.c_cc[VERASE];
		
		if (settermattr(STDOUT_FILENO, &newterm) < 0)
			die(1, "failed to set terminal attributes\n");
	}
	return;
}

void
cechck(void)
{
	if ((!half || !canonical) && interactive) {
		newterm.c_lflag |= ECHO;
		newterm.c_lflag |= ICANON;
		if (settermattr(STDIN_FILENO, &newterm) < 0)
			fprintf(stderr, "failed to enable echo and/or canonical mode\n");
	}
	return;
}

void
getcmd(int escape)
{
	Sizes msizes;
	char cmdchar;
	char ttr[64];
	unsigned int tspd;

	msizes.sizesm = scratchwsz;
	msizes.sizebm = wbuffsz;

	if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
		interactive = 1;

	interchck();

	if (escape)
		cmdchar = writebuff[0];
	else
		cmdchar = writebuff[1];

	switch (cmdchar) {
	case EOT: /* FALLTHROUGH */
	case '.':
		die(0,"\n[EOT]\n");
	case 'b':
		if (backspace)
			bsargs.input = DEL;
		else
			bsargs.input = BS;
		backspace ^= 1;
		break;
	case 'h':
		if (half)
			newterm.c_lflag &= ~ECHO;
		else
			newterm.c_lflag |= ECHO;
		half ^= 1;
		if (settermattr(STDOUT_FILENO, &newterm) < 0)
			die(1, "failed to set terminal attributes\n");
		break;
	case 'c':
		if (canonical)
			newterm.c_lflag &= ~ICANON;
		else
			newterm.c_lflag |= ICANON;
		canonical ^= 1;
		if (settermattr(STDOUT_FILENO, &newterm) < 0)
			die(1, "failed to set terminal attributes\n");
		break;
	case 'w':
		cechck();
		if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
			ssize_t frln;
			int wfd = open(ttr, O_RDONLY);
			replacechar(ttr, 63, &nolfargs);
			if (wfd == -1) {
				perror("error opening file");
				goto finish;
			}
			while ((frln = read(wfd, writebuff, wbuffsz - 1)) > 0) {
				rwc += frln;
				for (int i = 0; i <= frln; i++) {
					if (tropts.nocr)
						frln = rmchar(writebuff, frln, scratchw, &nocrargs);
					if (tropts.nolf)
						frln = rmchar(writebuff, frln, scratchw, &nolfargs);
					if (tropts.lfincr)
						frln = addchar(writebuff, frln, scratchw, &msizes, &lfincrargs);
	
					rwc -= write(fd, &writebuff[i], 1);
					nanosleep(&wts, NULL);
				}
			}
		}
		goto finish;
	case 's':
		cechck();
		if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
			replacechar(ttr, 63, &nolfargs);
			tspd = strtoui(ttr, "invalid speed\n", 0);
			if (tspd != uintmax) {
				ospeed = ispeed = tspd;
				settermspd(ispeed, ospeed, &cntrl);
				if (settermattr(fd, &cntrl) != 0)
					fprintf(stderr, "failed to set baudrate "
							"[RX:%u | TX:%u]\n", ispeed, ospeed);
			}
		}
		goto finish;
	case 'd':
		cechck();
		if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
			replacechar(ttr, 63, &nolfargs);
			chardelay = strtoui(ttr, "invalid delay\n", 0);
			if (chardelay != uintmax) {
				wts.tv_sec = 0;
				wts.tv_nsec = chardelay;
			}
		}
		goto finish;
	case 't':
		cechck();
		fprintf(stderr, "additional output translation option: ");
		if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
			replacechar(ttr, 63, &nolfargs);
			if(troptions(&tropts, ttr))
				goto finish;
			if (setroptions())
				fprintf(stderr, "could not set new options\n");		
		}
		goto finish;
	case 'T':
		cechck();
		printf("additional input translation option: ");
		if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
			replacechar(ttr, 63, &nolfargs);
			if (troptions(&itropts, ttr))
				goto finish;	
			if (setroptions())
				fprintf(stderr, "could not set new options\n");	
		}
		finish:
		if (!half && interactive)
			newterm.c_lflag &= ~ECHO;
		if (!canonical && interactive)
			newterm.c_lflag &= ~ICANON;
		if (settermattr(STDIN_FILENO, &newterm) == -1)
			fprintf(stderr, "failed to restore echo and/or canonical mode\n");
		break;
	case 'p':;
		int st;
		struct timespec ts;
		ts.tv_sec = spulsedelay;
		ts.tv_nsec = nspulsedelay;

		if (ioctl(fd, TIOCMGET, &st) != 0) {
			fprintf(stderr, "failed to get port status\n");
			break;
		}
		st ^= TIOCM_DTR;
		if (ioctl(fd, TIOCMSET, &st) != 0) {
			fprintf(stderr, "failed to set DTR [assertion]\n");
			break;
		}
		nanosleep(&ts, NULL);
		
		st ^= TIOCM_DTR;
		if (ioctl(fd, TIOCMSET, &st) != 0) 
			fprintf(stderr, "failed to set DTR [negation]\n");
		break;
	case BS: /* FALLTHROUGH */
	case DEL:
	case ESC:
		break;
	default:
		fprintf(stderr, "not a valid command [%c]\n", cmdchar);
		break;
	}
	return;
}

void
sighandl(int signo)
{
	die(128 + signo, "\nrecieved signal [%d], exiting\n", signo);
}

void
die(int code, const char *msg, ...)
{
	va_list fpa;
	while (rwc > 0) {
		int t = rwc;
		nanosleep(&wts, NULL);
		if (t == rwc) break;
	}
	if (fd != -1)
		close(fd);

	if (writebuff)
		free(writebuff);
	if (readbuff)
		free(readbuff);
	if (scratchw) 
		free(scratchw);
	if (scratchr)
		free(scratchr);

	if (termchck(&newterm))
		settermattr(STDIN_FILENO, &origterm);

	va_start(fpa, msg);
	vfprintf(stderr, msg, fpa);
	va_end(fpa);
	
	exit(code);
}

int
main(int argc, char *argv[])
{
	long tl;
	char *t;
	char *endptr;
	unsigned int tui;
	int oind, rxspdset, devset, c;
	oind = rxspdset = devset = 0;

	for (int i = 1; i < argc; i++ ) {
		if (argv[i][0] == '-' && argv[i][1] >= '0' && argv[i][1] <= '9') {
			if (asprintf(&t, "-s%s", argv[i] + 1) == -1) {
				fprintf(stderr, "%s: cannot convert -# to -s#\n", argv[0]);
				break;
			}
			argv[i] = t;
		}
	}

	static struct option longopt[] = {
		{"line", required_argument, NULL, 'l'},
		{"speed", required_argument, NULL, 's'},
		{"rx-speed", required_argument, NULL, 'i'},
		{"echo", no_argument, NULL, 'h'},
		{"canonical", no_argument, NULL, 'c'},
		{"odd", no_argument, NULL, 'o'},
		{"even", no_argument, NULL, 'e'},
		{"hardware-rts-cts", no_argument, NULL, 'R'},
		{"hardware-dtr-dsr", no_argument, NULL, 'r'},
		{"software", no_argument, NULL, 'X'},
		{"data", required_argument, NULL, 'D'},
		{"delay", required_argument, NULL, 'd'},
		{"min-chars", required_argument, NULL, 'm'},
		{"stop-bits", no_argument, NULL, 'S'},
		{"backspace", no_argument, NULL, 'b'},
		{"translation", required_argument, NULL, 't'},
		{"input-translation", required_argument, NULL, 'T'},
		{"verbose", no_argument, NULL, 'v'},
		{NULL, 0, NULL, 0}
	};
	 
	while ((c = getopt_long(argc, argv, "D:T:d:i:l:m:s:t:RXbcehorv", longopt, &oind)) != -1) {
		switch (c) {
		case 0: 
			break;
		case 'b':
			backspace ^= 1; break;
		case 'c':
			canonical ^= 1; break;
		case 'R':
			hard ^= 1; break;
		case 'r':
			hard ^= 2; break;
		case 'X':
			soft ^= 1; break;
		case 'd':
			tl = strtol(optarg, &endptr, 10);
			if (errno != 0 || *endptr != '\0' || t < 0) {
				fprintf(stderr, "invalid character delay: %s\n", optarg);
				goto ustusage;
			} else {
				chardelay = tl;
			}
			break;
		case 'D':
			if (optarg[1] != '\0' || !(optarg[0] >= '5' && optarg[0] <= '8')) {
				fprintf(stderr, "invalid number of data bits: %s\n", optarg);
				goto ustusage;	
			} else {
				datab = optarg[0];
			}
			break;
		case 'e':
			parity ^= 1; break;
		case 'o':
			parity ^= 2; break;
		case 'h':
			half ^= 1; break;
		case 'i': /* FALLTHROUGH */
		case 's':
			tui = strtoui(optarg, "invalid speed: %s\n", 1);
			if (tui == uintmax)
				goto ustusage;
			if (c == 'i') {
				ispeed = tui; rxspdset = 1;
			} else {
				ospeed = tui;
				ispeed = rxspdset ? ispeed : ospeed;
			}
			break;
		case 'S':
			stopb ^= 1; break;
		case 'l':
			if (devset) {
				fprintf(stderr, "cannot specify multiple devices\n");
				goto ustusage;
			}
			if (strlen(optarg) > 58) {
				fprintf(stderr, "device name too long\n");
				goto ustusage;
			}
			if (strchr(optarg, '/')) {
				strcpy(line, optarg);
				devset = 1;
			} else {
				sprintf(line, "/dev/%s", optarg);
				devset = 1;
			}
			break;
		case 'm':
			tui = strtoui(optarg, "invalid number of characters: %s\n", 1);
			if (tui == uintmax)
				goto ustusage;
			minchars = tui;
			break;
		case 't':
			if (troptions(&tropts, optarg))		
				goto ustusage;
			break;
		case 'T':
			if (troptions(&itropts, optarg))
				goto ustusage;
			break;
		case 'v':
			verbose ^= 1; break;
		default:
		ustusage:
			die(2, "usage: %s [--line|-l line] [--speed|-s #|-#] [--rx-speed|-i #]\n"
					"           [--data-bits|-D #] [--stop-bits|-S]"
					" [--even|-e] [--odd|-o]\n"
					"           [--hardware-rts-cts|-R]"
					" [--hardware-dtr-dsr|-r] [--software|-X]\n"
					"           [--delay|-d #] [--min-chars|-m #]"
					" [--canonical|-c] [--echo|-h]\n"
					"           [--translation|-t option]"
					" [--input-translation|-T option]\n"
					"           [--verbose|-v] [--backspace|-b]\n",
					argv[0]);
			break;
		}
	}
	/* 
	 * not the best practice, but in order for this `free()`
	 * to not get executed ust needs to be killed, 
	 * which means the OS *should* free the memory
	 */
	free(t);

	if (!rxspdset)
		ispeed = ospeed;

	if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
		interactive = 1;

	signal(SIGHUP, sighandl);
	signal(SIGINT, sighandl);
	signal(SIGQUIT, sighandl);
	signal(SIGTERM, sighandl);

	fd = openport();
	
	pthread_t readthread, writethread;
	pthread_create(&writethread, NULL, writeport, NULL);
	pthread_create(&readthread, NULL, readport, NULL);
	pthread_join(writethread, NULL);
	pthread_join(readthread, NULL);

	/* unreachable */
	return 0;
}
