#include <aspoutil.h>
#include <aspoprotocol.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#define LOG_BUFSZ 200
const size_t log_bufsz = LOG_BUFSZ;
char log_buffer[LOG_BUFSZ];

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

#define TIME 100000
#define READERSZ 5

uint8_t req[S6400_MAXREQLEN];
#define RESSZ 4000
uint8_t res[RESSZ];
struct termios ttysave;
int fd = -1;
char *dev;
static uint32_t reader[READERSZ];
size_t reader_cnt = 0;
static struct openraw_t opts = { 8, 0, 1, };

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

static void Usage(void) {
  printf("Usage:\n\t%s serial_port [reader_serial_number]...\n", cmdline_pgm);
  exit(1);
}

static void Close(void) {
  NOTICE("Stopping %s", cmdline_pgm);
  LOG_VIF( (fd = tty_close(fd, &ttysave, dev, getpid())) < 0, LOG_ERR, " dev = %s fd = %d", dev, fd) { }
}

/*
static void sighdl(int sig, siginfo_t *si, void *context) {
  signal_handlerLogReopen(sig, si, context);
  switch (sig) {
  case SIGINT:
  case SIGTERM:
    exit(Close());
    break;
  case SIGUSR1:
  case SIGUSR2:
    // theese are only logged for the time being
    break;
  }
}
*/

static int doio(const void *obuf, size_t olen, void *ibuf, ssize_t ilen ) {
  ssize_t ret;
  uint16_t code;
  const char *err1;
  const char *err2;

  LOG_IF( tcflush(fd, TCIOFLUSH) < 0, LOG_ERR) { }
  LOG_IF( (ret = tty_write(fd, obuf, olen) != (ssize_t) olen), LOG_NOTICE) return -1;
  /*s6400_prpkg("out ", obuf, olen); */
  /* sending 15 bytes and receiving 22 bytes takes about 1ms, so the delay is for reader tag processing */
  usleep(TIME); /* this don't check for signal interruptions */
  bzero( ibuf, ilen);  
  LOG_IF( (ret = tty_read(fd, ibuf, ilen)) < 0, LOG_ERR) return -1;
  /*s6400_prpkg("in  ", ibuf, ret); */

  if (!ret) {
    /*s6400_prpkg("no response for", obuf, olen); */
    return 0;
  }
  if (s6400_validate(ibuf, ret)   < 0) goto doio_err;
  if (s6400_check_error_res(ibuf, &code, &err1, &err2) < 0 || code) {
    if (code == 0x0201) return 0; /* no transponder found */
    if (code == 0x0206) return 0; /* for some reasons, sometimes tag crc error is reported */
    (void) s6400_check_error_report(code, err1, err2);
    goto doio_err;
  }

  return ret;

doio_err:
  s6400_prpkg("doio req: ", obuf, olen);
  s6400_prpkg("doio res: ", ibuf, ret);
  return -1;
}

static void run(void) {
  static long sec;
  const size_t req_len = s6400_read_tag_information_req( req, S6400_MAXREQLEN, 1, 0);
  {
    uint8_t request[reader_cnt][req_len];
    struct timespec deadline[reader_cnt];
    size_t ix;
    uint64_t uid;
    int val;
    struct timespec wshort = { 0, 100*1000*1000 };
    struct timespec wlong  = { 1,   0*1000*1000 };
    struct timespec wnxt   = { 0, wshort.tv_nsec / reader_cnt };

    (void) timespec_update();
    for (ix = 0; ix < reader_cnt; ++ix) {
      (void) s6400_read_tag_information_req( request[ix], req_len, reader[ix], 0);
      (void) nanosleep(&wnxt, NULL);
      deadline[ix] = timespec_current;
    }

    while(1) {
      int flg = 0;
      (void) timespec_update();
      if (sec != timespec_current.tv_sec) {
	flg = 1;
	sec = timespec_current.tv_sec;
      }
      for (ix = 0; ix < reader_cnt; ++ix) {
        (void) nanosleep(&wnxt, NULL);
	if (timespec_cmp(deadline[ix], timespec_current) > 0) continue;
	if ( (val = doio(request[ix], req_len, res, RESSZ)) <= 0) goto run_cont;
	if (s6400_read_tag_information_res(res, NULL, &uid, NULL, NULL, NULL, NULL, NULL) < 0) {
	  s6400_prpkg(NULL, request[ix], req_len);
	  s6400_prpkg(NULL, res, val);
	  goto run_cont;
	}
	flg = 0;
	printf("%ld %07d %llX\n", sec, reader[ix], uid);
	deadline[ix] = timespec_add(timespec_current, wlong);
	continue;

      run_cont:
	deadline[ix] = timespec_add(timespec_current, wshort);
      }
      if (flg) printf("%ld\n", sec); /* this is a timing "pulse" once a second */
    }
  }
}

int main(int argc, char *argv[]) {
  int val;
  int baudrate = B38400;
  /*sigset_t unblock_set; */
  int ix;
  int flag_not_found = 0;

  (void) cmdline_extrpgm(argv);
  log_open("", LOG_PRIO | 2, 0); /* log to stdout */
  if (argc < 2) Usage();
  dev = argv[1];

  val = argc - 2;
  LOG_VIF( READERSZ < val, LOG_NOTICE, " READERSZ = %d argc-2 = %d", READERSZ, val) {
    val = READERSZ;
  }

  /*
  {
    const int sigs[] = { SIGHUP, SIGINT, SIGUSR1, SIGUSR2, SIGTERM, 0 };
    LOG_IF( signal_set_multi(sighdl, sigs, &unblock_set), LOG_CRIT) return -1;
  }
  */

  LOG_VIF( (fd = tty_openraw(&ttysave, dev, baudrate, &opts)) < 0, LOG_ERR, " dev = %s", dev ) Usage();
  LOG_IF( atexit(Close) < 0, LOG_ERR) return -1;
  LOG_IF( setvbuf(stdout, (char *) NULL, _IOLBF, 0), LOG_NOTICE) { } /* too bad, but we'll continue */

  if (argc == 2) {
    val = s6400_reader_version_req( req, S6400_MAXREQLEN, 0);
    LOG_IF( doio(req, (size_t) val, res, RESSZ) < 0, LOG_ERR) return -1;
    LOG_IF( s6400_reader_version_res(res, NULL, NULL, reader), LOG_ERR) return -1;
    printf("Reader found:     %07u\n", reader[reader_cnt]);
    fflush(stdout);
    reader_cnt = 1;
  }


  for (ix = 2; ix < argc; ++ix) {
    uint32_t t1;
    uint32_t t2;
    int flag = 0;

    LOG_VIF( s6400_str2serial(argv[ix], &t1), LOG_ERR, " argv[%d] = %s", ix, argv[ix]) {
      Usage();
    }
    /* check for presence */
    val = s6400_reader_version_req( req, S6400_MAXREQLEN, t1);
    LOG_IF( doio(req, (size_t) val, res, RESSZ) < 0, LOG_ERR) flag = 1;
    else LOG_IF( s6400_reader_version_res(res, NULL, NULL, &t2), LOG_ERR) flag = 1;

    /* this might help, who knows? */
    val = s6400_reader_reset_req( req, S6400_MAXREQLEN, t1);
    LOG_IF( doio(req, (size_t) val, res, RESSZ) < 0, LOG_ERR) flag = 1;

    /* led and busser */
    val = s6400_reader_mode_req( req, S6400_MAXREQLEN, t1, S6400_MODE_LEDAUDIO, S6400_MODE_AUDIO);
    LOG_IF( doio(req, (size_t) val, res, RESSZ) < 0, LOG_ERR) {
      /* TODO response ignore for time beeing */
    }

    if (!flag && t1 == t2) {
      reader[reader_cnt] = t1;
      printf("Reader  verified: %07u\n", reader[reader_cnt]);
      fflush(stdout);
      ++reader_cnt;
    } else {
      printf("Reader not found: %07u\n", t1);
      flag_not_found = 1;
    }
  }
  if (flag_not_found) Usage();


/*  (void) setlogmask(LOG_UPTO(LOG_ERR)); */
  NOTICE("Starting %s", cmdline_pgm);

  run();

  /*Close(); */
  return 1;
}
