// cidr.c — calcula los datos de un prefijo IPv4 y lo divide en subredes iguales.
//
//   ./cidr 10.20.0.0/22       muestra máscara, red, broadcast y rango de hosts
//   ./cidr 10.20.0.0/22 4     lo divide en 4 subredes (una potencia de 2)
#define _POSIX_C_SOURCE 200809L
#include <arpa/inet.h>
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Máscara de un prefijo de largo len. El caso 0 va aparte porque desplazar un
// entero de 32 bits en 32 posiciones no está definido en C.
static uint32_t mask_of(int len) {
    return len == 0 ? 0 : UINT32_MAX << (32 - len);
}

// Convierte "a.b.c.d/n" a una dirección en orden de host y un largo.
static int parse_prefix(const char *text, uint32_t *addr, int *len) {
    char buffer[32];
    if (strlen(text) >= sizeof buffer) {
        return -1;
    }
    strcpy(buffer, text);

    char *slash = strchr(buffer, '/');
    if (slash == NULL) {
        return -1;
    }
    *slash = '\0';

    char *end;
    errno = 0;
    long value = strtol(slash + 1, &end, 10);
    if (errno != 0 || end == slash + 1 || *end != '\0' || value < 0 || value > 32) {
        return -1;
    }

    struct in_addr parsed;
    if (inet_pton(AF_INET, buffer, &parsed) != 1) {
        return -1;
    }
    *addr = ntohl(parsed.s_addr);
    *len = (int)value;
    return 0;
}

// Escribe una dirección en orden de host como texto. Devuelve un buffer estático
// distinto en cada una de las cuatro llamadas más recientes.
static const char *ip_text(uint32_t addr) {
    static char buffers[4][INET_ADDRSTRLEN];
    static int next = 0;
    char *out = buffers[next];
    next = (next + 1) % 4;

    struct in_addr raw = {.s_addr = htonl(addr)};
    if (inet_ntop(AF_INET, &raw, out, INET_ADDRSTRLEN) == NULL) {
        perror("inet_ntop");
        exit(1);
    }
    return out;
}

// Direcciones que se pueden asignar a hosts. En /31 (RFC 3021) son las dos, y en
// /32 es la única; en lo demás se restan la de red y la de broadcast.
static uint64_t host_count(int len) {
    if (len == 32) {
        return 1;
    }
    if (len == 31) {
        return 2;
    }
    return (UINT64_C(1) << (32 - len)) - 2;
}

static void print_info(uint32_t addr, int len) {
    uint32_t mask = mask_of(len);
    uint32_t network = addr & mask;
    uint32_t broadcast = network | ~mask;
    uint32_t first = len >= 31 ? network : network + 1;
    uint32_t last = len >= 31 ? broadcast : broadcast - 1;

    if (addr != network) {
        printf("aviso      %s tiene bits de host encendidos\n", ip_text(addr));
    }
    printf("prefijo    %s/%d\n", ip_text(network), len);
    printf("máscara    %s\n", ip_text(mask));
    printf("total      %" PRIu64 " direcciones\n", UINT64_C(1) << (32 - len));
    if (len < 31) {
        printf("broadcast  %s\n", ip_text(broadcast));
    }
    printf("hosts      %s - %s (%" PRIu64 ")\n", ip_text(first), ip_text(last),
           host_count(len));
}

static int split(uint32_t addr, int len, long parts) {
    if (parts > (1L << 32)) {
        fprintf(stderr, "cidr: /%d no alcanza para %ld subredes\n", len, parts);
        return 1;
    }
    int extra = 0;
    while ((1L << extra) < parts) {
        extra++;
    }
    if ((1L << extra) != parts) {
        fprintf(stderr, "cidr: %ld no es una potencia de 2\n", parts);
        return 1;
    }
    if (len + extra > 32) {
        fprintf(stderr, "cidr: /%d no alcanza para %ld subredes\n", len, parts);
        return 1;
    }

    int new_len = len + extra;
    uint32_t network = addr & mask_of(len);
    uint64_t step = UINT64_C(1) << (32 - new_len);
    printf("%s/%d en %ld subredes de /%d (%" PRIu64 " direcciones cada una):\n",
           ip_text(network), len, parts, new_len, step);
    for (long i = 0; i < parts; i++) {
        uint32_t sub = network + (uint32_t)((uint64_t)i * step);
        uint32_t broadcast = sub | ~mask_of(new_len);
        uint32_t first = new_len >= 31 ? sub : sub + 1;
        uint32_t last = new_len >= 31 ? broadcast : broadcast - 1;
        char label[32];
        int written = snprintf(label, sizeof label, "%s/%d", ip_text(sub), new_len);
        if (written < 0 || (size_t)written >= sizeof label) {
            fprintf(stderr, "cidr: error al formatear la subred\n");
            return 1;
        }
        printf("  %-18s hosts %s - %s (%" PRIu64 ")\n", label, ip_text(first), ip_text(last),
               host_count(new_len));
    }
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 2 && argc != 3) {
        fprintf(stderr, "uso: %s a.b.c.d/n [subredes]\n", argv[0]);
        return 2;
    }

    uint32_t addr;
    int len;
    if (parse_prefix(argv[1], &addr, &len) != 0) {
        fprintf(stderr, "cidr: prefijo inválido: %s\n", argv[1]);
        return 2;
    }

    if (argc == 2) {
        print_info(addr, len);
        return 0;
    }

    char *end;
    errno = 0;
    long parts = strtol(argv[2], &end, 10);
    if (errno != 0 || end == argv[2] || *end != '\0' || parts < 1) {
        fprintf(stderr, "cidr: número de subredes inválido: %s\n", argv[2]);
        return 2;
    }
    return split(addr, len, parts);
}
