#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>

#define BUFSIZE 1024

/** Name of the executable (for printing messages). */
char *program_name = "<not yet set>";

/** Structure for the arguments. */
struct arguments {
    const char *port_str;
    const char *argument;
};

/** Print a usage message and exit. */
void usage(const char *msg) {
    fprintf(stderr, "Usage: %s [-p PORT] ARGUMENT\n%s\n", program_name, msg);
    exit(EXIT_FAILURE);
}

void print_message(const char *msg) {
    fprintf(stderr, "[%s] %s\n", program_name, msg);
}

/** Print an error message and exit with EXIT_FAILURE. */
void error_exit(const char *msg) {
    if (errno == 0)
        fprintf(stderr, "[%s] [ERROR] %s\n", program_name, msg);
    else
        fprintf(stderr, "[%s] [ERROR] %s - %s\n", program_name, msg,
                strerror(errno));
    exit(EXIT_FAILURE);
}

/** Parse arguments. */
void parse_arguments(int argc, char *argv[], struct arguments *args) {
    memset(args, 0, sizeof(*args));

    int opt;
    while ((opt = getopt(argc, argv, "p:")) != -1) {
        switch (opt) {
            case 'p':
                if (args->port_str)
                    usage("-p option allowed only once");
                args->port_str = optarg;
                break;
            default:
                usage("invalid option");
        }
    }
    if (args->port_str == NULL)
        args->port_str = "2020";

    if (argc - optind != 1)
        usage("invalid number of arguments");

    args->argument = argv[optind];
}

FILE *setup_connection(const char *port_str) {
    struct addrinfo hints, *ai;
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    int res = getaddrinfo("localhost", port_str, &hints, &ai);
    if (res != 0)
        error_exit("getaddrinfo()");

    int connfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
    if (connfd < 0)
        error_exit("socket()");

    if (connect(connfd, ai->ai_addr, ai->ai_addrlen) < 0)
        error_exit("connect()");

    freeaddrinfo(ai);

    return fdopen(connfd, "w+");
}

int main(int argc, char *argv[]) {
    struct arguments args;
    program_name = argv[0];

    parse_arguments(argc, argv, &args);

    FILE *fp = setup_connection(args.port_str);

    fprintf(fp, "%s", args.argument);
    fflush(fp);
    shutdown(fileno(fp), SHUT_WR);

    char *lineptr = NULL;
    size_t n = 0;
    print_message("reading response:");
    while (getline(&lineptr, &n, fp) != -1) {
        fprintf(stdout, "[%s] >>> %s", program_name, lineptr);
        fflush(stdout);
    }
    fprintf(stderr, "\n");
    print_message("exiting regularly");

    return 0;
}
