/***********************************************************************
 *
 * Copyrigth: Karl Hammar, Aspö Data, LGPL
 *
 */
#include "davis_vantage.h"

#include <aspoutil.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

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

LOG_BUFFER(1000)

struct cmdline optv[] = {
  { 'v', "version", NULL, NULL, NULL, NULL, "print this usage text" },
  { 'h', "host", "HOST", "host", "192.168.0.100", NULL, "connect to <host> or /unix (if port is a pathname)" },
  { 'p', "port", "PORT", "port", "22222", NULL, "connect to <port> at remote host or pathname (socket or char device)" },
  OPT_LOG_L,
  OPT_UTIL_F,
};
/* TODO: theese are a pain to maintain, what else to do? */
#define OPTIX_V (0)
#define OPTIX_H (OPTIX_V+1)
#define OPTIX_P (OPTIX_H+1)
#define OPTIX_L (OPTIX_P+1)
#define OPTIX_F (OPTIX_L+1)

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

int debug_flag = 0;
const struct timespec timestep  = { 10, 0 };

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

int run_davis(int fd);
int debug(const uint8_t *buf, size_t len);
int doio(int fd, const uint8_t *obuf, const size_t olen, uint8_t *ibuf, size_t ilen);

void sighandler(int sig, siginfo_t *si, void *context);
int sleepnext(struct timespec *timenext);
void Usage(int exit_status);
int run_unix(const char *path);
int run_serial(const char *dev);
int run_tcp(const char *host, const char *port);

#define OCT (const uint8_t *)

/******************************/
/* return !0 to quit program */
int run_davis(int fd) {
  static int ix;
  struct timespec timenext = { 0, 0};
  const char wakeup[] = "\n";
  const char wrd[] = "WRD\x12M\n"; /* get station type */
  const char nver[] = "NVER\n";       /* firmware numeric version */
  const char ver[] = "VER\n";         /* firmware version date */
  const char loop[] = "LOOP 1\n";    /* get big blob of data */

#define BUFSZ 200
  uint8_t buf[BUFSZ];
  int nread;

  int station_ix;
#define FW_NUM_SZ 6
#define FW_DATE_SZ 12
  char fw_num[FW_NUM_SZ];
  char fw_date[FW_DATE_SZ];

  debug_flag = 0;
  NOTICE("starting run %d, fd = %d", ix, fd);

  /* IV. waking up the console, p.5 */
  nread = doio(fd, OCT wakeup, 1, buf, BUFSZ);
  if (nread == 2 && buf[0] == '\n' && buf[1] == '\r') {
    /*NOTICE("%s", "Wakeup ok\n"); */
  } else {
    ERR("%s", "No response");
    goto run_davis_end;
  }

  /* check station type, p. 9 */
  nread = doio(fd, OCT wrd, strlen(wrd), buf, BUFSZ);
  if (nread == 2 && buf[0] == ACK) {
    LOG_VIF( (station_ix = intstr_find_code((unsigned) buf[1], davis_station_type, davis_station_type_sz)) < 0,
	     LOG_ERR, " Got illegal station type <%02x>\n", (unsigned) buf[1]) {
      goto run_davis_end;
    } else {
      /*NOTICE("Found %s\n", davis_station_type[station_ix].str); */
    }
  } else {
    goto run_davis_end;
  }

  /* get firmware numeric version, p. 11 */
  nread = doio(fd, OCT nver, strlen(nver), buf, BUFSZ);
  if (davis_ok_cleanup(buf, nread) <= 0) goto run_davis_end;
  (void) strncpy(fw_num, (char *) buf, FW_NUM_SZ);

  /* get firmware date, p. 11 */
  nread = doio(fd, OCT ver, strlen(ver), buf, BUFSZ);
  if (davis_ok_cleanup(buf, nread) <= 0) goto run_davis_end;
  (void) strncpy(fw_date, (char *) buf, FW_DATE_SZ);

  NOTICE("Found %s, version %s (%s)", davis_station_type[station_ix].str, fw_num, fw_date);

  (void) timespec_align_to(10);
  (void) timespec_update();
  timenext.tv_sec = timespec_current.tv_sec;  /* no tv_nsec, we want it to happen on whole seconds */

  /* main loop */
  davis_loop_pr_hdr();
  while (1) {
    /* IV. waking up the console, p.5 */
    nread = doio(fd, OCT wakeup, 1, buf, BUFSZ);
    if (nread == 2 && buf[0] == '\n' && buf[1] == '\r') {
      /*NOTICE("%s", "Wakeup ok\n"); */
    } else {
      ERR("%s", "No response");
      goto run_davis_end;
    }

    /* get the current data, p. 12 */
    /*debug_flag = 1; */
    nread = doio(fd, OCT loop, strlen(loop), buf, BUFSZ);
    /*(void) debug_flag = 0; */
    LOG_IF( nread != 100, LOG_ERR) goto run_davis_end;
    {
      struct davis_loop_decoded pkt;
      LOG_IF( memcmp("\006LOO", buf, 4)   , LOG_ERR) goto run_davis_end;
      LOG_IF( davis_crc_buf(buf+1, 99)    , LOG_ERR) goto run_davis_end;
      LOG_IF( memcmp("\n\r", buf+96, 2)   , LOG_ERR) goto run_davis_end;
      LOG_IF( buf[DAVIS_PACKET_TYPE] != 0 , LOG_ERR) goto run_davis_end;
      /*NOTICE("%s", "packet ok"); */

      (void) davis_loop_cnv(buf, &pkt);
      (void) timespec_update();
      (void) davis_loop_pr_cnv(&pkt);
    }

    timenext = timespec_add( timenext, timestep);
    (void) timespec_sleep_to(timenext);
  }


 run_davis_end:
  NOTICE("stopping run %d, fd = %d", ix, fd);
  ++ix;

  return 0;
}

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

#define FOREGROUND (optv[OPTIX_F].arg_value)

int main(int argc, char *argv[]) {
  enum conn_type { t_socket, t_serial, t_tcp } type;
  const char *logfile;

  (void) cmdline_extrpgm(argv);
  (void) log_open(NULL, LOG_PRIO | LOG_SIGHUP | 2, 0);

  /******* checking arguments */
  if ( cmdline_getopt(argc, argv, CMDLINE_SZ(optv), optv) < 0) Usage(1);
  if (optv[OPTIX_V].arg_value) Usage(0);

  logfile = optv[OPTIX_L].arg_value;
  if ((logfile == NULL || logfile[0] == '\0') && optv[OPTIX_F].arg_value == NULL ) {
    fprintf(stderr, "No log file specified and not running in foregrund\n\n");
    Usage(1);
  }

  if (strcmp("/unix", optv[OPTIX_H].arg_value) == 0) {
    /* a socket or serial port wanted */
    struct stat sb;
    const char *path = optv[OPTIX_P].arg_value;

    LOG_VIFERRNO( stat(path, &sb), LOG_ERR, " path = %s", path ) {
      Usage(1);
    }
    switch (sb.st_mode & S_IFMT) {
    case S_IFSOCK:
      LOG_VIFERRNO( access(path, W_OK | R_OK), LOG_ERR, " path = %s", path) exit(1);
      type = t_socket;
      break;
    case S_IFCHR:
      LOG_VIFERRNO( access(path, W_OK | R_OK), LOG_ERR, " path = %s", path) exit(1);
      type = t_serial;
      break;
    default:
      fprintf(stderr, "\"%s\" is not a character device (serial port) nor a socket\n\n", path);
      Usage(1);
      break;
    }
  } else {
    /* tcp connection wanted */
    int err;
    struct addrinfo hints;
    struct addrinfo *res;

    const char *host = optv[OPTIX_H].arg_value;
    const char *port = optv[OPTIX_P].arg_value;

    type = t_tcp;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_flags  = AI_ADDRCONFIG;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    LOG_VIF( (err = getaddrinfo(host, port, &hints, &res)), LOG_ERR,
	     " err = %d (%s), host = %s, port = %s", err, gai_strerror(err), host, port) {
      Usage(1);
    }
  }

  /******* get going */
  {
    if (logfile && logfile[0]) {
      LOG_VIF( log_open(logfile, LOG_PRIO | LOG_SIGHUP | 2, 0) < 0, LOG_ERR, " logfile = %s", logfile) {
	exit(1);
      }
      if (optv[OPTIX_F].arg_value == NULL) {
	LOG_IFERRNO( daemon(0,0), LOG_ERR ) {
	  exit(1);
	}
      }
    }

    {
      pid_t me = getpid();
      pid_t pp = getppid();
      if (optv[OPTIX_F].arg_value == NULL) {
	NOTICE("Became daemon (ppid: %d pid: %d)", (int) pp, (int) me);
      } else {
	NOTICE("Running i foreground (ppid: %d pid: %d)", (int) pp, (int) me);
      }
    }
  }

  (void) signal_set_single(sighandler, SIGINT, NULL);
  (void) signal_set_single(sighandler, SIGTERM, NULL);

  switch (type) {
  case t_socket:
    return run_unix(optv[OPTIX_P].arg_value);
    break;
  case t_serial:
    return run_serial(optv[OPTIX_P].arg_value);
    break;
  case t_tcp:
    return run_tcp(optv[OPTIX_H].arg_value, optv[OPTIX_P].arg_value);
    break;
  }

  return 0;
}

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

void sighandler(int sig, siginfo_t *si, void *context) {
  signal_handlerLog(sig, si, context);

  switch (sig) {
  case SIGHUP:
    (void) log_reopen();
    break;
  case SIGINT:
  case SIGTERM:
    (void) log_close();
    exit(0);
  }
}

int debug(const uint8_t *buf, size_t len) {
  char obuf[500];
  char *pp = obuf;
  const size_t olen = 500;
  size_t nleft = olen;

  (void) quote(&pp, &nleft, buf, len, 2);
  DEBUG("%ld/%ld %s", (long unsigned) len, (long unsigned) olen-nleft, obuf);
  return 0;
}

int doio(int fd, const uint8_t *obuf, const size_t olen, uint8_t *ibuf, size_t ilen) {
  fd_set sr;
  struct timeval tv = { 1, 0 }; /* we'll wait 1sec for answers, else we'll consider the conn. broken */
  int byterate = 9600; /* ? */
  ssize_t nread;
  int res;

  (void) tty_write(fd, obuf, olen);
  if (debug_flag) (void) debug(obuf, olen);
  FD_ZERO(&sr);
  FD_SET(fd, &sr);
  LOG_IFERRNO ( (res = select(fd+1, &sr, NULL, NULL, &tv)) < 0, LOG_ERR ) return -1;
  if (res == 0) return 0;
  nread = tty_read_cont(fd, ibuf, ilen, byterate);
  if (debug_flag) (void) debug(ibuf, nread);

  return nread;
}

void Usage(int exit_status) {
  FILE *fp;

  if (exit_status) fp = stderr;
  else fp = stdout;

  fprintf(fp, "Name:\n\t%s - daemon to get values from Davis Advantage weather station (via network serial adapter)\n", cmdline_pgm);
  fprintf(fp, "Synopsis:\n\t%s [options]\n", cmdline_pgm);
  fprintf(fp, "Description: (short long env_var default curr_value descr.)\n");
  LOG_IF(cmdline_fusage(fp, CMDLINE_SZ(optv), optv, "\t"), LOG_ERR) {}
  fprintf(fp, "\n\tYou have to specify a log file unless you want to run in the foreground\n");

  exit(exit_status);
}

int run_unix(const char *path) {
  printf("%s, unix domain socket support not implemented yet, sorry\n", path);
  return -1;
}

int run_serial(const char *dev) {
  int stop = 0;
  int fd;
  struct termios old;
  int speed = B9600;

  /* compared to the tcp case, we won't loose out fd, so the open is placed outside the while(1) {} */
  LOG_IF( (fd = tty_openraw(&old, dev, speed, 1, 1, 1)) < 0, LOG_ERR ) {
    return -1;
  }
  while (!stop) {
    stop = run_davis(fd);
  }
  LOG_IF( tty_close(fd, &old, dev) < 0, LOG_ERR) return -1;
  return 0;
}

int run_tcp(const char *host, const char *port) {
  int err;
  int stop = 0;
  struct addrinfo hints;
  struct addrinfo *res;
  struct addrinfo *ap;

  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_flags  = AI_ADDRCONFIG;
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;

  while (!stop) {
    LOG_VIF( (err = getaddrinfo(host, port, &hints, &res)), LOG_ERR,
	     " err = %d (%s), host = %s, port = %s", err, gai_strerror(err), host, port) {
      /* do nothing for a while, maybe some dns etc. get fixed */
    } else {
      for (ap = res; ap != NULL; ap = ap->ai_next) {
	int fd;
	LOG_IFERRNO( (fd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol)) < 0, LOG_ERR ) {
	} else {
	  LOG_IFERRNO( connect(fd, ap->ai_addr, ap->ai_addrlen) < 0, LOG_ERR) {
	  } else {
	    stop = run_davis(fd);
	  }
	  LOG_IFERRNO( close(fd) < 0, LOG_ERR) { }
	}
      }
      freeaddrinfo(res);
    }
    sleep(10);
  }
  return 0;
}
