// raid5.c — un arreglo RAID-5 de juguete en memoria: paridad XOR,
// falla de un disco, reconstrucción y escritura pequeña.
//
// Uso: ./raid5 [DISCO_QUE_FALLA]      (0..4, por defecto 2)
//
// Cinco "discos" de NSTRIPES bloques de BS bytes. Paridad rotada al estilo
// de OSTEP (fig. 38.7): en la franja s la paridad vive en el disco
// N-1 - (s % N) y los datos siguen a su derecha, dando la vuelta.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define N        5                   // discos
#define NSTRIPES 4                   // franjas (stripes)
#define BS       8                   // bytes por bloque
#define NDATA    ((N - 1) * NSTRIPES) // bloques lógicos de datos

static unsigned char disk[N][NSTRIPES][BS];
static int failed = -1;              // disco caído (-1: ninguno)
static int nreads, nwrites;          // E/S físicas contadas

static int parity_disk(int s) { return N - 1 - (s % N); }

// Bloque lógico a -> (disco, franja)
static void map(int a, int *d, int *s) {
    *s = a / (N - 1);
    *d = (parity_disk(*s) + 1 + a % (N - 1)) % N;
}

static void xor_into(unsigned char *dst, const unsigned char *src) {
    for (int i = 0; i < BS; i++)
        dst[i] ^= src[i];
}

static void read_blk(int d, int s, unsigned char *buf) {
    if (d == failed) { fprintf(stderr, "leer disco caído\n"); exit(1); }
    memcpy(buf, disk[d][s], BS);
    nreads++;
}

static void write_blk(int d, int s, const unsigned char *buf) {
    memcpy(disk[d][s], buf, BS);
    nwrites++;
}

// Paridad de la franja s = XOR de sus N-1 bloques de datos.
static void full_parity(int s, unsigned char *p) {
    memset(p, 0, BS);
    for (int d = 0; d < N; d++)
        if (d != parity_disk(s))
            xor_into(p, disk[d][s]);
}

static void print_bits(const char *label, unsigned char c) {
    printf("  %-7s", label);
    for (int b = 7; b >= 0; b--)
        putchar(c >> b & 1 ? '1' : '0');
    printf("  (0x%02x '%c')\n", c, c >= 32 && c < 127 ? c : '.');
}

int main(int argc, char *argv[]) {
    const char *text = "Si un disco falla, la paridad XOR reconstruye sus "
                       "bloques: basta leer los otros cuatro discos de la "
                       "franja y combinarlos con XOR";
    unsigned char saved[NSTRIPES][BS], p[BS];
    int fail = argc > 1 ? atoi(argv[1]) : 2;
    if (fail < 0 || fail >= N) { fprintf(stderr, "disco 0..%d\n", N - 1); return 1; }

    // 1. Escribir los datos franja por franja (escrituras de franja completa)
    for (int a = 0; a < NDATA; a++) {
        int d, s;
        map(a, &d, &s);
        size_t off = (size_t)a * BS;
        memset(disk[d][s], '.', BS);
        if (off < strlen(text)) {
            size_t n = strlen(text) - off;
            memcpy(disk[d][s], text + off, n < BS ? n : BS);
        }
    }
    for (int s = 0; s < NSTRIPES; s++) {
        full_parity(s, p);
        memcpy(disk[parity_disk(s)][s], p, BS);
    }

    printf("Distribución (bloque lógico o P = paridad):\n       ");
    for (int d = 0; d < N; d++) printf("  disco%d", d);
    for (int s = 0; s < NSTRIPES; s++) {
        printf("\n  f%d:  ", s);
        for (int d = 0; d < N; d++) {
            if (d == parity_disk(s)) { printf("      P%d", s); continue; }
            for (int a = 0; a < NDATA; a++) {
                int dd, ss;
                map(a, &dd, &ss);
                if (dd == d && ss == s) printf("  %6d", a);
            }
        }
    }

    // 2. Paridad con bits reales: primer byte de cada bloque de la franja 0
    printf("\n\nFranja 0, primer byte de cada bloque:\n");
    for (int d = 0; d < N; d++) {
        char label[16];
        snprintf(label, sizeof label, d == parity_disk(0) ? "P0" : "D%d", d);
        print_bits(label, disk[d][0][0]);
    }

    // 3. Falla un disco: se pierde todo su contenido
    for (int s = 0; s < NSTRIPES; s++)
        memcpy(saved[s], disk[fail][s], BS);
    memset(disk[fail], 0, sizeof disk[fail]);
    failed = fail;
    printf("\nFalla el disco %d. Reconstrucción:\n", fail);
    nreads = nwrites = 0;
    int ok = 0;
    for (int s = 0; s < NSTRIPES; s++) {
        unsigned char rec[BS] = {0}, tmp[BS];
        // XOR de los N-1 sobrevivientes
        for (int d = 0; d < N; d++) {
            if (d == fail) continue;
            read_blk(d, s, tmp);
            xor_into(rec, tmp);
        }
        int same = memcmp(rec, saved[s], BS) == 0;
        ok += same;
        if (fail == parity_disk(s)) {       // era paridad: se muestra en hex
            printf("  f%d: P%d =", s, s);
            for (int i = 0; i < BS; i++) printf(" %02x", rec[i]);
        } else {
            printf("  f%d: \"%.*s\"", s, BS, rec);
        }
        printf("  %s\n", same ? "== original" : "DISTINTO");
    }
    printf("  %d/%d bloques recuperados, %d lecturas\n", ok, NSTRIPES, nreads);
    for (int s = 0; s < NSTRIPES; s++)      // disco de reemplazo
        memcpy(disk[fail][s], saved[s], BS);
    failed = -1;

    // 4. Escritura pequeña (método sustractivo): bloque lógico 6
    int a = 6, d, s;
    unsigned char old[BS], newb[BS], pold[BS];
    map(a, &d, &s);
    memcpy(newb, "NUEVO!!!", BS);
    nreads = nwrites = 0;
    int pd = parity_disk(s);
    read_blk(d, s, old);      // dato viejo
    read_blk(pd, s, pold);    // paridad vieja
    // P_nueva = P_vieja ^ D_viejo ^ D_nuevo
    xor_into(pold, old);
    xor_into(pold, newb);
    write_blk(d, s, newb);    // dato nuevo
    write_blk(pd, s, pold);   // paridad nueva
    full_parity(s, p);
    printf("\nEscritura pequeña al bloque %d:\n", a);
    printf("  dato en disco %d, paridad en disco %d\n", d, pd);
    printf("  %d lecturas + %d escrituras = %d E/S\n",
           nreads, nwrites, nreads + nwrites);
    printf("  paridad sustractiva %s recalculada\n",
           memcmp(p, disk[pd][s], BS) == 0 ? "==" : "!=");
    return 0;
}
