#pragma once
#include "fs/tmpfs/FileSystem.h"
#include <luna/Badge.h>
#include <luna/Buffer.h>
#include <luna/StaticString.h>
#include <luna/String.h>
#include <luna/Vector.h>

namespace TmpFS
{
    class FileInode : public VFS::FileInode
    {
      public:
        FileInode() = default;

        void set_fs(FileSystem& fs, Badge<FileSystem>)
        {
            m_fs = &fs;
        }

        void set_inode_number(usize inum, Badge<FileSystem>)
        {
            m_metadata.inum = inum;
        }

        VFS::FileSystem* fs() const override
        {
            return m_fs;
        }
        Result<usize> read(u8*, usize, usize) const override;

        Result<usize> write(const u8*, usize, usize) override;

        Result<void> truncate(usize size) override;

        Result<u64> query_shared_memory(off_t offset, usize count) override;

        void did_link() override
        {
            m_metadata.nlinks++;
        }

        void did_unlink() override
        {
            m_metadata.nlinks--;
        }

        virtual ~FileInode() = default;

      private:
        VFS::FileSystem* m_fs;
        Buffer m_data_buffer;

        friend class FileSystem;
    };

    class SymlinkInode : public VFS::FileInode
    {
      public:
        SymlinkInode() = default;

        VFS::InodeType type() const override
        {
            return VFS::InodeType::Symlink;
        }

        void set_fs(FileSystem& fs, Badge<FileSystem>)
        {
            m_fs = &fs;
        }

        void set_inode_number(usize inum, Badge<FileSystem>)
        {
            m_metadata.inum = inum;
        }

        Result<void> set_link(StringView link, Badge<FileSystem>)
        {
            m_link = TRY(String::from_string_view(link));
            return {};
        }

        VFS::FileSystem* fs() const override
        {
            return m_fs;
        }

        Result<usize> read(u8*, usize, usize) const override
        {
            return err(ENOTSUP);
        }

        Result<usize> write(const u8*, usize, usize) override
        {
            return err(ENOTSUP);
        }

        Result<void> truncate(usize) override
        {
            return err(ENOTSUP);
        }

        void did_link() override
        {
            m_metadata.nlinks++;
        }

        void did_unlink() override
        {
            m_metadata.nlinks--;
        }

        Result<StringView> readlink() override
        {
            return m_link.view();
        }

        virtual ~SymlinkInode() = default;

      private:
        VFS::FileSystem* m_fs;
        String m_link;

        friend class FileSystem;
    };

    class DeviceInode : public VFS::DeviceInode
    {
      public:
        DeviceInode() = default;

        VFS::InodeType type() const override
        {
            return m_device->is_block_device() ? VFS::InodeType::BlockDevice : VFS::InodeType::CharacterDevice;
        }

        void set_fs(FileSystem& fs, Badge<FileSystem>)
        {
            m_fs = &fs;
        }

        void set_inode_number(usize inum, Badge<FileSystem>)
        {
            m_metadata.inum = inum;
        }

        void set_device(SharedPtr<Device> device, Badge<FileSystem>)
        {
            m_device = device;
        }

        void set_device_id(dev_t id, Badge<FileSystem>)
        {
            m_metadata.devid = id;
        }

        Result<u64> query_shared_memory(off_t offset, usize count) override
        {
            return m_device->query_shared_memory(offset, count);
        }

        VFS::FileSystem* fs() const override
        {
            return m_fs;
        }

        Result<usize> read(u8* buf, usize offset, usize length) const override
        {
            return m_device->read(buf, offset, length);
        }

        Result<usize> write(const u8* buf, usize offset, usize length) override
        {
            return m_device->write(buf, offset, length);
        }

        Result<void> truncate(usize) override
        {
            // POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files.
            return err(EINVAL);
        }

        Result<u64> ioctl(int request, void* arg) override
        {
            return m_device->ioctl(request, arg);
        }

        Result<u64> isatty() const override
        {
            return m_device->isatty();
        }

        bool will_block_if_read() const override
        {
            return m_device->will_block_if_read();
        }

        void did_link() override
        {
            m_metadata.nlinks++;
        }

        void did_unlink() override
        {
            m_metadata.nlinks--;
        }

        virtual ~DeviceInode() = default;

      private:
        VFS::FileSystem* m_fs;
        SharedPtr<Device> m_device;

        friend class FileSystem;
    };

    class DirInode : public VFS::Inode
    {
      public:
        DirInode() = default;

        void set_fs(FileSystem& fs, Badge<FileSystem>)
        {
            m_fs = &fs;
        }

        void set_inode_number(usize inum, Badge<FileSystem>)
        {
            m_metadata.inum = inum;
        }

        void set_self(SharedPtr<VFS::Inode> self, Badge<FileSystem>)
        {
            m_self = self;
        }

        Result<SharedPtr<VFS::Inode>> find(const char* name) const override;
        Option<VFS::DirectoryEntry> get(usize index) const override;

        Result<usize> read(u8*, usize, usize) const override
        {
            return err(EISDIR);
        }

        Result<usize> write(const u8*, usize, usize) override
        {
            return err(EISDIR);
        }

        Result<void> truncate(usize) override
        {
            return err(EISDIR);
        }

        bool will_block_if_read() const override
        {
            return false;
        }

        VFS::FileSystem* fs() const override
        {
            return m_fs;
        }

        VFS::InodeType type() const override
        {
            return VFS::InodeType::Directory;
        }

        void did_link() override
        {
        }

        void did_unlink() override
        {
            m_self = {};
            m_entries.clear();
        }

        usize entries() const override
        {
            return m_entries.size();
        }

        Result<void> remove_entry(const char* name) override;

        Result<SharedPtr<VFS::Inode>> create_file(const char* name, mode_t mode) override;
        Result<SharedPtr<VFS::Inode>> create_subdirectory(const char* name, mode_t mode) override;

        Result<void> add_entry(SharedPtr<VFS::Inode> inode, const char* name);
        Result<void> replace_entry(SharedPtr<VFS::Inode> inode, const char* name);

        virtual ~DirInode() = default;

      private:
        VFS::FileSystem* m_fs;
        SharedPtr<VFS::Inode> m_self;
        Vector<VFS::DirectoryEntry> m_entries;

        friend class FileSystem;
    };
}