#include "ipio.h"
#include <aspoutil.h>

#include <fcntl.h>
#include <netdb.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

int ipio_open(int *fd, struct sockaddr_in *endpoint, const char *host,
	      unsigned short port) {
  int retstat = 0;
  struct hostent *he;

  errno = 0;
  LOG_IF(fd == NULL, LOG_CRIT) return -1;
  LOG_IF(endpoint == NULL, LOG_CRIT) return -1;
  LOG_IF(!host && !(host = getenv("IPIO_HOST")), LOG_CRIT) return -1;

  if (!port) port = 8;

  LOG_IFERRNO( (retstat = socket(AF_INET, SOCK_DGRAM, 0)) == -1, LOG_CRIT)
    return retstat;
  *fd = retstat;

  LOG_IFERRNO( (retstat = fcntl(*fd, F_GETFL)) == -1, LOG_CRIT) return retstat;
  LOG_IFERRNO( fcntl(*fd, F_SETFL, retstat | O_NONBLOCK) == -1, LOG_CRIT){
    close(*fd);
    return retstat;
  }

  endpoint->sin_family = AF_INET;
  he = gethostbyname(host);
  if (!he) {
    ERR("gethostbyname(%s) => h_errno = %d", host, h_errno);
    close(*fd);
    return -1;
  }
  memcpy( &(endpoint->sin_addr.s_addr), he->h_addr_list[0], he->h_length);
  endpoint->sin_port = htons(port);

  return retstat;
}

int ipio_send(int fd, struct sockaddr_in *endpoint, uint8_t const *obuf,
	      int const olen) {
  int retstat;

  errno = 0;
  LOG_IF(fd == -1, LOG_CRIT) return -1;
  LOG_IF(endpoint == NULL, LOG_CRIT) return -1;
  LOG_IF(obuf == NULL, LOG_CRIT) return -1;
  LOG_IF(olen < 4, LOG_CRIT) return -1;

  LOG_IFERRNO( (retstat = sendto(fd, obuf, olen, 0, (struct sockaddr *) endpoint,\
 sizeof(struct sockaddr_in))) == -1,
	      LOG_CRIT)
    return retstat;
  if (retstat != olen) {
    ERR("wanted to send %d bytes, but %d bytes was send", olen, retstat);
    return -1;
  }

  return retstat;
}

int ipio_recv(int fd, uint8_t const *obuf, int const olen, uint8_t *ibuf,
	      int const ilen) {
  int retstat = 0;

  errno = 0;
  LOG_IF(fd == -1, LOG_CRIT) return -1;
  LOG_IF(obuf == NULL, LOG_CRIT) return -1;
  LOG_IF(olen < 4, LOG_CRIT) return -1;
  LOG_IF(ibuf == NULL, LOG_CRIT) return -1;
  /* though STATUS requires 10 bytes, 4 is a minimum */
  LOG_IF(ilen < 5, LOG_CRIT) return -1;

  errno = 0;
  retstat = recv(fd, ibuf, ilen, 0);
  if (retstat == -1) {
    if (errno != EAGAIN)
      CRIT("recv(fd, ibuf, ilen, 0): %s (%d)", strerror(errno), errno);
    return errno;
  }

  if      (retstat == 0)
    NOTICE("%s", "peer has shutdown");
  else if (retstat < 5)
    NOTICE("too short (%d) response", retstat);
  else if (ibuf[1] != obuf[1])
    NOTICE("id (%d) didn't match response (%d)", obuf[1], ibuf[1]);
  else if (ibuf[2] != obuf[0])
    NOTICE("command (%x) didn't match response (%x)", obuf[0], ibuf[2]);
  else if (ibuf[3] != obuf[2])
    NOTICE("address (%x) didn't match response (%x)", obuf[2], ibuf[3]);

  return retstat;
}

int ipio_exec(int fd, struct sockaddr_in *endpoint, uint8_t const *obuf,
	      int const olen, uint8_t *ibuf, int const ilen,
	      struct timeval *tv) {
  int retstat = 0;
  int ix;
  fd_set rd;
  struct timeval tm = *tv;

  if ((retstat = ipio_send(fd, endpoint, obuf, olen)) < 0) return -1;

  FD_ZERO(&rd);
  FD_SET(fd, &rd);
  LOG_IFERRNO( (retstat = select(fd + 1, &rd, NULL, NULL, tv)) == -1,
	      LOG_CRIT) return retstat;
  if (retstat == 0) {
    NOTICE("timeout tv = { %ld, %ld }", tm.tv_sec, tm.tv_usec);
    return -1;
  }

  for(ix = 0; ix < 1000; ix++) { /* put a limit on the retries */
    retstat = ipio_recv(fd, obuf, olen, ibuf, ilen);
    if (retstat > 0) return retstat;
    LOG_VIF( retstat <= 0 && errno != EAGAIN, LOG_ERR, "%s", "giving up")
      return retstat;
  }
  return retstat;
}
