// Funciones de apoyo para sockets TCP.
#define _POSIX_C_SOURCE 200809L

#include "io_util.h"

#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 <unistd.h>

int send_all(int fd, const void *buf, size_t n) {
    const char *p = buf;
    size_t left = n;
    while (left > 0) {
        // MSG_NOSIGNAL: si el otro extremo cerró, recibimos EPIPE y no la señal
        // SIGPIPE, que por defecto mataría al proceso.
        ssize_t sent = send(fd, p, left, MSG_NOSIGNAL);
        if (sent == -1) {
            if (errno == EINTR) {
                continue;
            }
            perror("send");
            return -1;
        }
        p += sent;
        left -= (size_t)sent;
    }
    return 0;
}

ssize_t recv_all(int fd, void *buf, size_t n) {
    char *p = buf;
    size_t got = 0;
    while (got < n) {
        ssize_t r = recv(fd, p + got, n - got, 0);
        if (r == -1) {
            if (errno == EINTR) {
                continue;
            }
            perror("recv");
            return -1;
        }
        if (r == 0) {
            break;  // el otro extremo cerró
        }
        got += (size_t)r;
    }
    return (ssize_t)got;
}

int tcp_listen(uint16_t port) {
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("socket");
        return -1;
    }
    int on = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) == -1) {
        perror("setsockopt");
        close_checked(fd);
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port);
    if (bind(fd, (struct sockaddr *)&addr, sizeof addr) == -1) {
        perror("bind");
        close_checked(fd);
        return -1;
    }
    if (listen(fd, 16) == -1) {
        perror("listen");
        close_checked(fd);
        return -1;
    }
    return fd;
}

int print_bound_port(int fd) {
    struct sockaddr_in addr;
    socklen_t len = sizeof addr;
    if (getsockname(fd, (struct sockaddr *)&addr, &len) == -1) {
        perror("getsockname");
        return -1;
    }
    printf("escuchando en el puerto %u\n", ntohs(addr.sin_port));
    fflush(stdout);
    return 0;
}

uint16_t parse_port(const char *text) {
    char *end;
    errno = 0;
    long value = strtol(text, &end, 10);
    if (errno != 0 || *end != '\0' || end == text || value < 0 || value > 65535) {
        fprintf(stderr, "puerto no válido: %s\n", text);
        exit(EXIT_FAILURE);
    }
    return (uint16_t)value;
}

void close_checked(int fd) {
    if (close(fd) == -1) {
        perror("close");
    }
}
