#include "vfs.h"
#include <video/render.h>
#include <arch/x86_64/e9.h>
#include "mp/spinlock.h"

static spinlock_t s_vfs_lock = SPINLOCK_INIT;
static vfs_file_t vfs_fd_table[VFS_MAX_FDS];

fd_t VFS_Open_internal(const char* path)
{
    uint32_t ino = ext2_resolve_path(path);
    if (ino == 0)
        return -1;

    // find free fd
    for (int i = 4; i < VFS_MAX_FDS; i++)
    {
        if (!vfs_fd_table[i].used)
        {
            vfs_fd_table[i].used = true;
            vfs_fd_table[i].type = VFS_NODE_EXT2;
            vfs_fd_table[i].offset = 0;
            vfs_fd_table[i].ext2.inode_num = ino;

            if (!ext2_read_inode(ino, &vfs_fd_table[i].ext2.inode))
                return -1;

            return i;
        }
    }

    return -1;
}

fd_t VFS_Open(const char* path) {
    uint64_t flags;
    spinlock_acquire_irqsave(&s_vfs_lock, &flags);
    fd_t fd = VFS_Open_internal(path);
    spinlock_release_irqrestore(&s_vfs_lock, flags);
    return fd;
}

int VFS_Close_internal(fd_t fd)
{
    if (fd < 0 || fd >= VFS_MAX_FDS)
        return -1;

    vfs_fd_table[fd].used = false;
    return 0;
}

int VFS_Close(fd_t fd) {
    uint64_t flags;
    spinlock_acquire_irqsave(&s_vfs_lock, &flags);
    int resp = VFS_Close_internal(fd);
    spinlock_release_irqrestore(&s_vfs_lock, flags);
    return resp;
}

int VFS_Read(fd_t fd, uint8_t* buf, size_t size)
{
    if (fd < 0 || fd >= VFS_MAX_FDS)
        return -1;

    vfs_file_t* file = &vfs_fd_table[fd];

    if (!file->used)
        return -1;

    switch (file->type)
    {
    case VFS_NODE_EXT2:
    {
        uint32_t file_size = file->ext2.inode.i_size;

        if (file->offset >= file_size)
            return 0;

        // clamp read
        if (file->offset + size > file_size)
            size = file_size - file->offset;

        // naive: read whole file then slice
        uint8_t tmp[file_size];
        if (!ext2_read_file(&file->ext2.inode, tmp))
            return -1;

        for (size_t i = 0; i < size; i++)
            buf[i] = tmp[file->offset + i];

        file->offset += size;
        return size;
    }

    default:
        return -1;
    }
}

int VFS_Write(fd_t file, uint8_t* data, size_t size)
{
    switch (file)
    {
    case VFS_FD_STDIN:
        return 0;

    case VFS_FD_STDOUT:
        for (size_t i = 0; i < size; i++)
            putchar(data[i]);
        return size;
    case VFS_FD_STDERR:
        for (size_t i = 0; i < size; i++)
            putchar(data[i]);
        return size;

    case VFS_FD_DEBUG:
        for (size_t i = 0; i < size; i++)
            e9_putc(data[i]);
        return size;

    default:
        break;
    }

    if (file < 0 || file >= VFS_MAX_FDS)
        return -1;

    vfs_file_t* f = &vfs_fd_table[file];

    if (!f->used)
        return -1;

    if (f->type == VFS_NODE_EXT2)
    {
        if (!ext2_write_file(
                &f->ext2.inode,
                f->ext2.inode_num,
                data,
                size,
                EXT2_WRITE_APPEND))
            return -1;

        return size;
    }

    return -1;
}