// vsfs.c: un sistema de archivos muy simple (VSFS) sobre un archivo imagen.
//
// Formato: 64 bloques de 4 KB.
//   0 superbloque | 1 bitmap de inodos | 2 bitmap de datos |
//   3-7 tabla de inodos (80 inodos de 256 B) | 8-63 bloques de datos
// Sin caché: cada lectura o escritura de bloque es una E/S y se cuenta.
//
// Uso:
//   vsfs img mkfs
//   vsfs img mkdir /ruta
//   vsfs img create /ruta nbytes      (crea y escribe nbytes)
//   vsfs img read /ruta               (abre y lee todo el archivo)
//   vsfs img ls /ruta
//   vsfs img stat inum
// Con -v antes de img se imprime cada E/S.

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BSIZE      4096
#define NBLOCKS    64
#define IBMAP      1
#define DBMAP      2
#define INODESTART 3
#define DATASTART  8
#define ISIZE      256
#define IPB        (BSIZE / ISIZE)              // 16 inodos por bloque
#define NINODES    ((DATASTART - INODESTART) * IPB)
#define NDATA      (NBLOCKS - DATASTART)
#define NDIRECT    12
#define ROOTINO    2                            // como en Unix
#define DIRSIZ     12
#define MAGIC      0x56534653

enum { T_FREE = 0, T_FILE = 1, T_DIR = 2 };

struct superblock {
    uint32_t magic, nblocks, ninodes, inodestart, datastart;
};

struct dinode {                                 // 256 bytes en disco
    uint16_t type, nlink;
    uint32_t size;
    uint32_t addrs[NDIRECT];                    // 0 = sin bloque
    uint8_t pad[ISIZE - 8 - 4 * NDIRECT];
};

struct dirent {                                 // 16 bytes
    uint32_t inum;
    char name[DIRSIZ];
};

static int fd, verbose, nreads, nwrites;

static const char *region(uint32_t b) {
    static char s[32];
    if (b == 0) return "superbloque";
    if (b == IBMAP) return "bitmap de inodos";
    if (b == DBMAP) return "bitmap de datos";
    if (b < DATASTART) {
        snprintf(s, sizeof s, "inodos %u-%u", (b - INODESTART) * IPB,
                 (b - INODESTART) * IPB + IPB - 1);
        return s;
    }
    return "datos";
}

static void bread(uint32_t b, void *buf) {
    if (pread(fd, buf, BSIZE, (off_t)b * BSIZE) != BSIZE) { perror("pread"); exit(1); }
    nreads++;
    if (verbose) printf("    leer      bloque %2u  (%s)\n", b, region(b));
}

static void bwrite(uint32_t b, const void *buf) {
    if (pwrite(fd, buf, BSIZE, (off_t)b * BSIZE) != BSIZE) { perror("pwrite"); exit(1); }
    nwrites++;
    if (verbose) printf("    escribir  bloque %2u  (%s)\n", b, region(b));
}

// Inodo inum: está en el bloque INODESTART + inum/IPB, posición inum%IPB.
static void iread(uint32_t inum, struct dinode *ip) {
    uint8_t buf[BSIZE];
    bread(INODESTART + inum / IPB, buf);
    memcpy(ip, buf + (inum % IPB) * ISIZE, ISIZE);
}

static void iwrite(uint32_t inum, const struct dinode *ip) {
    uint8_t buf[BSIZE];
    bread(INODESTART + inum / IPB, buf);         // leer-modificar-escribir
    memcpy(buf + (inum % IPB) * ISIZE, ip, ISIZE);
    bwrite(INODESTART + inum / IPB, buf);
}

// Busca un bit en 0 en el bitmap, lo pone en 1 y devuelve su índice.
static int bitmap_alloc(uint32_t bmap, int n) {
    uint8_t buf[BSIZE];
    bread(bmap, buf);
    for (int i = 0; i < n; i++) {
        if ((buf[i / 8] & (1 << (i % 8))) == 0) {
            buf[i / 8] |= 1 << (i % 8);
            bwrite(bmap, buf);
            return i;
        }
    }
    return -1;
}

static int balloc(void) {
    int i = bitmap_alloc(DBMAP, NDATA);
    return i < 0 ? -1 : DATASTART + i;
}

// Busca name en el directorio dp; devuelve el inum o 0.
static uint32_t dirlookup(struct dinode *dp, const char *name) {
    struct dirent de[BSIZE / sizeof(struct dirent)];
    uint32_t nent = dp->size / sizeof(struct dirent);
    for (uint32_t i = 0; i < nent; i++) {
        uint32_t k = i % (BSIZE / sizeof(struct dirent));
        if (k == 0) bread(dp->addrs[i / (BSIZE / sizeof(struct dirent))], de);
        if (de[k].inum && strncmp(de[k].name, name, DIRSIZ) == 0)
            return de[k].inum;
    }
    return 0;
}

// Recorre la ruta desde la raíz. Si last != NULL, se detiene en el
// directorio padre y copia el último componente en last.
static uint32_t namei(const char *path, char *last) {
    char copy[256], *save, *tok, *next;
    uint32_t inum = ROOTINO;
    struct dinode d;

    snprintf(copy, sizeof copy, "%s", path);
    tok = strtok_r(copy, "/", &save);
    while (tok) {
        next = strtok_r(NULL, "/", &save);
        if (last && next == NULL) {             // último componente
            snprintf(last, DIRSIZ + 1, "%s", tok);
            return inum;
        }
        iread(inum, &d);
        if (d.type != T_DIR) return 0;
        uint32_t child = dirlookup(&d, tok);
        if (verbose) printf("  en el inodo %u: \"%s\" -> inodo %u\n", inum, tok, child);
        if (child == 0) return 0;
        inum = child;
        tok = next;
    }
    return inum;
}

// Agrega (name, inum) al final del directorio dinum, cuyo inodo es *dp.
static int diradd(uint32_t dinum, struct dinode *dp, const char *name, uint32_t inum) {
    struct dirent de[BSIZE / sizeof(struct dirent)];
    uint32_t bn = dp->size / BSIZE;
    if (bn >= NDIRECT) return -1;
    if (dp->size % BSIZE == 0) {                // hace falta un bloque nuevo
        int b = balloc();
        if (b < 0) return -1;
        dp->addrs[bn] = b;
        memset(de, 0, sizeof de);
    } else {
        bread(dp->addrs[bn], de);
    }
    struct dirent *e = &de[(dp->size % BSIZE) / sizeof(struct dirent)];
    e->inum = inum;
    strncpy(e->name, name, DIRSIZ);
    bwrite(dp->addrs[bn], de);
    dp->size += sizeof(struct dirent);
    iwrite(dinum, dp);
    return 0;
}

// Crea un archivo o directorio vacío en path; devuelve su inum.
static uint32_t create(const char *path, int type) {
    char name[DIRSIZ + 1];
    uint32_t parent = namei(path, name);
    struct dinode dp;
    if (parent == 0) { fprintf(stderr, "no existe el directorio padre\n"); exit(1); }
    iread(parent, &dp);
    if (dirlookup(&dp, name)) { fprintf(stderr, "%s ya existe\n", path); exit(1); }
    int inum = bitmap_alloc(IBMAP, NINODES);
    if (inum < 0) { fprintf(stderr, "no hay inodos libres\n"); exit(1); }
    struct dinode ip = { .type = type, .nlink = 1 };
    if (type == T_DIR) {                        // un bloque con "." y ".."
        struct dirent de[BSIZE / sizeof(struct dirent)] = {{ inum, "." }, { parent, ".." }};
        int b = balloc();
        if (b < 0) { fprintf(stderr, "disco lleno\n"); exit(1); }
        bwrite(b, de);
        ip.addrs[0] = b;
        ip.size = 2 * sizeof(struct dirent);
        ip.nlink = 2;
    }
    iwrite(inum, &ip);
    if (type == T_DIR) dp.nlink++;              // por el ".." del hijo
    if (diradd(parent, &dp, name, inum) < 0) { fprintf(stderr, "directorio lleno\n"); exit(1); }
    return inum;
}

static void phase(const char *what) {
    static int r0, w0;
    if (verbose && what == NULL)
        printf("    -> %d lecturas, %d escrituras\n", nreads - r0, nwrites - w0);
    if (what && verbose) printf("%s\n", what);
    r0 = nreads;
    w0 = nwrites;
}

static void do_mkfs(void) {
    uint8_t buf[BSIZE] = {0};
    for (int b = 0; b < NBLOCKS; b++) bwrite(b, buf);
    struct superblock sb = { MAGIC, NBLOCKS, NINODES, INODESTART, DATASTART };
    memcpy(buf, &sb, sizeof sb);
    bwrite(0, buf);
    memset(buf, 0, BSIZE);
    buf[0] = 0x07;                              // inodos 0, 1 (reservados) y 2 (raíz)
    bwrite(IBMAP, buf);
    buf[0] = 0x01;                              // bloque de datos 0 = bloque 8
    bwrite(DBMAP, buf);
    struct dinode root = { .type = T_DIR, .nlink = 2, .size = 2 * sizeof(struct dirent) };
    root.addrs[0] = DATASTART;
    iwrite(ROOTINO, &root);
    struct dirent de[BSIZE / sizeof(struct dirent)] = {{ ROOTINO, "." }, { ROOTINO, ".." }};
    bwrite(DATASTART, de);
    printf("mkfs: %d bloques de %d B, %d inodos de %d B, %d bloques de datos\n",
           NBLOCKS, BSIZE, NINODES, ISIZE, NDATA);
}

static void do_write(uint32_t inum, int nbytes) {
    uint8_t buf[BSIZE], iblk[BSIZE];
    uint32_t ib = INODESTART + inum / IPB, io = (inum % IPB) * ISIZE;
    struct dinode ip;
    for (int off = 0, i = 0; off < nbytes; off += BSIZE, i++) {
        char what[64];
        snprintf(what, sizeof what, "write() del bloque %d del archivo", i);
        phase(what);
        if (i >= NDIRECT) { fprintf(stderr, "archivo demasiado grande\n"); exit(1); }
        bread(ib, iblk);                        // leer el inodo
        memcpy(&ip, iblk + io, ISIZE);
        int b = balloc();                       // leer y escribir el bitmap
        if (b < 0) { fprintf(stderr, "disco lleno\n"); exit(1); }
        memset(buf, 'a' + i % 26, BSIZE);
        bwrite(b, buf);                         // escribir los datos
        ip.addrs[i] = b;
        ip.size = (uint32_t)(off + BSIZE < nbytes ? off + BSIZE : nbytes);
        memcpy(iblk + io, &ip, ISIZE);
        bwrite(ib, iblk);                       // escribir el inodo
        phase(NULL);
    }
}

static void do_read(const char *path) {
    uint8_t buf[BSIZE];
    struct dinode ip;
    phase("open()");
    uint32_t inum = namei(path, NULL);
    if (inum == 0) { fprintf(stderr, "%s: no existe\n", path); exit(1); }
    iread(inum, &ip);
    phase(NULL);
    for (uint32_t i = 0; i * BSIZE < ip.size; i++) {
        phase("read()");
        bread(ip.addrs[i], buf);                // el inodo ya está en memoria
        phase(NULL);
    }
    printf("%s: inodo %u, %u bytes\n", path, inum, ip.size);
}

static void do_ls(const char *path) {
    struct dinode dp, ip;
    struct dirent de[BSIZE / sizeof(struct dirent)];
    uint32_t inum = namei(path, NULL);
    if (inum == 0) { fprintf(stderr, "%s: no existe\n", path); exit(1); }
    iread(inum, &dp);
    for (uint32_t i = 0; i < dp.size / sizeof(struct dirent); i++) {
        uint32_t k = i % (BSIZE / sizeof(struct dirent));
        if (k == 0) bread(dp.addrs[i / (BSIZE / sizeof(struct dirent))], de);
        iread(de[k].inum, &ip);
        printf("%-12.12s inodo %2u  %-4s %6u B\n", de[k].name, de[k].inum,
               ip.type == T_DIR ? "dir" : "arch", ip.size);
    }
}

static void do_stat(uint32_t inum) {
    struct dinode ip;
    if (inum >= NINODES) { fprintf(stderr, "inodo fuera de rango\n"); exit(1); }
    iread(inum, &ip);
    printf("inodo %u: bloque %u, byte %u del bloque\n", inum,
           INODESTART + inum / IPB, (inum % IPB) * ISIZE);
    printf("  tipo %s, nlink %u, tamaño %u B\n",
           ip.type == T_DIR ? "dir" : ip.type == T_FILE ? "arch" : "libre",
           ip.nlink, ip.size);
    printf("  bloques:");
    for (int i = 0; i < NDIRECT && ip.addrs[i]; i++) printf(" %u", ip.addrs[i]);
    printf("\n");
}

int main(int argc, char *argv[]) {
    int a = 1;
    if (a < argc && strcmp(argv[a], "-v") == 0) { verbose = 1; a++; }
    if (argc - a < 2) {
        fprintf(stderr, "uso: %s [-v] img mkfs|mkdir|create|read|ls|stat ...\n", argv[0]);
        return 1;
    }
    const char *img = argv[a], *cmd = argv[a + 1], *arg = argc - a > 2 ? argv[a + 2] : "/";
    int mk = strcmp(cmd, "mkfs") == 0;
    fd = open(img, mk ? O_RDWR | O_CREAT | O_TRUNC : O_RDWR, 0644);
    if (fd < 0) { perror(img); return 1; }
    if (!mk) {
        struct superblock sb;
        if (pread(fd, &sb, sizeof sb, 0) != sizeof sb || sb.magic != MAGIC) {
            fprintf(stderr, "%s: no es una imagen vsfs\n", img);
            return 1;
        }
    }

    if (mk) {
        do_mkfs();
    } else if (strcmp(cmd, "mkdir") == 0) {
        phase("mkdir()");
        create(arg, T_DIR);
        phase(NULL);
    } else if (strcmp(cmd, "create") == 0 && argc - a > 3) {
        phase("create()");
        uint32_t inum = create(arg, T_FILE);
        phase(NULL);
        do_write(inum, atoi(argv[a + 3]));
        printf("%s: inodo %u\n", arg, inum);
    } else if (strcmp(cmd, "read") == 0) {
        do_read(arg);
    } else if (strcmp(cmd, "ls") == 0) {
        do_ls(arg);
    } else if (strcmp(cmd, "stat") == 0) {
        do_stat(atoi(arg));
    } else {
        fprintf(stderr, "comando desconocido: %s\n", cmd);
        return 1;
    }
    if (verbose) printf("total: %d lecturas, %d escrituras\n", nreads, nwrites);
    close(fd);
    return 0;
}
