// sink: acepta una conexión TCP, lee todo lo que llegue y cuenta los bytes.
//
//   sink <puerto> [--rcvbuf B] [--stall-ms N] [--reply] [--close-first] [--backlog N]
//                 [--no-reuseaddr] [--stay]
//
// --rcvbuf      fija SO_RCVBUF antes de listen, para que la conexión lo herede
// --stall-ms    acepta y se queda N ms sin leer: el buffer de recepción se llena
// --reply       cuando el cliente cierra su sentido, le contesta cuántos bytes recibió
// --close-first apenas acepta, cierra él primero (se queda con TIME_WAIT)
// --backlog     segundo argumento de listen
// --stay        no acepta nunca: sirve para ver la cola de conexiones
#define _POSIX_C_SOURCE 200809L
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>

static double now_seconds(void) {
    struct timespec ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
        perror("clock_gettime");
        exit(1);
    }
    return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
}

static void pause_ms(long ms) {
    struct timespec ts = {ms / 1000, (ms % 1000) * 1000000L};
    while (nanosleep(&ts, &ts) < 0) {
        if (errno != EINTR) {
            perror("nanosleep");
            exit(1);
        }
    }
}

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "uso: %s <puerto> [opciones]\n", argv[0]);
        return 2;
    }
    int rcvbuf = 0, backlog = 16, reply = 0, close_first = 0, reuse = 1, stay = 0;
    long stall_ms = 0;
    for (int i = 2; i < argc; i++) {
        if (strcmp(argv[i], "--rcvbuf") == 0 && i + 1 < argc) {
            rcvbuf = atoi(argv[++i]);
        } else if (strcmp(argv[i], "--stall-ms") == 0 && i + 1 < argc) {
            stall_ms = atol(argv[++i]);
        } else if (strcmp(argv[i], "--backlog") == 0 && i + 1 < argc) {
            backlog = atoi(argv[++i]);
        } else if (strcmp(argv[i], "--reply") == 0) {
            reply = 1;
        } else if (strcmp(argv[i], "--close-first") == 0) {
            close_first = 1;
        } else if (strcmp(argv[i], "--no-reuseaddr") == 0) {
            reuse = 0;
        } else if (strcmp(argv[i], "--stay") == 0) {
            stay = 1;
        } else {
            fprintf(stderr, "opción desconocida: %s\n", argv[i]);
            return 2;
        }
    }

    int listener = socket(AF_INET, SOCK_STREAM, 0);
    if (listener < 0) {
        perror("socket");
        return 1;
    }
    if (reuse && setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse) < 0) {
        perror("setsockopt SO_REUSEADDR");
        return 1;
    }
    if (rcvbuf > 0 && setsockopt(listener, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf) < 0) {
        perror("setsockopt SO_RCVBUF");
        return 1;
    }
    struct sockaddr_in addr = {.sin_family = AF_INET,
                               .sin_port = htons((uint16_t)atoi(argv[1])),
                               .sin_addr.s_addr = htonl(INADDR_ANY)};
    if (bind(listener, (struct sockaddr *)&addr, sizeof addr) < 0) {
        perror("bind");
        return 1;
    }
    if (listen(listener, backlog) < 0) {
        perror("listen");
        return 1;
    }
    if (stay) {
        for (;;) {
            pause_ms(1000);
        }
    }

    int fd = accept(listener, NULL, NULL);
    if (fd < 0) {
        perror("accept");
        return 1;
    }
    double start = now_seconds();
    if (stall_ms > 0) {
        pause_ms(stall_ms);
    }
    if (close_first) {
        if (close(fd) < 0) {
            perror("close");
            return 1;
        }
        printf("cerré primero\n");
        return 0;
    }

    static char buf[65536];
    long long total = 0;
    ssize_t got;
    while ((got = read(fd, buf, sizeof buf)) != 0) {
        if (got < 0) {
            if (errno == EINTR) {
                continue;
            }
            perror("read");
            return 1;
        }
        total += got;
    }
    if (reply) {
        char text[64];
        int len = snprintf(text, sizeof text, "%lld bytes\n", total);
        if (write(fd, text, (size_t)len) < 0) {
            perror("write");
            return 1;
        }
    }
    double seconds = now_seconds() - start;
    if (close(fd) < 0) {
        perror("close");
        return 1;
    }
    // Se mide aquí y no en el emisor: write termina cuando el kernel acepta los bytes en su
    // buffer, no cuando llegan.
    printf("recibidos %lld bytes en %.2f s (%.1f Mbit/s)\n", total, seconds,
           seconds > 0 ? (double)total * 8 / seconds / 1e6 : 0.0);
    return 0;
}
