/*
 * Copyright (C) Eugene 'Vindex' Stulin, 2023-2025
 *
 * Distributed under
 * the Boost Software License 1.0 or (at your option)
 * the GNU Lesser General Public License 3.0 or later.
 * This file is offered as-is, without any warranty.
 *
 * This D wrapper was written following some header files
 * of the FUSE library: https://github.com/libfuse/libfuse
 */

/**
 * The module contains basic definitions
 * of functions and structures from *libfuse*.
 */

module oxfuse.cfuse;  // simple wrapper

import std.bitmanip : bitfields;

public import core.sys.posix.sys.stat;
public import core.sys.posix.sys.types;
public import core.sys.posix.signal : timespec;
public import core.sys.posix.sys.statvfs : statvfs_t;
public import core.sys.posix.fcntl : flock_t = flock;

import oxfuse.fuse_config;
import oxfuse.fuse_file_info;
import oxfuse.fuse_conn_info;

extern(C):


/** Handle for a FUSE filesystem */
struct fuse;


struct fuse_pollhandle;


/**
 * Flags passed to readdir().
 */
enum ReadDirFlags {
    /**
     * The kernel wants to prefill the inode cache during readdir.
     * The filesystem may honour this by filling in the attributes and setting
     * FUSE_FILL_DIR_FLAGS for the filler function.
     * The filesystem may also just ignore this flag completely.
     */
    FUSE_READDIR_DEFAULTS = 0,
    FUSE_READDIR_PLUS = (1 << 0)  //< "Plus" mode.
}
private alias RDFlags = ReadDirFlags;


enum FillDirFlags {
    /**
     * "Plus" mode: all file attributes are valid.
     * The attributes are used by the kernel to prefill the inode cache
     * during a readdir.
     * It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set
     * and vice versa.
     */
    FUSE_FILL_DIR_DEFAULTS = 0,
    FUSE_FILL_DIR_PLUS = (1 << 1)
}


/**
 * Function to add an entry in a readdir() operation
 *
 * The *offset* parameter can be any non-zero value that enables the
 * filesystem to identify the current point in the directory
 * stream. It does not need to be the actual physical position. A
 * value of zero is reserved to indicate that seeking in directories
 * is not supported.
 *
 * Params:
 *   buf      = the buffer passed to the readdir() operation
 *   name     = the file name of the directory entry
 *   stbuf    = file attributes, can be null
 *   offset   = offset of the next entry or zero
 *   fill_dir = fill flags
 *
 * Returns:
 *   1 if buffer is full, zero otherwise
 */
alias fuse_fill_dir_t = int function(
    void* buf, char* name, stat_t* stbuf, off_t offset, int fill_dir
) nothrow @nogc;
private alias FillFn = fuse_fill_dir_t;


/**
 * Get the current context.
 *
 * The context is only valid for the duration of a filesystem
 * operation, and thus must not be stored and used later.
 *
 * Returns:
 *   the context
 */
fuse_context* fuse_get_context() nothrow @nogc;


/**
 * The file system operations:
 *
 * Most of these should work very similarly to the well known UNIX
 * file system operations.  A major exception is that instead of
 * returning an error in 'errno', the operation should return the
 * negated error value (-errno) directly.
 *
 * All methods are optional, but some are essential for a useful
 * filesystem (e.g. getattr).  Open, flush, release, fsync, opendir,
 * releasedir, fsyncdir, access, create, truncate, lock, initialize and
 * destroy are special purpose methods, without which a full featured
 * filesystem can still be implemented.
 *
 * In general, all methods are expected to perform any necessary
 * permission checking. However, a filesystem may delegate this task
 * to the kernel by passing the `default_permissions` mount option to
 * `fuse_new()`. In this case, methods will only be called if
 * the kernel's permission check has succeeded.
 *
 * Almost all operations take a path which can be of any length.
 */
struct fuse_operations {
    int function(const char*, stat_t*, ffi_t* fi) getattr;
    int function(const char*, char*, size_t) readlink;
    int function(const char*, mode_t, dev_t) mknod;
    int function(const char*, mode_t) mkdir;
    int function(const char*) unlink;
    int function(const char*) rmdir;
    int function(const char*, const char*) symlink;
    int function(const char *, const char *, uint flags) rename;
    int function(const char*, const char*) link;
    int function(const char*, mode_t, ffi_t* fi) chmod;
    int function(const char*, uid_t, gid_t, ffi_t* fi) chown;
    int function(const char*, off_t, ffi_t* fi) truncate;
    int function(const char*, ffi_t*) open;
    int function(const char*, ubyte*, size_t, off_t, ffi_t*) read;
    int function(const char*, const ubyte*, size_t, off_t, ffi_t*) write;
    int function(const char*, statvfs_t*) statfs;
    int function(const char*, ffi_t*) flush;
    int function(const char*, ffi_t*) release;
    int function(const char*, int, ffi_t*) fsync;
    int function(const char*, const char*, const char*, size_t, int) setxattr;
    int function(const char*, const char*, char*, size_t) getxattr;
    int function(const char*, char*, size_t) listxattr;
    int function(const char*, const char*) removexattr;
    int function(const char*, ffi_t*) opendir;
    int function(const char*, void*, FillFn, off_t, ffi_t*, RDFlags) readdir;
    int function(const char*, ffi_t*) releasedir;
    int function(const char*, int, ffi_t*) fsyncdir;
    void* function(fuse_conn_info*, fuse_config*) initialize;
    void function(void* private_data) destroy;
    int function(const char*, int) access;
    int function(const char*, mode_t, ffi_t*) create;
    int function(const char*, ffi_t*, int, flock_t*) lock;
    int function(const char*, ref const timespec[2], ffi_t*) utimens;
    int function(const char*, size_t, ulong*) bmap;
    int function(const char*, uint, void*, ffi_t*, uint, void*) ioctl;
    int function(const char*, ffi_t*, fuse_pollhandle*, uint*) poll;
    int function(const char*, fuse_bufvec*, off_t, ffi_t*) write_buf;
    int function(const char*, fuse_bufvec**, size_t, off_t, ffi_t*) read_buf;
    int function(const char*, ffi_t*, int) flock;
    int function(const char*, int, off_t, off_t, ffi_t*) fallocate;
    ssize_t function(
        const char*, ffi_t*, off_t, const char*, ffi_t*, off_t, size_t, int
    ) copy_file_range;
    off_t function(const char*, off_t offset, int whence, ffi_t*) lseek;
}


/**
 * Extra context that may be needed by some filesystems.
 * The uid, gid and pid fields are not filled in case of a writepage operation.
 */
struct fuse_context {
    .fuse* fuse;  /// pointer to the fuse object
    uid_t uid;  /// user ID of the calling process
    gid_t gid;  /// group ID of the calling process
    pid_t pid;  /// process ID of the calling thread
    void* private_data;  /// private filesystem data
    mode_t umask;  /// umask of the calling process
}


/**
 * Data buffer vector
 *
 * An array of data buffers, each containing a memory pointer or a
 * file descriptor.
 *
 * Allocate dynamically to add more than one buffer.
 */
struct fuse_bufvec {
    size_t count;  // number of buffers in the array
    size_t idx;  // index of current buffer within the array
    size_t off;  // current offset within the current buffer
    fuse_buf[1] buf;  /// array of buffers
}


/**
 * Buffer flags
 */
enum fuse_buf_flags {
    /**
     * Buffer contains a file descriptor
     *
     * If this flag is set, the .fd field is valid, otherwise the
     * .mem fields is valid.
     */
    FUSE_BUF_IS_FD = (1 << 1),

    /**
     * Seek on the file descriptor
     *
     * If this flag is set then the .pos field is valid and is
     * used to seek to the given offset before performing
     * operation on file descriptor.
     */
    FUSE_BUF_FD_SEEK = (1 << 2),

    /**
     * Retry operation on file descriptor
     *
     * If this flag is set then retry operation on file descriptor
     * until .size bytes have been copied or an error or EOF is
     * detected.
     */
    FUSE_BUF_FD_RETRY = (1 << 3)
}


/**
 * Single data buffer
 *
 * Generic data buffer for I/O, extended attributes, etc...  Data may
 * be supplied as a memory pointer or as a file descriptor
 */
struct fuse_buf {
    size_t size;  /// size of data in bytes
    fuse_buf_flags flags;  /// buffer flags
    void* mem;  /// memory pointer; used unless FUSE_BUF_IS_FD flag is set
    int fd;  /// file descriptor; used if FUSE_BUF_IS_FD flag is set
    off_t pos;  /// file position, used if FUSE_BUF_FD_SEEK is set
}


/**
 * Main function of FUSE.
 *
 * This is for the lazy.  This is all that has to be called from the
 * main() function.
 *
 * This function does the following:
 *   - parses command line options (-d -s and -h);
 *   - passes relevant mount options to the fuse_mount();
 *   - installs signal handlers for INT, HUP, TERM and PIPE;
 *   - registers an exit handler to unmount the filesystem on program exit;
 *   - creates a fuse handle;
 *   - registers the operations;
 *   - calls either the single-threaded or the multi-threaded event loop.
 *
 * Params:
 *   argc      = the argument counter passed to the main() function
 *   argv      = the argument vector passed to the main() function
 *   op        = the file system operation
 *   userData = user data supplied in the context during the initialize() method
 * Returns:
 *   0 on success, nonzero on failure
 */
int fuse_main(int argc, char** argv, fuse_operations* op, void* userData)
nothrow @nogc {
    return fuse_main_real(argc, argv, op, fuse_operations.sizeof, userData);
}


private int fuse_main_real(
    int argc,
    char** argv,
    const fuse_operations* op,
    size_t op_size,
    void* user_data
) nothrow @nogc;


/**
 * This function is used to notify that some expected event has occurred.
 * Calling this function initiates a new poll() call.
 */
int fuse_notify_poll(fuse_pollhandle* ph) nothrow @nogc;


/// Destroy poll handle.
void fuse_pollhandle_destroy(fuse_pollhandle* ph);


/// Get the version of the library.
int fuse_version();


/// Get the full package version string of the library.
const(char)* fuse_pkgversion();


/// Check if the current request has already been interrupted.
extern(C) int fuse_interrupted() nothrow @nogc;
