#if 0
/*─────────────────────────────────────────────────────────────────╗
│ To the extent possible under law, Justine Tunney has waived      │
│ all copyright and related or neighboring rights to this file,    │
│ as it is written in the following disclaimers:                   │
│   • http://unlicense.org/                                        │
│   • http://creativecommons.org/publicdomain/zero/1.0/            │
╚─────────────────────────────────────────────────────────────────*/
#endif
#include "libc/calls/calls.h"
#include "libc/fmt/conv.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/linger.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/shut.h"
#include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h"
#include "third_party/getopt/getopt.internal.h"
#include "third_party/musl/netdb.h"

/**
 * @fileoverview netcat clone
 *
 * Implemented because BusyBox's netcat doesn't detect remote close and
 * lingers in the CLOSE_WAIT wait possibly due to file descriptor leaks
 *
 * Here's an example usage:
 *
 *     make -j8 o//examples/nc.com
 *     printf 'GET /\r\nHost: justine.lol\r\n\r\n' | o//examples/nc.com
 * justine.lol 80
 *
 * Once upon time we called this command "telnet"
 */

int main(int argc, char *argv[]) {
  ssize_t rc;
  size_t i, got;
  int opt, sock;
  char buf[1500];
  bool halfclose = true;
  const char *host, *port;
  struct addrinfo *ai = NULL;
  struct linger linger = {true, 1};
  struct pollfd fds[2] = {{-1, POLLIN}, {-1, POLLIN}};
  struct addrinfo hint = {AI_NUMERICSERV, AF_INET, SOCK_STREAM, IPPROTO_TCP};

  while ((opt = getopt(argc, argv, "hH")) != -1) {
    switch (opt) {
      case 'H':
        halfclose = false;
        break;
      case 'h':
        tinyprint(1, "Usage: ", argv[0], " [-hH] IP PORT\n", NULL);
        exit(0);
      default:
        fprintf(stderr, "bad option %d\n", opt);
        exit(1);
    }
  }
  if (argc - optind != 2) {
    fputs("missing args\n", stderr);
    exit(1);
  }
  host = argv[optind + 0];
  port = argv[optind + 1];

  if ((rc = getaddrinfo(host, port, &hint, &ai))) {
    tinyprint(2, host, ": ", gai_strerror(rc), "\n", NULL);
    exit(1);
  }

  if ((sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) {
    perror("socket");
    exit(1);
  }

  if (setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)) == -1) {
    perror("SO_LINGER");
    exit(1);
  }

  if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
    perror(host);
    exit(1);
  }

  fds[0].fd = 0;
  fds[1].fd = sock;
  for (;;) {
    fds[0].revents = 0;
    fds[1].revents = 0;
    if (poll(fds, ARRAYLEN(fds), -1) == -1) {
      perror("poll");
      exit(1);
    }

    if (fds[0].revents & (POLLIN | POLLERR | POLLHUP)) {
      if ((rc = read(0, buf, 1400)) == -1) {
        perror("read(stdin)");
        exit(1);
      }
      if (!(got = rc)) {
        if (halfclose) {
          shutdown(sock, SHUT_WR);
        }
        fds[0].fd = -1;
      }
      for (i = 0; i < got; i += rc) {
        if ((rc = write(sock, buf + i, got - i)) == -1) {
          perror("write(sock)");
          exit(1);
        }
      }
    }

    if (fds[1].revents & (POLLIN | POLLERR | POLLHUP)) {
      if ((rc = read(sock, buf, 1500)) == -1) {
        perror("read(sock)");
        exit(1);
      }
      if (!(got = rc)) {
        break;
      }
      for (i = 0; i < got; i += rc) {
        if ((rc = write(1, buf + i, got - i)) == -1) {
          perror("write(stdout)");
          exit(1);
        }
      }
    }
  }

  if (close(sock) == -1) {
    perror("close");
    exit(1);
  }

  freeaddrinfo(ai);
  return 0;
}
