#include "filesystem.h"
#include "disk.h"

struct SuperBlock {
    unsigned int magic;
    unsigned int inode_count;
    unsigned int data_start;
    unsigned int version;
};

static struct SuperBlock sb;
static struct Inode inodes[MAX_FILES];

#define INODE_PER_SEC (512 / sizeof(struct Inode))

static int str_eq(const char *a, const char *b)
{
    while (*a && *b) {
        if (*a != *b) return 0;
        a++; b++;
    }
    return *a == *b;
}

static void str_copy(char *dst, const char *src, int max)
{
    int i = 0;
    while (*src && i < max - 1) dst[i++] = *src++;
    dst[i] = '\0';
}

static void save_inodes(void)
{
    unsigned char buf[512];
    int i, j, k;
    for (i = 0; i < MAX_FILES; i += INODE_PER_SEC) {
        for (k = 0; k < 512; k++) buf[k] = 0;
        for (j = 0; j < INODE_PER_SEC && (i + j) < MAX_FILES; j++) {
            unsigned char *src = (unsigned char *)&inodes[i + j];
            unsigned char *dst = buf + j * sizeof(struct Inode);
            for (k = 0; k < (int)sizeof(struct Inode); k++) dst[k] = src[k];
        }
        disk_write_sector(INODE_START + i / INODE_PER_SEC, buf);
    }
}

static void load_inodes(void)
{
    unsigned char buf[512];
    int i, j, k;
    for (i = 0; i < MAX_FILES; i += INODE_PER_SEC) {
        disk_read_sector(INODE_START + i / INODE_PER_SEC, buf);
        for (j = 0; j < INODE_PER_SEC && (i + j) < MAX_FILES; j++) {
            unsigned char *src = buf + j * sizeof(struct Inode);
            unsigned char *dst = (unsigned char *)&inodes[i + j];
            for (k = 0; k < (int)sizeof(struct Inode); k++) dst[k] = src[k];
        }
    }
}

void fs_init(void)
{
    unsigned char buf[512];
    struct SuperBlock *p = (struct SuperBlock *)buf;
    int i;

    disk_read_sector(FS_START_SECTOR, buf);

    if (p->magic == FS_MAGIC && p->version == FS_VERSION) {
        sb = *p;
        load_inodes();
        return;
    }

    sb.magic = FS_MAGIC;
    sb.version = FS_VERSION;
    sb.inode_count = MAX_FILES;
    sb.data_start = DATA_START;

    for (i = 0; i < MAX_FILES; i++) { inodes[i].used = 0; inodes[i].owner = 0; inodes[i].mode = 0; }

    str_copy(inodes[0].name, "/",    MAX_NAME); inodes[0].type = FS_DIR; inodes[0].used = 1; inodes[0].parent = -1; inodes[0].owner = 0;    inodes[0].mode = 0755;
    str_copy(inodes[1].name, "bin",  MAX_NAME); inodes[1].type = FS_DIR; inodes[1].used = 1; inodes[1].parent = 0;  inodes[1].owner = 0;    inodes[1].mode = 0755;
    str_copy(inodes[2].name, "etc",  MAX_NAME); inodes[2].type = FS_DIR; inodes[2].used = 1; inodes[2].parent = 0;  inodes[2].owner = 0;    inodes[2].mode = 0755;
    str_copy(inodes[3].name, "home", MAX_NAME); inodes[3].type = FS_DIR; inodes[3].used = 1; inodes[3].parent = 0;  inodes[3].owner = 0;    inodes[3].mode = 0755;
    str_copy(inodes[4].name, "root", MAX_NAME); inodes[4].type = FS_DIR; inodes[4].used = 1; inodes[4].parent = 0;  inodes[4].owner = 0;    inodes[4].mode = 0700;
    str_copy(inodes[5].name, "tmp",  MAX_NAME); inodes[5].type = FS_DIR; inodes[5].used = 1; inodes[5].parent = 0;  inodes[5].owner = 0;    inodes[5].mode = 01777;
    str_copy(inodes[6].name, "usr",  MAX_NAME); inodes[6].type = FS_DIR; inodes[6].used = 1; inodes[6].parent = 0;  inodes[6].owner = 0;    inodes[6].mode = 0755;
    str_copy(inodes[7].name, "var",  MAX_NAME); inodes[7].type = FS_DIR; inodes[7].used = 1; inodes[7].parent = 0;  inodes[7].owner = 0;    inodes[7].mode = 0755;
    str_copy(inodes[8].name, "user", MAX_NAME); inodes[8].type = FS_DIR; inodes[8].used = 1; inodes[8].parent = 3;  inodes[8].owner = 1000; inodes[8].mode = 0700;
    for (i = 0; i < 512; i++) buf[i] = 0;
    {
        unsigned char *src = (unsigned char *)&sb;
        for (i = 0; i < (int)sizeof(struct SuperBlock); i++) buf[i] = src[i];
    }
    disk_write_sector(FS_START_SECTOR, buf);
    save_inodes();
}

int fs_find_in(int parent, const char *name)
{
    int i;
    for (i = 0; i < MAX_FILES; i++) {
        if (inodes[i].used && inodes[i].parent == parent &&
            str_eq(inodes[i].name, name)) return i;
    }
    return -1;
}

int fs_create(int parent, const char *name, int type)
{
    unsigned int mode = (type == FS_DIR) ? 0755 : 0644;
    return fs_create_ex(parent, name, type, 0, mode);
}

int fs_create_ex(int parent, const char *name, int type, int owner, unsigned int mode)
{
    int i;
    if (fs_find_in(parent, name) >= 0) return -1;
    for (i = 1; i < MAX_FILES; i++) {
        if (!inodes[i].used) {
            str_copy(inodes[i].name, name, MAX_NAME);
            inodes[i].type = type;
            inodes[i].size = 0;
            inodes[i].used = 1;
            inodes[i].parent = parent;
            inodes[i].start_sector = 0;
            inodes[i].owner = owner;
            inodes[i].mode = mode & 07777;
            save_inodes();
            return i;
        }
    }
    return -1;
}

int fs_mkdir(int parent, const char *name) { return fs_create(parent, name, FS_DIR); }

int fs_delete(int parent, const char *name)
{
    int idx = fs_find_in(parent, name);
    if (idx <= 0) return -1;
    inodes[idx].used = 0;
    save_inodes();
    return 0;
}

int fs_cd(int current, const char *path)
{
    int cur = current;
    const char *p = path;

    if (*p == '/') { cur = 0; p++; }

    while (*p) {
        char name[MAX_NAME];
        int i = 0;
        while (*p && *p != '/') {
            if (i < MAX_NAME - 1) name[i++] = *p;
            p++;
        }
        name[i] = '\0';
        if (i == 0) { while (*p == '/') p++; continue; }

        if (str_eq(name, ".")) { }
        else if (str_eq(name, "..")) { if (cur > 0) cur = inodes[cur].parent; }
        else {
            int idx = fs_find_in(cur, name);
            if (idx < 0 || inodes[idx].type != FS_DIR) return -1;
            cur = idx;
        }
        while (*p == '/') p++;
    }
    return cur;
}

void fs_get_path(int idx, char *buf)
{
    int stack[32], top = 0, i;
    if (idx == 0) { buf[0] = '/'; buf[1] = '\0'; return; }
    while (idx > 0 && top < 32) { stack[top++] = idx; idx = inodes[idx].parent; }
    i = 0;
    for (top--; top >= 0; top--) {
        buf[i++] = '/';
        str_copy(buf + i, inodes[stack[top]].name, MAX_NAME);
        while (buf[i]) i++;
    }
    buf[i] = '\0';
}


static int read_inode(int idx, void *buf, int size)
{
    unsigned char sec[512];
    unsigned char *p = (unsigned char *)buf;
    unsigned int nsec, i;
    int j;

    if (idx < 0 || idx >= MAX_FILES || !inodes[idx].used) return -1;
    if (inodes[idx].type != FS_FILE) return -1;
    if (size > (int)inodes[idx].size) size = inodes[idx].size;
    if (size <= 0) return 0;

    nsec = (size + 511) / 512;
    for (i = 0; i < nsec; i++) {
        int chunk = (i == nsec - 1) ? (size - (int)i * 512) : 512;
        if (disk_read_sector(inodes[idx].start_sector + i, sec) < 0) return -1;
        for (j = 0; j < chunk; j++) p[i * 512 + j] = sec[j];
    }
    return size;
}

static int write_inode(int idx, const void *buf, int size)
{
    unsigned char sec[512];
    const unsigned char *p = (const unsigned char *)buf;
    unsigned int start, nsec, i;
    int j;

    if (idx < 0 || idx >= MAX_FILES || !inodes[idx].used) return -1;
    if (size > MAX_FILE_SIZE) size = MAX_FILE_SIZE;

    start = DATA_START;
    for (i = 1; i < MAX_FILES; i++) {
        if (inodes[i].used && i != (unsigned int)idx && inodes[i].start_sector > 0) {
            unsigned int end = inodes[i].start_sector + (inodes[i].size + 511) / 512;
            if (end > start) start = end;
        }
    }

    nsec = (size + 511) / 512;
    inodes[idx].start_sector = start;
    inodes[idx].size = size;

    for (i = 0; i < nsec; i++) {
        int chunk = (i == nsec - 1) ? (size - (int)i * 512) : 512;
        for (j = 0; j < 512; j++) sec[j] = 0;
        for (j = 0; j < chunk; j++) sec[j] = p[i * 512 + j];
        if (disk_write_sector(start + i, sec) < 0) return -1;
    }
    save_inodes();
    return size;
}


int fs_write(const char *name, const void *buf, int size)
{
    int idx = -1, i;
    for (i = 1; i < MAX_FILES; i++) {
        if (inodes[i].used && str_eq(inodes[i].name, name)) { idx = i; break; }
    }
    if (idx < 0) return -1;
    return write_inode(idx, buf, size);
}

int fs_read(const char *name, void *buf, int size)
{
    int idx = -1, i;
    for (i = 1; i < MAX_FILES; i++) {
        if (inodes[i].used && str_eq(inodes[i].name, name)) { idx = i; break; }
    }
    if (idx < 0) return -1;
    return read_inode(idx, buf, size);
}


int fs_read_at(int parent, const char *name, void *buf, int size)
{
    int idx = fs_find_in(parent, name);
    if (idx < 0) return -1;
    return read_inode(idx, buf, size);
}

int fs_write_at(int parent, const char *name, const void *buf, int size)
{
    int idx = fs_find_in(parent, name);
    if (idx < 0) return -1;
    return write_inode(idx, buf, size);
}

int fs_is_dir(int idx)
{
    if (idx < 0 || idx >= MAX_FILES) return 0;
    return inodes[idx].used && inodes[idx].type == FS_DIR;
}

int fs_size_of(int idx)
{
    if (idx < 0 || idx >= MAX_FILES || !inodes[idx].used) return 0;
    return inodes[idx].size;
}

int fs_list(int parent, char names[][MAX_NAME], int max)
{
    int i, n = 0;
    for (i = 0; i < MAX_FILES && n < max; i++) {
        if (inodes[i].used && inodes[i].parent == parent) {
            str_copy(names[n], inodes[i].name, MAX_NAME);
            n++;
        }
    }
    return n;
}

int fs_get_owner(int idx)
{
    if (idx < 0 || idx >= MAX_FILES) return -1;
    return inodes[idx].owner;
}

unsigned int fs_get_mode(int idx)
{
    if (idx < 0 || idx >= MAX_FILES) return 0;
    return inodes[idx].mode;
}

int fs_chmod(int parent, const char *name, unsigned int mode)
{
    int idx = fs_find_in(parent, name);
    if (idx < 0) return -1;
    inodes[idx].mode = mode & 07777;
    save_inodes();
    return 0;
}

int fs_chown(int parent, const char *name, int uid)
{
    int idx = fs_find_in(parent, name);
    if (idx < 0) return -1;
    inodes[idx].owner = uid;
    save_inodes();
    return 0;
}