#define MODULE "vfs"

#include "fs/VFS.h"
#include "log/Log.h"
#include "std/errno.h"
#include "std/libgen.h"
#include "std/stdlib.h"
#include "std/string.h"

static VFS::Node* vfs_root;

int VFS::would_block(Node* node)
{
    if (!node) { return 0; }
    if (!node->block_func) { return 0; }
    return node->block_func(node);
}

ssize_t VFS::read(Node* node, size_t offset, size_t length, char* buffer)
{
    if (!node)
    {
        kwarnln("read() failed: trying to read from nullptr");
        return -1;
    }
    if (node->type == VFS_DIRECTORY)
    {
        kwarnln("read() failed: is a directory");
        return -EISDIR;
    }
    if (!node->read_func)
    {
        kwarnln("read() failed: the chosen node doesn't support reading");
        return -1;
    }

    return node->read_func(node, offset, length, buffer);
}

ssize_t VFS::write(Node* node, size_t offset, size_t length, const char* buffer)
{
    if (!node)
    {
        kwarnln("write() failed: trying to write to nullptr");
        return -1;
    }
    if (node->type == VFS_DIRECTORY)
    {
        kwarnln("write() failed: is a directory");
        return -EISDIR;
    }
    if (!node->write_func)
    {
        kwarnln("write() failed: the chosen node doesn't support writing");
        return -1;
    }

    return node->write_func(node, offset, length, buffer);
}

void VFS::mount_root(Node* root)
{
    if (!root)
    {
        kwarnln("mount_root() failed: attempted to mount nullptr");
        return;
    }
    if (vfs_root)
    {
        kwarnln("mount_root() failed: root filesystem already mounted");
        return;
    }
    kinfoln("mounting node '%s' as vfs root", root->name);
    vfs_root = root;
}

VFS::Node* VFS::root()
{
    return vfs_root;
}

VFS::Node* VFS::resolve_path(const char* filename, Node* root)
{
    if (!root) root = vfs_root;

    if (strlen(filename) == 0) return 0;
    if (*filename == '/') // Absolute path.
    {
        filename++;
        root = vfs_root;
    }

    Node* current_node = root;

    while (true)
    {
        while (*filename == '/') { filename++; }
        if (*filename == 0) { return current_node; }

        size_t path_section_size = 0;
        while (filename[path_section_size] && filename[path_section_size] != '/') { path_section_size++; }

        if (strncmp(filename, ".", path_section_size) != 0) // The current path section is not '.'
        {
            char* buffer = (char*)kmalloc(path_section_size + 1);
            memcpy(buffer, filename, path_section_size);
            buffer[path_section_size] = 0;
            if (!current_node->find_func)
            {
                kwarnln("Current node has no way to find child nodes");
                return 0;
            }
            Node* child = current_node->find_func(current_node, buffer);
            if (!child) { return 0; }
            if (child->flags & VFS_MOUNTPOINT)
            {
                if (!child->link)
                {
                    kwarnln("Current node's link is null");
                    return 0;
                }
                child = child->link;
            }
            current_node = child;
            kfree(buffer);
        }

        filename += path_section_size;
    }
}

int VFS::mkdir(const char* path, const char* name)
{
    Node* node = resolve_path(path, vfs_root);
    if (!node)
    {
        kwarnln("Attempting to mkdir in %s, which does not exist", path);
        return -ENOENT;
    }
    if (node->type != VFS_DIRECTORY)
    {
        kwarnln("Attempting to mkdir in %s, which is not a directory!!", path);
        return -ENOTDIR;
    }
    if (!node->mkdir_func)
    {
        kwarnln("Chosen node does not support mkdir()");
        return -ENOTSUP;
    }
    if (!node->find_func)
    {
        kwarnln("Chosen node does not support finddir()");
        return -ENOTSUP;
    }
    if (!strncmp(name, ".", strlen(name)) || !strncmp(name, "..", strlen(name)))
    {
        kwarnln("Attempted to mkdir . or .., which already exist");
        return -EEXIST;
    }
    if (node->find_func(node, name) != nullptr)
    {
        kwarnln("Already exists");
        return -EEXIST;
    }
    return node->mkdir_func(node, name, 0755);
}

int VFS::do_mkdir(const char* path, const char* name, int uid, int gid, mode_t mode)
{
    Node* node = resolve_path(path, vfs_root);
    if (!node)
    {
        kwarnln("Attempting to mkdir in %s, which does not exist", path);
        return -ENOENT;
    }
    if (node->type != VFS_DIRECTORY)
    {
        kwarnln("Attempting to mkdir in %s, which is not a directory!!", path);
        return -ENOTDIR;
    }
    if (!node->mkdir_func)
    {
        kwarnln("Chosen node does not support mkdir()");
        return -ENOTSUP;
    }
    if (!node->find_func)
    {
        kwarnln("Chosen node does not support finddir()");
        return -ENOTSUP;
    }
    if (!strncmp(name, ".", strlen(name)) || !strncmp(name, "..", strlen(name)))
    {
        kwarnln("Attempted to mkdir . or .., which already exist");
        return -EEXIST;
    }
    if (node->find_func(node, name) != nullptr)
    {
        kwarnln("Already exists");
        return -EEXIST;
    }
    if (!can_write(node, uid, gid))
    {
        kwarnln("Not enough permissions");
        return -EACCES;
    }
    return node->mkdir_func(node, name, mode);
}

int VFS::mkdir(const char* pathname)
{
    char* bstr = strdup(pathname);
    char* dstr = strdup(pathname);

    char* base = basename(bstr);
    char* dir = dirname(dstr);

    kdbgln("mkdir(): creating %s in directory %s", base, dir);

    int result = mkdir(dir, base);

    kfree(bstr);
    kfree(dstr);

    return result;
}

int VFS::do_mkdir(const char* pathname, int uid, int gid, mode_t mode)
{
    char* bstr = strdup(pathname);
    char* dstr = strdup(pathname);

    char* base = basename(bstr);
    char* dir = dirname(dstr);

    kdbgln("mkdir(): creating %s in directory %s", base, dir);

    int result = do_mkdir(dir, base, uid, gid, mode);

    kfree(bstr);
    kfree(dstr);

    return result;
}

bool VFS::exists(const char* pathname)
{
    return resolve_path(pathname) != nullptr;
}

void VFS::mount(Node* mountpoint, Node* mounted)
{
    if (!mountpoint || !mounted) return;
    if (mountpoint->flags & VFS_MOUNTPOINT || mounted->flags & VFS_MOUNTPOINT) return;
    mountpoint->link = mounted;
    mountpoint->flags |= VFS_MOUNTPOINT;
}

void VFS::mount(const char* pathname, Node* mounted)
{
    return mount(resolve_path(pathname), mounted);
}

void VFS::unmount(Node* mountpoint)
{
    if (!mountpoint) return;
    if (!(mountpoint->flags & VFS_MOUNTPOINT)) return;
    mountpoint->flags &= ~VFS_MOUNTPOINT;
}

VFS::Node* VFS::readdir(VFS::Node* dir, long offset)
{
    if (!dir) return 0;
    if (!dir->readdir_func) return 0;
    return dir->readdir_func(dir, offset);
}

bool VFS::can_execute(VFS::Node* node, int uid, int gid)
{
    if (uid == node->uid) return node->mode & 0100;
    if (gid == node->gid) return node->mode & 0010;
    return node->mode & 0001;
}

bool VFS::can_write(VFS::Node* node, int uid, int gid)
{
    if (uid == node->uid) return node->mode & 0200;
    if (gid == node->gid) return node->mode & 0020;
    return node->mode & 0002;
}

bool VFS::can_read(VFS::Node* node, int uid, int gid)
{
    if (uid == node->uid) return node->mode & 0400;
    if (gid == node->gid) return node->mode & 0040;
    return node->mode & 0004;
}

bool VFS::is_setuid(VFS::Node* node)
{
    return node->mode & 04000;
}

bool VFS::is_setgid(VFS::Node* node)
{
    return node->mode & 02000;
}