// Servidor UDP que muestra qué socket recibe cada datagrama.
//   udp_demux <puerto>                       un solo socket, sin connect
//   udp_demux <puerto> <ip> <puerto_remoto>  además, un socket conectado a ese cliente
// Contesta a cada datagrama con el número de socket que lo recibió.
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define MAX_DATAGRAM 1500

static int parse_port(const char *text) {
    int port = atoi(text);
    if (port <= 0 || port > 65535) {
        fprintf(stderr, "puerto no válido: %s\n", text);
        exit(EXIT_FAILURE);
    }
    return port;
}

// Crea un socket UDP ligado al puerto. SO_REUSEADDR permite dos sockets en el mismo puerto.
static int bound_socket(int port) {
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    int yes = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    struct sockaddr_in local;
    memset(&local, 0, sizeof local);
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = htonl(INADDR_ANY);
    local.sin_port = htons((uint16_t)port);
    if (bind(fd, (struct sockaddr *)&local, sizeof local) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }
    return fd;
}

// Recibe un datagrama de fd, informa quién lo mandó y contesta.
static void serve_one(int fd) {
    char data[MAX_DATAGRAM];
    struct sockaddr_in peer;
    socklen_t peer_len = sizeof peer;
    ssize_t got = recvfrom(fd, data, sizeof data - 1, 0, (struct sockaddr *)&peer, &peer_len);
    if (got == -1) {
        perror("recvfrom");
        return;
    }
    data[got] = '\0';
    char ip[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &peer.sin_addr, ip, sizeof ip) == NULL) {
        perror("inet_ntop");
        return;
    }
    printf("socket %d  de %s:%u  \"%s\"\n", fd, ip, ntohs(peer.sin_port), data);

    char reply[64];
    int len = snprintf(reply, sizeof reply, "recibido en el socket %d", fd);
    if (sendto(fd, reply, (size_t)len, 0, (struct sockaddr *)&peer, peer_len) == -1) {
        perror("sendto");
    }
}

int main(int argc, char *argv[]) {
    if (argc != 2 && argc != 4) {
        fprintf(stderr, "uso: %s <puerto> [<ip> <puerto_remoto>]\n", argv[0]);
        return EXIT_FAILURE;
    }
    if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
        perror("setvbuf");
    }
    int port = parse_port(argv[1]);
    struct pollfd watched[2] = {{.fd = bound_socket(port), .events = POLLIN}};
    nfds_t count = 1;

    if (argc == 4) {
        // connect en UDP no manda nada: solo le dice al kernel de quién es este socket.
        struct sockaddr_in remote;
        memset(&remote, 0, sizeof remote);
        remote.sin_family = AF_INET;
        remote.sin_port = htons((uint16_t)parse_port(argv[3]));
        if (inet_pton(AF_INET, argv[2], &remote.sin_addr) != 1) {
            fprintf(stderr, "dirección no válida: %s\n", argv[2]);
            return EXIT_FAILURE;
        }
        int fd = bound_socket(port);
        if (connect(fd, (struct sockaddr *)&remote, sizeof remote) == -1) {
            perror("connect");
            return EXIT_FAILURE;
        }
        watched[1] = (struct pollfd){.fd = fd, .events = POLLIN};
        count = 2;
    }

    for (;;) {
        if (poll(watched, count, -1) == -1) {
            perror("poll");
            return EXIT_FAILURE;
        }
        for (nfds_t i = 0; i < count; i++) {
            if (watched[i].revents & POLLIN) {
                serve_one(watched[i].fd);
            }
        }
    }
}
