#include "fs/tmpfs/Inode.h"

namespace TmpFS
{
    Result<SharedPtr<VFS::Inode>> DirInode::find(const char* name) const
    {
        for (const auto& entry : m_entries)
        {
            if (!strcmp(name, entry.name.chars())) return entry.inode;
        }

        return err(ENOENT);
    }

    Result<void> DirInode::replace_entry(SharedPtr<VFS::Inode> inode, const char* name)
    {
        for (auto& entry : m_entries)
        {
            if (!strcmp(name, entry.name.chars()))
            {
                entry.inode = inode;
                return {};
            }
        }

        return err(ENOENT);
    }

    Option<VFS::DirectoryEntry> DirInode::get(usize index) const
    {
        if (index >= m_entries.size()) return {};

        return m_entries[index];
    }

    Result<void> DirInode::add_entry(SharedPtr<VFS::Inode> inode, const char* name)
    {
        if (find(name).has_value()) return err(EEXIST);

        VFS::DirectoryEntry entry { inode, name };

        TRY(m_entries.try_append(move(entry)));

        inode->did_link();

        return {};
    }

    Result<void> DirInode::remove_entry(const char* name)
    {
        SharedPtr<VFS::Inode> inode = TRY(find(name));

        if (inode->type() == VFS::InodeType::Directory && inode->entries() != 2) return err(ENOTEMPTY);

        if (inode->is_mountpoint()) return err(EBUSY);

        m_entries.remove_first_matching(
            [&](const VFS::DirectoryEntry& entry) { return !strcmp(entry.name.chars(), name); });

        inode->did_unlink();

        return {};
    }

    Result<SharedPtr<VFS::Inode>> DirInode::create_file(const char* name, mode_t mode)
    {
        auto inode = TRY(m_fs->create_file_inode(mode));

        TRY(add_entry(inode, name));

        return inode;
    }

    Result<SharedPtr<VFS::Inode>> DirInode::create_subdirectory(const char* name, mode_t mode)
    {
        auto inode = TRY(m_fs->create_dir_inode(m_self, mode));

        TRY(add_entry(inode, name));

        return inode;
    }

    Result<usize> FileInode::read(u8* buf, usize offset, usize length) const
    {
        if (length == 0) return 0;

        if (offset > m_data_buffer.size()) return 0;
        if (offset + length > m_data_buffer.size()) length = m_data_buffer.size() - offset;

        memcpy(buf, m_data_buffer.data() + offset, length);

        return length;
    }

    Result<usize> FileInode::write(const u8* buf, usize offset, usize length)
    {
        if (length == 0) return 0;

        if (offset > m_data_buffer.size())
        {
            // Fill the in-between space with zeroes.
            usize old_size = m_data_buffer.size();
            usize zeroes = offset - old_size;

            TRY(m_data_buffer.try_resize(offset));

            memset(m_data_buffer.data() + old_size, 0, zeroes);
        }

        u8* slice = TRY(m_data_buffer.slice(offset, length));
        memcpy(slice, buf, length);

        m_metadata.size = m_data_buffer.size();

        return length;
    }

    Result<void> FileInode::truncate(usize size)
    {
        usize old_size = m_data_buffer.size();

        TRY(m_data_buffer.try_resize(size));

        if (size > old_size) memset(m_data_buffer.data() + old_size, 0, size - old_size);

        m_metadata.size = m_data_buffer.size();

        return {};
    }
}