#include "fs.h"
#include "fb.h"
#include "util.h"

/* ──────────────────────────────────────────────
 * RAM disk: 4MB в статическом буфере
 * ────────────────────────────────────────────── */
static uint8_t disk[FS_TOTAL_BLOCKS * FS_BLOCK_SIZE] __attribute__((aligned(4096)));
#define SUPER      ((struct fs_super*)(disk))
#define BITMAP     (disk + FS_BLOCK_SIZE)
#define INODE_BASE (disk + 17 * FS_BLOCK_SIZE)
#define INODE(idx) ((struct fs_inode*)(INODE_BASE + (idx) * FS_INODE_SIZE))
#define DATA_BLOCK(blk) (disk + (blk) * FS_BLOCK_SIZE)

static uint32_t current_dir; /* текущая директория */

/* ──────────────────────────────────────────────
 * Bitmap
 * ────────────────────────────────────────────── */
static void bitmap_set(uint32_t block, int used)
{
    uint32_t byte = block / 8;
    uint32_t bit  = block % 8;
    if(used)
        BITMAP[byte] |= (1 << bit);
    else
        BITMAP[byte] &= ~(1 << bit);
}

static int bitmap_test(uint32_t block)
{
    return (BITMAP[block / 8] >> (block % 8)) & 1;
}

uint32_t fs_alloc_block(void)
{
    for(uint32_t i = SUPER->data_start; i < SUPER->total_blocks; i++) {
        if(!bitmap_test(i)) {
            bitmap_set(i, 1);
            SUPER->free_blocks--;
            memset(DATA_BLOCK(i), 0, FS_BLOCK_SIZE);
            return i;
        }
    }
    return 0;
}

void fs_free_block(uint32_t block)
{
    if(block < SUPER->data_start || block >= SUPER->total_blocks) return;
    if(bitmap_test(block)) {
        bitmap_set(block, 0);
        SUPER->free_blocks++;
    }
}

/* ──────────────────────────────────────────────
 * Inode operations
 * ────────────────────────────────────────────── */
static int inode_write_data(uint32_t inode_num, const void *data, uint32_t size)
{
    struct fs_inode *in = INODE(inode_num);
    uint32_t written = 0;
    const uint8_t *src = data;

    for(uint32_t i = 0; i < size && i < FS_DIRECT_BLOCKS * FS_BLOCK_SIZE; ) {
        uint32_t blk_idx = i / FS_BLOCK_SIZE;
        uint32_t offset  = i % FS_BLOCK_SIZE;
        uint32_t to_write = FS_BLOCK_SIZE - offset;
        if(to_write > size - i) to_write = size - i;

        if(!in->direct[blk_idx]) {
            in->direct[blk_idx] = fs_alloc_block();
            if(!in->direct[blk_idx]) break;
        }
        memcpy(DATA_BLOCK(in->direct[blk_idx]) + offset, src + written, to_write);
        written += to_write;
        i += to_write;
    }
    in->size = written;
    return (int)written;
}

static int inode_read_data(uint32_t inode_num, void *buf, uint32_t size, uint32_t offset)
{
    struct fs_inode *in = INODE(inode_num);
    uint8_t *dst = buf;
    uint32_t read = 0;

    for(uint32_t i = offset; i < offset + size && i < in->size; ) {
        uint32_t blk_idx = i / FS_BLOCK_SIZE;
        uint32_t blk_off = i % FS_BLOCK_SIZE;
        uint32_t to_read = FS_BLOCK_SIZE - blk_off;
        uint32_t remain  = in->size - i;
        if(to_read > remain) to_read = remain;
        if(to_read > size - read) to_read = size - read;

        if(blk_idx < FS_DIRECT_BLOCKS && in->direct[blk_idx]) {
            memcpy(dst + read, DATA_BLOCK(in->direct[blk_idx]) + blk_off, to_read);
        }
        read += to_read;
        i += to_read;
    }
    return (int)read;
}

static void inode_free_blocks(uint32_t inode_num)
{
    struct fs_inode *in = INODE(inode_num);
    for(int i = 0; i < FS_DIRECT_BLOCKS; i++) {
        if(in->direct[i]) {
            fs_free_block(in->direct[i]);
            in->direct[i] = 0;
        }
    }
    in->size = 0;
}

/* ──────────────────────────────────────────────
 * Directory operations
 * ────────────────────────────────────────────── */
static int dir_find_entry(uint32_t dir_inode, const char *name)
{
    struct fs_inode *dir = INODE(dir_inode);
    uint32_t entries = dir->size / FS_DIR_ENTRY_SIZE;
    uint8_t buf[FS_BLOCK_SIZE];

    for(uint32_t i = 0; i < entries; i++) {
        uint32_t pos = i * FS_DIR_ENTRY_SIZE;
        uint32_t blk = pos / FS_BLOCK_SIZE;
        uint32_t off = pos % FS_BLOCK_SIZE;

        if(blk < FS_DIRECT_BLOCKS && dir->direct[blk]) {
            if(off == 0)
                memcpy(buf, DATA_BLOCK(dir->direct[blk]), FS_BLOCK_SIZE);
            struct fs_dirent *de = (struct fs_dirent*)(buf + off);
            if(de->inode && de->type != FS_TYPE_FREE && strcmp(de->name, name) == 0)
                return (int)de->inode;
        }
    }
    return -1;
}

static int dir_add_entry(uint32_t dir_inode, const char *name, uint32_t child_inode, uint8_t type)
{
    struct fs_inode *dir = INODE(dir_inode);
    uint32_t entries = dir->size / FS_DIR_ENTRY_SIZE;

    /* Ищем свободный слот или добавляем в конец */
    uint8_t buf[FS_BLOCK_SIZE];
    for(uint32_t i = 0; i <= entries; i++) {
        uint32_t pos = i * FS_DIR_ENTRY_SIZE;
        uint32_t blk = pos / FS_BLOCK_SIZE;
        uint32_t off = pos % FS_BLOCK_SIZE;

        if(blk >= FS_DIRECT_BLOCKS) return -1;

        if(!dir->direct[blk]) {
            dir->direct[blk] = fs_alloc_block();
            if(!dir->direct[blk]) return -1;
            memset(buf, 0, FS_BLOCK_SIZE);
        } else if(off == 0) {
            memcpy(buf, DATA_BLOCK(dir->direct[blk]), FS_BLOCK_SIZE);
        }

        struct fs_dirent *de = (struct fs_dirent*)(buf + off);
        if(de->inode == 0 || de->type == FS_TYPE_FREE) {
            memset(de, 0, FS_DIR_ENTRY_SIZE);
            int nlen = strlen(name);
            if(nlen >= FS_MAX_NAME) nlen = FS_MAX_NAME - 1;
            memcpy(de->name, name, nlen);
            de->name[nlen] = '\0';
            de->inode = child_inode;
            de->type = type;
            memcpy(DATA_BLOCK(dir->direct[blk]) + off, de, FS_DIR_ENTRY_SIZE);
            if(i >= entries)
                dir->size = (i + 1) * FS_DIR_ENTRY_SIZE;
            return 0;
        }
    }
    return -1;
}

static int dir_remove_entry(uint32_t dir_inode, const char *name)
{
    struct fs_inode *dir = INODE(dir_inode);
    uint32_t entries = dir->size / FS_DIR_ENTRY_SIZE;
    uint8_t buf[FS_BLOCK_SIZE];

    for(uint32_t i = 0; i < entries; i++) {
        uint32_t pos = i * FS_DIR_ENTRY_SIZE;
        uint32_t blk = pos / FS_BLOCK_SIZE;
        uint32_t off = pos % FS_BLOCK_SIZE;

        if(blk < FS_DIRECT_BLOCKS && dir->direct[blk]) {
            if(off == 0)
                memcpy(buf, DATA_BLOCK(dir->direct[blk]), FS_BLOCK_SIZE);
            struct fs_dirent *de = (struct fs_dirent*)(buf + off);
            if(de->inode && de->type != FS_TYPE_FREE && strcmp(de->name, name) == 0) {
                de->inode = 0;
                de->type = FS_TYPE_FREE;
                memcpy(DATA_BLOCK(dir->direct[blk]) + off, de, FS_DIR_ENTRY_SIZE);
                return 0;
            }
        }
    }
    return -1;
}

/* ──────────────────────────────────────────────
 * Public API
 * ────────────────────────────────────────────── */
void fs_init(void)
{
    memset(disk, 0, sizeof(disk));

    SUPER->magic = FS_MAGIC;
    SUPER->total_blocks = FS_TOTAL_BLOCKS;
    SUPER->data_start = 66;
    SUPER->inode_count = 512;

    /* Занимаем блок 0 (super), 1-16 (bitmap), 17-65 (inodes) */
    for(uint32_t i = 0; i < SUPER->data_start; i++)
        bitmap_set(i, 1);
    SUPER->free_blocks = FS_TOTAL_BLOCKS - SUPER->data_start;

    /* Создаём корневую директорию (inode 0) */
    struct fs_inode *root = INODE(0);
    root->mode = FS_TYPE_DIR;
    root->size = 0;
    root->parent = 0; /* корень — сам себе родитель */

    SUPER->root_inode = 0;
    current_dir = 0;

    fb_printf("fs: mounted (%d KB, %d free blocks)\n",
              (FS_TOTAL_BLOCKS * FS_BLOCK_SIZE) / 1024, SUPER->free_blocks);
}

int fs_create(const char *name, uint8_t type, uint32_t parent)
{
    if(strlen(name) >= FS_MAX_NAME) return -1;

    /* Проверяем дубликат */
    if(dir_find_entry(parent, name) >= 0) return -1;

    /* Ищем свободный inode */
    uint32_t inum = 0;
    for(uint32_t i = 1; i < SUPER->inode_count; i++) {
        struct fs_inode *in = INODE(i);
        if(in->mode == FS_TYPE_FREE) {
            inum = i;
            break;
        }
    }
    if(!inum) return -1;

    memset(INODE(inum), 0, FS_INODE_SIZE);
    INODE(inum)->mode = type;
    INODE(inum)->parent = parent;

    if(dir_add_entry(parent, name, inum, type) < 0) {
        memset(INODE(inum), 0, FS_INODE_SIZE);
        return -1;
    }
    return (int)inum;
}

int fs_unlink(const char *name, uint32_t parent)
{
    int inum = dir_find_entry(parent, name);
    if(inum < 0) return -1;

    struct fs_inode *in = INODE(inum);
    if(in->mode == FS_TYPE_DIR && in->size > 0) return -1; /* не пустая */

    inode_free_blocks(inum);
    memset(INODE(inum), 0, FS_INODE_SIZE);
    dir_remove_entry(parent, name);
    return 0;
}

int fs_find(const char *name, uint32_t parent)
{
    if(!name) return -1;
    return dir_find_entry(parent, name);
}

int fs_write(uint32_t inode, const void *data, uint32_t size)
{
    struct fs_inode *in = INODE(inode);
    if(in->mode != FS_TYPE_FILE) return -1;
    inode_free_blocks(inode);
    return inode_write_data(inode, data, size);
}

int fs_read(uint32_t inode, void *buf, uint32_t size, uint32_t offset)
{
    return inode_read_data(inode, buf, size, offset);
}

int fs_get_path(uint32_t inode, char *buf, int max)
{
    char temp[FS_MAX_PATH];
    int pos = 0;
    uint32_t cur = inode;

    while(cur != SUPER->root_inode) {
        struct fs_inode *in = INODE(cur);
        uint32_t parent = in->parent;

        /* Ищем имя этого inode в родителе */
        struct fs_inode *pin = INODE(parent);
        uint32_t entries = pin->size / FS_DIR_ENTRY_SIZE;
        uint8_t dbuf[FS_BLOCK_SIZE];
        char found[FS_MAX_NAME] = {0};

        for(uint32_t i = 0; i < entries; i++) {
            uint32_t p = i * FS_DIR_ENTRY_SIZE;
            uint32_t blk = p / FS_BLOCK_SIZE;
            uint32_t off = p % FS_BLOCK_SIZE;
            if(blk < FS_DIRECT_BLOCKS && pin->direct[blk]) {
                if(off == 0)
                    memcpy(dbuf, DATA_BLOCK(pin->direct[blk]), FS_BLOCK_SIZE);
                struct fs_dirent *de = (struct fs_dirent*)(dbuf + off);
                if(de->inode == cur && de->type != FS_TYPE_FREE) {
                    int flen = strlen(de->name);
                    if(flen >= FS_MAX_NAME) flen = FS_MAX_NAME - 1;
                    memcpy(found, de->name, flen);
                    found[flen] = '\0';
                    break;
                }
            }
        }

        if(!found[0]) break;

        int flen = strlen(found);
        if(pos + flen + 1 >= max) break;
        temp[pos++] = '/';
        memcpy(temp + pos, found, flen);
        pos += flen;
        cur = parent;
    }

    /* Реверсируем */
    if(pos == 0) {
        buf[0] = '/';
        buf[1] = '\0';
        return 0;
    }
    int out = 0;
    buf[out++] = '/';
    for(int i = pos - 1; i > 0 && out < max - 1; i--)
        buf[out++] = temp[i];
    buf[out] = '\0';
    return out;
}

int fs_stat(uint32_t inode, char *name_out, uint8_t *type_out, uint32_t *size_out)
{
    struct fs_inode *in = INODE(inode);
    if(name_out) name_out[0] = '\0';
    if(type_out) *type_out = in->mode;
    if(size_out) *size_out = in->size;
    return 0;
}

uint32_t fs_get_current(void) { return current_dir; }
void     fs_set_current(uint32_t inum) { current_dir = inum; }

uint32_t fs_get_parent(uint32_t inum) { return INODE(inum)->parent; }
uint32_t fs_get_direct(uint32_t inum, int idx) { return INODE(inum)->direct[idx]; }
uint32_t fs_get_size(uint32_t inum) { return INODE(inum)->size; }
uint8_t  fs_get_mode(uint32_t inum) { return INODE(inum)->mode; }
void    *fs_data_block(uint32_t block) { return DATA_BLOCK(block); }
uint32_t fs_total_blocks(void) { return SUPER->total_blocks; }
uint32_t fs_free_blocks(void)  { return SUPER->free_blocks; }
