#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>

#define MAXHOSTNAMELEN 256
#define MAXSOCKTYPELEN 64
#define MAXPORTLEN 6
#define DEFAULT_PORT 80
#define INPUTBUFFERLEN 256

static void
usage(const char *progname)
{
    fprintf(stderr, "Usage: %s [-p port-number] hostname socket-type\n", progname);
}

static char    *
text_of(const struct sockaddr *address)
{
    char           *text = malloc(INET6_ADDRSTRLEN);
    const char     *result;
    struct sockaddr_in6 *address_v6;
    struct sockaddr_in *address_v4;
    if (address->sa_family == AF_INET6) {
        address_v6 = (struct sockaddr_in6 *) address;
        result = inet_ntop(AF_INET6, &address_v6->sin6_addr, text, INET6_ADDRSTRLEN);
    } else if (address->sa_family == AF_INET) {
        address_v4 = (struct sockaddr_in *) address;
        result = inet_ntop(AF_INET, &address_v4->sin_addr, text, INET_ADDRSTRLEN);
    } else {
        return ("[Unknown family address]");
    }
    if (result == NULL) {
        return ("[Internal error in address formating]");
    }
    return text;
}

int
main(int argc, char **argv)
{
    static int      verbose_flag = 0;
    static int      http_flag = 0;
    static struct option long_options[] = {
        {"verbose", no_argument, &verbose_flag, 1},
        {"http", no_argument, &http_flag, 1},
        {"port", required_argument, NULL, 0},
        {0, 0, 0, 0}
    };
    char            hostname[MAXHOSTNAMELEN + 1];
    char            socket_type[MAXSOCKTYPELEN];
    int             socktype;
    struct addrinfo hints_numeric, hints;
    struct addrinfo *addrs, *hostref;
    int             status;
    int             c;
    int             port = DEFAULT_PORT;
    char           *port_text = malloc(MAXPORTLEN);
    char           *readbuf = malloc(INPUTBUFFERLEN);
    int             sd;
    ssize_t         n;
    bool            success = false;
    FILE           *wsock;

    while (true) {
        /* getopt_long stores the option index here.  */
        int             option_index = 0;

        c = getopt_long(argc, argv, "vhp:", long_options, &option_index);

        /* Detect the end of the options.  */
        if (c == -1)
            break;

        switch (c) {
        case 0:
            /* If this option set a flag, do nothing else now.  */
            if (long_options[option_index].flag != 0)
                break;
            if (strcmp(long_options[option_index].name, "port") == 0) {
                port = strtol(optarg, NULL, 10);        /* Assume the port number is 
                                                         * in base 10 */
                if (port == 0) {
                    fprintf(stderr, "Invalid port number %s\n", optarg);
                    abort();
                }
            } else {
                fprintf(stderr, "Internal error, option %s not known\n",
                        long_options[option_index].name);
            }
            break;
        case 'v':
            verbose_flag = 1;
            break;
        case 'h':
            http_flag = 1;
            break;
        case 'p':
            port = strtol(optarg, NULL, 10);    /* Assume the port number is in base 
                                                 * 10 */
            if (port == 0) {
                fprintf(stderr, "Invalid port number %s\n", optarg);
                abort();
            }
            break;
        case '?':
            usage(argv[0]);
            break;
        default:
            abort();
        }
    }

    /* Print any remaining command line arguments (not options).  */
    if (argc - optind != 2) {
        usage(argv[0]);
        exit(EXIT_FAILURE);
    }
    strncpy(hostname, argv[optind], MAXHOSTNAMELEN);
    hostname[MAXHOSTNAMELEN] = '\0';
    sprintf(port_text, "%i", port);
    strncpy(socket_type, argv[optind + 1], MAXSOCKTYPELEN);
    if (strcmp(socket_type, "STREAM") == 0) {
        socktype = SOCK_STREAM;
    } else if (strcmp(socket_type, "DATAGRAM") == 0) {
        socktype = SOCK_DGRAM;
    } else if (strcmp(socket_type, "SEQPACKET") == 0) {
        socktype = SOCK_SEQPACKET;
    } else if (strcmp(socket_type, "RDM") == 0) {
        socktype = SOCK_RDM;
    } else {
        fprintf(stderr,
                "Unknown socket type \"%s\", must be STREAM, DATAGRAM, SEQPACKET or RDM\n",
                socket_type);
        exit(EXIT_FAILURE);
    }
    /* RFC 1123 says we must try IP addresses first */
    memset(&hints_numeric, 0, sizeof(hints_numeric));
    hints_numeric.ai_flags = AI_NUMERICHOST;
    hints_numeric.ai_socktype = socktype;
    addrs = malloc(sizeof(struct addrinfo));
    memset(&addrs, 0, sizeof(addrs));
    status = getaddrinfo(hostname, port_text, &hints_numeric, &addrs);
    if (!status) {
        hints = hints_numeric;
        if (verbose_flag) {
            fprintf(stdout, "%s is an IP address\n", hostname);
        }
    } else {
        if (status == EAI_NONAME) {
            /* Not an IP address */
            memset(&hints, 0, sizeof(hints));
            hints.ai_family = AF_UNSPEC;
            hints.ai_socktype = socktype;
            addrs = malloc(sizeof(struct addrinfo));
            status = getaddrinfo(hostname, port_text, &hints, &addrs);
            if (status) {
                fprintf(stderr, "Nothing found about host name \"%s\"\n", hostname);
                exit(EXIT_FAILURE);
            }
        } else {
            fprintf(stderr,
                    "Internal error, cannot resolve \"%s\" (error %i, may be unsupported socket type)\n",
                    hostname, status);
            exit(EXIT_FAILURE);
        }
        if (verbose_flag) {
            fprintf(stdout, "Address(es) of %s is(are):", hostname);
            fprintf(stdout, " %s ", text_of(addrs->ai_addr));
            for (hostref = addrs->ai_next; hostref != NULL;
                 hostref = hostref->ai_next) {
                fprintf(stdout, "%s ", text_of(hostref->ai_addr));
            }
            fprintf(stdout, "\n");
        }
    }
    hostref = addrs;
    while (!success && hostref != NULL) {
        status = socket(hostref->ai_family, hints.ai_socktype, hostref->ai_protocol);
        if (status < 0) {
            fprintf(stderr, "socket: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        sd = status;
        status =
            connect(sd, (const struct sockaddr *) hostref->ai_addr,
                    hostref->ai_addrlen);
        if (status != 0) {
            fprintf(stderr, "connect to %s: %s\n", text_of(hostref->ai_addr),
                    strerror(errno));
            hostref = hostref->ai_next;
        } else {
            if (verbose_flag) {
                fprintf(stdout, "\"Connection\" %s successful to %s\n", socket_type,
                        text_of(hostref->ai_addr));
                success = true;
            }
        }
    }
    if (!success) {
        fprintf(stderr, "No IP address succeeded\n");
        exit(EXIT_FAILURE);
    }
    if (http_flag) {
        wsock = fdopen(sd, "a");
        if (wsock == NULL) {
            fprintf(stderr, "fdopen: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        fprintf(wsock, "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", hostname);
        fflush(wsock);
        n = read(sd, readbuf, INPUTBUFFERLEN);
        if (verbose_flag) {
            fprintf(stdout, "%zi response bytes read\n", n);
        }
    }
    exit(EXIT_SUCCESS);
}
