/**
 * @file FileSystem.cpp
 * @author apio (cloudapio.eu)
 * @brief APIs to read and modify the general file system.
 *
 * @copyright Copyright (c) 2023, the Luna authors.
 *
 */

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <luna/PathParser.h>
#include <luna/String.h>
#include <os/Directory.h>
#include <os/FileSystem.h>
#include <pwd.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

namespace os::FileSystem
{
    bool exists(const Path& path, bool follow_symlinks)
    {
        struct stat st;

        if (stat(path, st, follow_symlinks).has_error()) return false;

        return true;
    }

    bool is_directory(const Path& path, bool follow_symlinks)
    {
        struct stat st;

        if (stat(path, st, follow_symlinks).has_error()) return false;

        return S_ISDIR(st.st_mode);
    }

    Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks)
    {
        long rc = syscall(SYS_fstatat, path.dirfd(), path.name().chars(), &st,
                          (int)(path.is_empty_path() | (follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW)));

        return Result<void>::from_syscall(rc);
    }

    Result<void> create_directory(StringView path, mode_t mode)
    {
        long rc = syscall(SYS_mkdir, path.chars(), mode);

        return Result<void>::from_syscall(rc);
    }

    Result<void> remove(const Path& path)
    {
        long rc = syscall(SYS_unlinkat, path.dirfd(), path.name().chars(), 0);

        return Result<void>::from_syscall(rc);
    }

    Result<void> remove_tree(const Path& path)
    {
        auto rc = remove(path);
        if (!rc.has_error()) return {};
        if (rc.error() != ENOTEMPTY) return rc.release_error();

        auto dir = TRY(os::Directory::open(path));

        Vector<String> entries = TRY(dir->list_names(os::Directory::Filter::ParentAndBase));

        for (const auto& entry : entries) { TRY(remove_tree({ dir->fd(), entry.view() })); }

        return remove(path);
    }

    Result<String> readlink(const Path& path)
    {
        struct stat st;
        TRY(stat(path, st, false));
        if (!S_ISLNK(st.st_mode)) return String {};

        char* buf = TRY(make_array<char>(st.st_size + 1));
        auto guard = make_scope_guard([buf] { delete[] buf; });
        usize nread = TRY(
            Result<usize>::from_syscall(syscall(SYS_readlinkat, path.dirfd(), path.name().chars(), buf, st.st_size)));

        buf[nread] = '\0';
        guard.deactivate();

        return String { buf, nread };
    }

    Result<String> working_directory()
    {
        char* ptr = getcwd(NULL, 0);
        if (!ptr) return err(errno);
        return String { ptr };
    }

    Result<String> home_directory()
    {
        char* home = getenv("HOME");
        if (home) return String::from_cstring(home);

        struct passwd* pw = getpwuid(getuid());
        if (!pw) return err(ENOENT);

        return String::from_cstring(pw->pw_dir);
    }

    Result<void> change_directory(StringView path)
    {
        long rc = syscall(SYS_chdir, path.chars());

        return Result<void>::from_syscall(rc);
    }
}