#include <aspoutil.h>
#include <aspoprotocol.h>

#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

LOG_BUFFER(200)

struct cmdline optv[] = {
  { 'b', "baud-rate", "BAUD_RATE", "num", "38400", NULL, "use <num> as default baudrate" }
  /*OPT_LOG_L, */
  /*OPT_UTIL_F, */
  /*{ 'c', "command", "COMMAND", "file", NULL, NULL, "use pipe <file> as a command channel" } */
};

/****************************/

#define TIME 100000
speed_t baud_def = B38400;

#define READERSZ 5
#define RESSZ 4000

struct buffer_t {
  /* allocated area */
  size_t sz;
  uint8_t *buf;

  /* used portion */
  uint8_t *sta;
  uint8_t *end;
};

struct serial_port {
  int fin;
  int fout;
  struct termios save;
  const char *dev;
  speed_t baud_bits;

  struct buffer_t wr;
  struct buffer_t rd;
};

struct reader_info {
  uint32_t serial;  /* reader serial number */
  int port_ix;
};

/*typedef int cb_t(int fd, int mode, void *data); */

int Usage(int code);
/*int run(int cnt, int fd[cnt], cb_t (*cb)[cnt]); */
int run( int sz, struct serial_port *port);

/****************************/

int main(int argc, char *argv[]) {
  int reader_cnt = 0;
  /*int port_cnt = 0; */
  /*int reader_flag = -1; */
  int ix;
  struct serial_port port[2];
  struct serial_port *pp;
  uint8_t cmdrd[400];
  uint8_t cmdwr[400];
  uint8_t req[1][S6400_MAXREQLEN];
  uint8_t res[1][RESSZ];

  (void) cmdline_extrpgm(argv);
  (void) log_open("", LOG_PRIO | 2, 0); /* log to stdout */

  if ( cmdline_getopt(argc, argv, CMDLINE_SZ(optv), optv) < 0 || argc - optind < 1) {
    Usage(1);
  } else {
    struct ttybaud const *ttybaud = ttybaud_str(optv[0].arg_value);
    if (ttybaud == NULL) {
      char buf[1000] = "";
      char *cp = buf;
      size_t len = 1000;

      printf("Baudrate not supported: %s\n", optv[0].arg_value);
      (void) ttybaud_print_valid(&cp, &len);
      printf("Valid baudrates:\n\t%s\n", buf);
      Usage(1);
    }
    baud_def = ttybaud->bits;
  }

  LOG_IF( setvbuf(stdout, (char *) NULL, _IOLBF, 0), LOG_NOTICE) {
    /* too bad, but we'll continue */
  }

  if (argc - optind != 1) {
    Usage(1);
  }

  pp = port;
  pp->fin  = STDIN_FILENO;
  pp->fout = STDOUT_FILENO;
  pp->dev = NULL;
  memset(&(pp->rd), 0, sizeof(struct buffer_t));
  memset(&(pp->wr), 0, sizeof(struct buffer_t));
  pp->rd.sz = 400;
  pp->rd.buf = cmdrd;
  pp->wr.sz = 400;
  pp->wr.buf = cmdwr;
  ++pp;

  for (ix = 0; ix < argc - optind; ++ix) {
    pp->dev = argv[ix + optind];
    pp->baud_bits = baud_def;

    LOG_VIF( (pp->fin = tty_openraw(&(pp->save), pp->dev, pp->baud_bits, 0, 1, 1)) < 0, LOG_ERR, " dev = %s", pp->dev ) {
      Usage(1);
    }
    pp->fout = pp->fin;
    memset(&(pp->rd), 0, sizeof(struct buffer_t));
    memset(&(pp->wr), 0, sizeof(struct buffer_t));
    pp->wr.sz = S6400_MAXREQLEN;
    pp->wr.buf = req[ix];
    pp->rd.sz = RESSZ;
    pp->rd.buf = res[ix];
    ++reader_cnt;
    ++pp;
  }

  run(2, port);

  for (pp = port+1; pp < port + reader_cnt; ++pp) {
    LOG_VIF( tty_close(pp->fin, &(pp->save), pp->dev), LOG_ERR, " dev = %s", pp->dev) { }
  }
  printf("bye\n");
  return 0;
}

int Usage(int code) {
  printf("Name:\n\t%s - s6400 protocol gateway for humans\n", cmdline_pgm);
  printf("Synopsis:\n\t%s [options] serial_port\n", cmdline_pgm);
  printf("Description:\n");
  LOG_IF(cmdline_usage(CMDLINE_SZ(optv), optv, "\t"), LOG_ERR) { }

  if (code) exit(code);
  return 0;
}

int setset(fd_set *sr, fd_set *sw, int sz, struct serial_port *port);
int setset(fd_set *sr, fd_set *sw, int sz, struct serial_port *port) {
  int nfds = 0;
  int ix;

  FD_ZERO(sr);
  for (ix = 0; ix < sz; ++ix) {
    FD_SET(port[ix].fin, sr);
    if (nfds < port[ix].fin) nfds = port[ix].fin;
  }

  FD_ZERO(sw);
  for (ix = 0; ix < sz; ++ix) {
    if (port[ix].wr.sta < port[ix].wr.end) FD_SET(port[ix].fout, sw);
    if (nfds < port[ix].fout) nfds = port[ix].fout;
  }
  ++nfds;
  return nfds;
}

int run( int sz, struct serial_port *port) {
  int ix;
  fd_set sr;
  fd_set sw;
  int nfds = 0;
  struct timeval tv = { 1, 0 };

  for (ix = 0; ix < sz; ++ix) {
    port[ix].rd.sta = port[ix].rd.end = port[ix].rd.buf;
    port[ix].wr.sta = port[ix].wr.end = port[ix].wr.buf;
  }

  while(1) {
    nfds = setset(&sr, &sw, sz, port);
    LOG_IFERRNO ( (ix = select(nfds, &sr, &sw, NULL, &tv)) < 0 && errno != EINTR, LOG_ERR ) {
      /* too bad, let user handle this */
      break;
    }
    if (ix < 0) { /* EINTR */
      continue;
    } else if (ix == 0) { /* timeout */


    } else { /* data to be read/written */
      int cnt;
      struct serial_port *pp;

      /* check writers */
      for (pp = port; pp < port+sz; ++pp) {
	struct buffer_t *pb = &(pp->wr);
	if (FD_ISSET(pp->fout, &sw)) {
	  LOG_IFERRNO( (cnt = write(pp->fout, pb->sta, pb->end - pb->sta)) < 0 && errno != EINTR, LOG_ERR) {
	    return -1;
	  }
	  pb->sta += cnt;
	  if (pb->sta == pb->end) {
	    pb->sta = pb->end = pb->buf;
	  }
	}
      }

      /* check readers */
      for (pp = port; pp < port+sz; ++pp) {
	struct buffer_t *pb = &(pp->rd);
	if (FD_ISSET(pp->fin, &sr)) {
	  size_t len = pb->buf + pb->sz - pb->end;
	  if (len > pb->sz) len = pb->sz;
	  LOG_IFERRNO( (cnt = read(pp->fin, pb->end, len)) < 0 && errno != EINTR, LOG_ERR) {
	    return -1;
	  }
	  pb->end += cnt;
	  if (memchr(pb->buf, '\n', pb->end - pb->buf)) {
	    /*memcpy(); */
	  }
	}
      }

    }

  }

  return -1;
}
