// ttyUSB0 is a serial line coming in to the Pi from a Windows PC
// ttyUSB1 is the serial connection to the GRBL controller...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <libgen.h>
#include <termios.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdarg.h>
#include <signal.h>
#define BUF_SIZE 1024

char USB0_buf[BUF_SIZE+1], USB1_buf[BUF_SIZE+1];

struct termios ttyUSB0Orig, ttyUSB1Orig, t0, t1;

int USB0_Fd, USB1_Fd, file0_fd, file1_fd;

fd_set inFds;

void ttyReset(void) {
  (void)tcsetattr (USB0_Fd, TCSANOW, &ttyUSB0Orig); // restore keyboard/stdin
  (void)tcsetattr (USB1_Fd, TCSANOW, &ttyUSB1Orig); // restore keyboard/stdin
  fprintf(stderr, "\nexit()\n");
  _exit(EXIT_SUCCESS);
}

char *progname;

void errExit(char *what) {
  exit(EXIT_FAILURE);
}

void  INThandler(int sig)
{ // force ^C to go via atexit() handler...
  signal(sig, SIG_IGN);
  exit(0);
  // signal(SIGINT, INThandler);
}

int main(int argc, char **argv) {
  size_t numRead;
  char *s;

  // Get clean version of executable name.  Should work on most existing systems (2006)
  progname = argv[0];
  if ((s = strrchr(progname, '/')) != NULL) progname = s+1;  // Unix
  if ((s = strrchr(progname, '\\')) != NULL) progname = s+1; // M$
  if ((s = strrchr(progname, ']')) != NULL) progname = s+1;  // Dec
  if ((s = strrchr(progname, ';')) != NULL) *s = '\0';       // Version no's
  if (((s = strrchr(progname, '.')) != NULL) && (strcasecmp(s, ".exe") == 0)) *s = '\0';
  if (((s = strrchr(progname, '.')) != NULL) && (strcasecmp(s, ".com") == 0)) *s = '\0';

  if (tcgetattr (USB0_Fd, &ttyUSB0Orig) == -1)
    errExit("tcgetattr");

  if (tcgetattr (USB1_Fd, &ttyUSB1Orig) == -1)
    errExit("tcgetattr");

  system ("stty -F /dev/ttyUSB0 speed 115200 > /dev/null");
  system ("stty -F /dev/ttyUSB1 speed 115200 > /dev/null");

  USB0_Fd = open ("/dev/ttyUSB0", O_RDWR);
  if (USB0_Fd < 0) {
    errExit("open");
  }
  USB1_Fd = open ("/dev/ttyUSB1", O_RDWR);
  if (USB1_Fd < 0) {
    errExit("open");
  }

  tcsendbreak(USB0_Fd, 0);
  tcsendbreak(USB1_Fd, 0);
  
  /* relay data between both USB serial ports */

  if (tcgetattr (USB0_Fd, &t0) == -1) /*return -1*/;
  if (tcgetattr (USB1_Fd, &t1) == -1) /*return -1*/;
  ttyUSB0Orig = t0;
  ttyUSB1Orig = t1;
  t0.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); // COMMENT OUT THIS "| ECHO"
                                                  // to cause GRBL commands to
                                                  // be echoed on the incoming
                                                  // connection
  t1.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
  /* Noncanonical mode, disable signals, extended input processing, and echoing */
  t0.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK);
  t1.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK);
  /* Disable special handling of CR, NL, and BREAK. No 8th-bit stripping or parity error handling. Disable START/STOP output flow control. */

  // If it weren't for the fact that I might later want to support ^S/^Q, I would probably put these in RAW mode instead:
#ifdef NEVER // RAW:
  t?.c_iflag |= IGNBRK;
  t?.c_iflag &= ~(INLCR | ICRNL | IXON | IXOFF);

  t?.c_lflag &= ~(ICANON | ECHO | ECHOK | ECHOE | ECHONL | ISIG | IEXTEN);
#endif

  t0.c_oflag &= ~OPOST;          /* Disable all output processing */
  t1.c_oflag &= ~OPOST;          /* Disable all output processing */
  t0.c_cc[VMIN] = 1;             /* Character-at-a-time input */
  t1.c_cc[VMIN] = 1;             /* Character-at-a-time input */
  t0.c_cc[VTIME] = 0;            /* with blocking */
  t1.c_cc[VTIME] = 0;            /* with blocking */
  (void)tcsetattr (USB0_Fd, TCSAFLUSH, &t0);
  (void)tcsetattr (USB1_Fd, TCSAFLUSH, &t1); // TCSANOW perhaps?


  if (atexit(ttyReset) != 0)
    errExit("atexit");

  /* everything that comes from USB0 is forwarded to USB1, and vice-versa */

  signal(SIGINT, INThandler);

  file0_fd = open("USBLOG0-to-CNC.bin", O_WRONLY|O_CREAT, 0755);
  file1_fd = open("USBLOG1-from-CNC.bin", O_WRONLY|O_CREAT, 0755);

  for (;;) {

    FD_ZERO (&inFds);
    FD_SET (USB0_Fd, &inFds);
    FD_SET (USB1_Fd, &inFds);

    (void)select (USB1_Fd + 1, &inFds, NULL, NULL, NULL);

    if (FD_ISSET (USB0_Fd, &inFds)) {  /* USB0 -> USB1 */
      numRead = read (USB0_Fd, USB0_buf, BUF_SIZE);
      if (numRead > 0) {
	(void)write(USB1_Fd, USB0_buf, numRead); // for now assume all writes succeed.
	(void)write(2, USB0_buf, numRead);
	(void)write(file0_fd, USB0_buf, numRead);
      }
    }

    if (FD_ISSET (USB1_Fd, &inFds)) {  /* USB1 -> USB0 */
      numRead = read (USB1_Fd, USB1_buf, BUF_SIZE);
      if (numRead > 0) {
	(void)write(USB0_Fd, USB1_buf, numRead);
        (void)write(2, "\033[7m", 4); // Reversed
	(void)write(2, USB1_buf, numRead);
        (void)write(2, "\033[m", 3);  // Normal
	(void)write(file1_fd, USB1_buf, numRead);
      }
    }

  }
  return(EXIT_FAILURE); // killing is the only expected exit
}