#define PORT 8080

// ttyGS0 is a virtual serial line implemented by the Pi as a gadget, fed from a Windows PC USB port

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <netdb.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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdarg.h>
#include <signal.h>

#ifndef FALSE
#define FALSE (0!=0)
#define TRUE  (0==0)
#endif

#define BUF_SIZE 1024

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

struct termios ttyUSB0Orig, t0;

int USB0_Fd, WIFI_Fd;

fd_set inFds;

static char ServerName[1024];

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

char *progname;

void errExit (char *what)
{
  fprintf (stderr, "Exit: %s\n", 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 (argc > 2) {
    fprintf(stderr, "syntax: %s [server]\n", progname);
    exit(EXIT_FAILURE);
  }
  strcpy(ServerName, "127.0.0.1");
  if (argc == 2) strcpy(ServerName, argv[1]);
  
  struct sockaddr_in servaddr;

  // socket create and varification 
  WIFI_Fd = socket (AF_INET, SOCK_STREAM, 0);
  if (WIFI_Fd == -1) {
    fprintf (stderr, "socket creation failed...\n");
    exit (0);
  } else {
    fprintf (stderr, "Socket successfully created..\n");
  }
  bzero (&servaddr, sizeof (servaddr));

  // assign IP, PORT 
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = inet_addr (ServerName);
  servaddr.sin_port = htons (PORT);

  // connect the client socket to server socket 
  while (connect (WIFI_Fd, (struct sockaddr *) &servaddr, sizeof (servaddr))
	 != 0) {
    fprintf (stderr, "connection with %s failed... retrying...\n", ServerName);
    sleep (1);
  }

  fprintf (stderr, "Connected to %s..\n", ServerName);

  // fprintf(stderr, "Debug: 1\n");

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

  // fprintf(stderr, "Debug: 2\n");
  system ("stty -F /dev/ttyGS0 speed 115200 > /dev/null 2>&1");

  // fprintf(stderr, "Debug: 3\n");
  USB0_Fd = open ("/dev/ttyGS0", O_RDWR);
  // fprintf(stderr, "Debug: 3a\n");
  if (USB0_Fd < 0) {
    errExit ("open");
  }
  // fprintf(stderr, "Debug: 4\n");
  tcsendbreak (USB0_Fd, 0);
  /* relay data between both USB serial ports */
  // fprintf(stderr, "Debug: 5\n");
  if (tcgetattr (USB0_Fd, &t0) == -1)	/* return -1 */
    errExit ("tcgetattr");

  ttyUSB0Orig = t0;
  t0.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);	// COMMENT OUT THIS
  // "| ECHO"
  // to cause GRBL commands to
  // be echoed on the incoming
  // connection
  /* Noncanonical mode, disable signals, extended input processing, and
     echoing */
  t0.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 */
  t0.c_cc[VMIN] = 1;		/* Character-at-a-time input */
  t0.c_cc[VTIME] = 0;		/* with blocking */
  // fprintf(stderr, "Debug: 6\n");
  (void) tcsetattr (USB0_Fd, TCSAFLUSH, &t0);

  // fprintf(stderr, "Debug: 7\n");
  if (atexit (ttyReset) != 0)
    errExit ("atexit");

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

  // TEMP!!! signal(SIGINT, INThandler);

  fprintf (stderr, "Starting main loop\n");
  for (;;) {
  int rc, got_any;

    got_any = FALSE;
    FD_ZERO (&inFds);
    FD_SET (USB0_Fd, &inFds);
    FD_SET (WIFI_Fd, &inFds);

    if (WIFI_Fd > USB0_Fd) {
      rc = select (WIFI_Fd + 1, &inFds, NULL, NULL, NULL);
    } else {
      rc = select (USB0_Fd + 1, &inFds, NULL, NULL, NULL);
    }

    if (FD_ISSET (USB0_Fd, &inFds)) {	/* USB0 -> WIFI */
      // fprintf(stderr, "getting serial data\n");

      numRead = read (USB0_Fd, USB0_buf, BUF_SIZE);
      if (numRead > 0) {
	got_any = TRUE;
	// fprintf(stderr, "got serial data %d\n", numRead);
	(void) write (WIFI_Fd, USB0_buf, numRead);	// for now assume all 
							// 
	// writes succeed.
	(void) write (2, USB0_buf, numRead);	// for now assume all 
	// writes succeed.
      }
    }

    if (FD_ISSET (WIFI_Fd, &inFds)) {	/* WIFI -> USB0 */
      numRead = read (WIFI_Fd, WIFI_buf, BUF_SIZE);
      if (numRead > 0) {
	got_any = TRUE;
	(void) write (USB0_Fd, WIFI_buf, numRead);
	// (void)write(2, WIFI_buf, numRead);
      }
    }

    if (!got_any) {
      fprintf (stderr,
	       "server died? Will retry until we re-establish a connection...\n");

      // socket create and varification 
      WIFI_Fd = socket (AF_INET, SOCK_STREAM, 0);
      if (WIFI_Fd == -1) {
	fprintf (stderr, "socket creation failed...\n");
	exit (0);
      } else {
	fprintf (stderr, "Socket successfully created..\n");
      }
      bzero (&servaddr, sizeof (servaddr));

      // assign IP, PORT 
      servaddr.sin_family = AF_INET;
      servaddr.sin_addr.s_addr = inet_addr ("127.0.0.1");
      servaddr.sin_port = htons (PORT);

      // connect the client socket to server socket 
      while (connect
	     (WIFI_Fd, (struct sockaddr *) &servaddr,
	      sizeof (servaddr)) != 0) {
	fprintf (stderr,
		 "reconnection with the %s failed... retrying...\n", ServerName);
	sleep (1);
      }
      fprintf (stderr, "Connected to %s.\n", ServerName);
      // fprintf(stderr, "Debug: 1\n");

      // close(WIFI_Fd);
      // (void)tcsetattr (USB0_Fd, TCSANOW, &ttyUSB0Orig); // restore
      // keyboard/stdin
      // exit(EXIT_FAILURE);
    }
  }
  return (EXIT_FAILURE);	// killing is the only expected exit
}