#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>
#include <thread>
#include <atomic>

static const int backlog = 10;

static void handle_client(int fd);

static std::atomic<size_t> g_count(0);

int main(int argc, char **argv) {
  struct addrinfo *ai;
  struct addrinfo hints = {0};
  int res, fd;

  if (argc != 2) errx(1, "usage: %s port", argv[0]);
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;

  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 addr;
    socklen_t addrlen = sizeof(addr);
    char ip[128] = {0};
    int newfd;

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

    getnameinfo((struct sockaddr *)&addr, addrlen, ip, sizeof(ip), NULL, 0,
                NI_NUMERICHOST);
    fprintf(stderr, "new connection from %s\n", ip);

    std::thread thr(handle_client, newfd);
    thr.detach();
  }

  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;

    g_count += count;
    printf("total input: %zd bytes\n", (size_t)g_count);

    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;
    }
  }

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