#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static const int backlog = 10;

static void handle_client(int fd);

int main(int argc, char **argv) {
  struct addrinfo *ai;
  struct addrinfo hints = {.ai_family = AF_UNSPEC,
                           .ai_socktype = SOCK_STREAM,
                           .ai_flags = AI_PASSIVE};
  int res, fd;

  if (argc != 2) errx(1, "usage: %s port", argv[0]);

  res = getaddrinfo(NULL, argv[1], &hints, &ai);
  if (res) errx(EXIT_FAILURE, "getaddrinfo: %s", gai_strerror(res));

  fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
  if (fd < 0) err(EXIT_FAILURE, "socket");

  res = 1;
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res)))
    err(EXIT_FAILURE, "setsockopt");

  if (bind(fd, ai->ai_addr, ai->ai_addrlen)) err(EXIT_FAILURE, "bind");

  if (listen(fd, backlog)) err(EXIT_FAILURE, "listen");

  for (;;) {
    struct sockaddr_storage clt_addr;
    socklen_t clt_addrlen = sizeof(clt_addr);
    char clt_dns[NI_MAXHOST], clt_ip[NI_MAXHOST], clt_port[NI_MAXSERV];
    struct sockaddr_storage srv_addr;
    socklen_t srv_addrlen = sizeof(srv_addr);
    char srv_dns[NI_MAXHOST], srv_ip[NI_MAXHOST], srv_port[NI_MAXSERV];
    int newfd;

    /* accept(fd, NULL, NULL) */
    newfd = accept(fd, (struct sockaddr *)&clt_addr, &clt_addrlen);
    if (newfd < 0) err(EXIT_FAILURE, "accept");

    getnameinfo((struct sockaddr *)&clt_addr, clt_addrlen, clt_dns,
                sizeof(clt_dns), NULL, 0, 0);
    getnameinfo((struct sockaddr *)&clt_addr, clt_addrlen, clt_ip,
                sizeof(clt_ip), clt_port, sizeof(clt_port),
                NI_NUMERICHOST | NI_NUMERICSERV);
    fprintf(stderr, "client: %s (%s), port %s \n", clt_dns, clt_ip, clt_port);

    getsockname(newfd, (struct sockaddr *)&srv_addr, &srv_addrlen);
    getnameinfo((struct sockaddr *)&srv_addr, srv_addrlen, srv_dns,
                sizeof(srv_dns), NULL, 0, 0);
    getnameinfo((struct sockaddr *)&srv_addr, srv_addrlen, srv_ip,
                sizeof(srv_ip), srv_port, sizeof(srv_port),
                NI_NUMERICHOST | NI_NUMERICSERV);
    fprintf(stderr, "server: %s (%s), port %s \n", srv_dns, srv_ip, srv_port);

    handle_client(newfd);

    fprintf(stderr, "close connection\n");
    close(newfd);
  }

  close(fd);
  freeaddrinfo(ai);
}

static void handle_client(int fd) {
  char buf[64];

  for (;;) {
    size_t count = read(fd, buf, sizeof(buf)), offset = 0;

    if (count < 0) {
      if (errno == EAGAIN || errno == EINTR) continue;
      err(EXIT_FAILURE, "read");
    }
    if (count == 0) break;

    while (offset < count) {
      ssize_t res = write(fd, buf + offset, count - offset);

      if (res < 0) {
        if (errno == EAGAIN || errno == EINTR) continue;
        err(EXIT_FAILURE, "write");
      }
      offset += res;
    }
  }
}