// epollecho.c — servidor de eco con un ciclo de eventos (epoll), un solo hilo.
//
// Uso: ./epollecho serve [puerto]      servidor; probar con: nc 127.0.0.1 9000
//      ./epollecho test [clientes] [mensajes]
//
// En modo test el proceso abre el servidor en un puerto libre de 127.0.0.1 y
// crea con fork un cliente "lento" que se conecta y no envía nada durante
// 1 s, y luego `clientes` clientes (por omisión 50) que envían `mensajes`
// líneas cada uno (por omisión 20) y verifican el eco. El servidor atiende
// a todos sin bloquearse por el lento, y termina cuando se cierran todas
// las conexiones.
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#define MAXEV 64
#define BUFSZ 4096

// Estado de una conexión. Con un hilo por cliente esto viviría en la pila
// del hilo; en un ciclo de eventos hay que guardarlo a mano.
struct conn {
    int fd;
    char buf[BUFSZ];  // datos leídos que falta devolver
    size_t len;       // cuántos bytes hay en buf
    size_t off;       // cuántos ya se escribieron
};

static void die(const char *msg) {
    perror(msg);
    exit(1);
}

static double now(void) {
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return t.tv_sec + t.tv_nsec / 1e9;
}

static void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
        die("fcntl");
}

static int listen_on(int port) {
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0)
        die("socket");
    int one = 1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
    struct sockaddr_in a = {.sin_family = AF_INET, .sin_port = htons(port)};
    a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    if (bind(s, (struct sockaddr *)&a, sizeof a) < 0)
        die("bind");
    if (listen(s, 128) < 0)
        die("listen");
    set_nonblocking(s);
    return s;
}

// Cambiar lo que nos interesa de una conexión: leer o escribir.
static void watch(int ep, struct conn *c, unsigned events) {
    struct epoll_event ev = {.events = events, .data.ptr = c};
    if (epoll_ctl(ep, EPOLL_CTL_MOD, c->fd, &ev) < 0)
        die("epoll_ctl MOD");
}

// Escribir lo pendiente. Devuelve 1 si terminó, 0 si falta, -1 si error.
static int flush(struct conn *c) {
    while (c->off < c->len) {
        ssize_t n = write(c->fd, c->buf + c->off, c->len - c->off);
        if (n < 0 && errno == EAGAIN)
            return 0;               // el socket está lleno: seguir luego
        if (n < 0)
            return -1;
        c->off += n;
    }
    c->len = c->off = 0;
    return 1;
}

// Estadísticas del servidor.
static long waits, accepted, closed, bytes, max_ready;

// El ciclo de eventos. Si `limit` > 0, termina al cerrarse `limit` conexiones.
static void serve(int ls, long limit, long report_at) {
    int ep = epoll_create1(0);
    if (ep < 0)
        die("epoll_create1");
    struct epoll_event ev = {.events = EPOLLIN, .data.ptr = NULL};
    if (epoll_ctl(ep, EPOLL_CTL_ADD, ls, &ev) < 0)  // NULL: el socket de escucha
        die("epoll_ctl ADD");

    double t0 = now();
    struct epoll_event ready[MAXEV];
    while (limit == 0 || closed < limit) {
        int n = epoll_wait(ep, ready, MAXEV, -1);   // única espera del programa
        if (n < 0 && errno == EINTR)
            continue;
        if (n < 0)
            die("epoll_wait");
        waits++;
        if (n > max_ready)
            max_ready = n;
        for (int i = 0; i < n; i++) {
            struct conn *c = ready[i].data.ptr;
            if (c == NULL) {                        // conexiones nuevas
                int fd;
                while ((fd = accept(ls, NULL, NULL)) >= 0) {
                    set_nonblocking(fd);
                    c = calloc(1, sizeof *c);
                    c->fd = fd;
                    struct epoll_event e = {.events = EPOLLIN, .data.ptr = c};
                    if (epoll_ctl(ep, EPOLL_CTL_ADD, fd, &e) < 0)
                        die("epoll_ctl ADD");
                    accepted++;
                }
                continue;
            }
            int r = 1;
            if (ready[i].events & EPOLLOUT) {       // terminar lo pendiente
                r = flush(c);
                if (r == 1)
                    watch(ep, c, EPOLLIN);
            } else if (ready[i].events & (EPOLLIN | EPOLLHUP | EPOLLERR)) {
                ssize_t got = read(c->fd, c->buf, BUFSZ);
                if (got < 0 && errno == EAGAIN)
                    continue;                       // falsa alarma
                if (got <= 0) {
                    r = -1;                         // el cliente cerró
                } else {
                    bytes += got;
                    c->len = got;
                    r = flush(c);
                    if (r == 0)                     // escritura parcial
                        watch(ep, c, EPOLLOUT);
                }
            }
            if (r < 0) {
                close(c->fd);                       // también lo saca de epoll
                free(c);
                closed++;
                if (closed == report_at)
                    printf("servidor: %ld conexiones atendidas en %.3f s; "
                           "%ld sigue abierta sin enviar nada\n",
                           closed, now() - t0, accepted - closed);
            }
        }
    }
    close(ep);
}

static int connect_to(int port) {
    int s = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in a = {.sin_family = AF_INET, .sin_port = htons(port)};
    a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    if (s < 0 || connect(s, (struct sockaddr *)&a, sizeof a) < 0)
        die("connect");
    return s;
}

// Enviar una línea y esperar el mismo texto de vuelta (cliente bloqueante).
static int echo_once(int s, const char *msg) {
    size_t len = strlen(msg), got = 0;
    char back[256];
    if (write(s, msg, len) != (ssize_t)len)
        return -1;
    while (got < len) {
        ssize_t n = read(s, back + got, len - got);
        if (n <= 0)
            return -1;
        got += n;
    }
    return memcmp(msg, back, len) == 0 ? 0 : -1;
}

static void client(int port, int id, int msgs, int slow) {
    int s = connect_to(port);
    char msg[128];
    if (slow) {
        sleep(1);                   // conectado, pero sin enviar nada
        snprintf(msg, sizeof msg, "cliente lento: por fin escribo\n");
        _exit(echo_once(s, msg) == 0 ? 0 : 1);
    }
    for (int i = 0; i < msgs; i++) {
        snprintf(msg, sizeof msg, "cliente %d, mensaje %d\n", id, i);
        if (echo_once(s, msg) < 0)
            _exit(1);
    }
    close(s);
    _exit(0);
}

int main(int argc, char *argv[]) {
    if (argc >= 2 && strcmp(argv[1], "serve") == 0) {
        int port = argc > 2 ? atoi(argv[2]) : 9000;
        int ls = listen_on(port);
        printf("servidor de eco en 127.0.0.1:%d (Ctrl-C para salir)\n", port);
        fflush(stdout);
        serve(ls, 0, -1);
        return 0;
    }
    if (argc < 2 || strcmp(argv[1], "test") != 0) {
        fprintf(stderr, "uso: %s serve [puerto] | test [clientes] [mensajes]\n",
                argv[0]);
        return 1;
    }
    int nclients = argc > 2 ? atoi(argv[2]) : 50;
    int msgs = argc > 3 ? atoi(argv[3]) : 20;

    int ls = listen_on(0);                          // 0: puerto libre
    struct sockaddr_in a;
    socklen_t alen = sizeof a;
    getsockname(ls, (struct sockaddr *)&a, &alen);
    int port = ntohs(a.sin_port);
    printf("servidor: puerto %d, 1 cliente lento + %d clientes x %d mensajes\n",
           port, nclients, msgs);
    fflush(stdout);

    if (fork() == 0)
        client(port, -1, 0, 1);                     // el lento, primero
    for (int i = 0; i < nclients; i++)
        if (fork() == 0)
            client(port, i, msgs, 0);

    serve(ls, nclients + 1, nclients);

    int ok = 0, status;
    while (wait(&status) > 0)
        if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
            ok++;
    printf("servidor: %ld conexiones, %ld bytes, %ld llamadas a epoll_wait "
           "(hasta %ld fd listos a la vez)\n",
           accepted, bytes, waits, max_ready);
    printf("clientes: %d de %d recibieron su eco completo\n", ok, nclients + 1);
    return ok == nclients + 1 ? 0 : 1;
}
