/*
 * Copyright (C) Eugene 'Vindex' Stulin, 2025-2026
 *
 * 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.
 */

/**
 * The libfuse3 library broke backward compatibility several times.
 * Because of this we have to use several versions of the fuse_config struct.
 * The correct version is determined at compile time.
 */
module oxfuse.fuse_config;

import unix_sig = core.sys.posix.signal;

import std.algorithm : among;
import std.exception : enforce;
import std.stdio;

import oxfuse.libfuse_version : libfuseVersion;
import oxfuse.utils : compareVersions, NoData;

private alias V = libfuseVersion;
private alias cmp = compareVersions;

static if (cmp(V, "3.11.0") == '<')
public import oxfuse.fuse_config.v3_0_0;

static if (cmp(V, "3.11.0").among('=', '>') && cmp(V, "3.14.1") == '<')
public import oxfuse.fuse_config.v3_11_0;

static if (cmp(V, "3.14.1").among('=', '>') && cmp(V, "3.17.0") == '<')
public import oxfuse.fuse_config.v3_14_1;

static if (cmp(V, "3.17.0").among('=', '>'))
public import oxfuse.fuse_config.v3_17_x;


/**
 * Object of the FuseConfig appears in FileSystem.initialize() only.
 * This contains a pointer to the C fuse_config structure
 * and provides a set of methods to manage the contents of this structure.
 * The fuse_config fields can also be accessed directly if the required method
 * is not present, example:
 * ```
 * override void initialize(ConnInfo conn, FuseConfig cfg) {
 *     cfg.getRawPointer().nullpath_ok = 1;
 * }
 * ```
 */
class FuseConfig {
    /// Pointer to the fuse_config object.
    private fuse_config* settings;
    private deprecated alias config = settings;

    /// Returns pointer to struct fuse_config from libfuse.
    fuse_config* getRawPointer() {
        return settings;
    }

    /**
     * Setter and getter for entry timeout.
     * Entry timeout is the timeout in seconds for which name lookups will be
     * cached. It's equal to 1 second, by default.
     * Usage:
     * ```
     * override void initialize(ConnInfo conn, FuseConfig cfg) {
     *     writeln(cfg.entryTimeout.get());
     *     cfg.entryTimeout.set(0.0);
     * }
     * ```
     */
    Timeout!"entry_timeout" entryTimeout;

    /**
     * Setter and getter for negative timeout, it is the timeout in seconds
     * for which a negative lookup will be cached. This means that if a file
     * did not exist (ENOENT), the lookup will only be redone after the timeout,
     * and the file will be assumed to not exist until then. 1 s, by default.
     */
    Timeout!"negative_timeout" negativeTimeout;

    /**
     * Setter and getter for attribute timeout, it is the timeout in seconds
     * for which file attributes are cached. 1 s, by default.
     */
    Timeout!"attr_timeout" attrTimeout;

    /// Default constructor.
    this(fuse_config* cfg) {
        this.settings = cfg;
        this.entryTimeout    = new typeof(entryTimeout);
        this.negativeTimeout = new typeof(negativeTimeout);
        this.attrTimeout     = new typeof(attrTimeout);
    }

    /* Nested template class for implementation of entryTimeout,
     * negativeTimeout, attrTimeout. Contains set() and get() methods.
     */
    class Timeout(alias field) {
        void set(double timeout) {
            enforce!NoData(settings != null);
            mixin("settings." ~ field) = timeout;
        }
        double get() {
            enforce!NoData(settings != null);
            return mixin("settings." ~ field);
        }
    }

    /// The st_gid attribute of each file is set to `gid`.
    void useGid(uint gid) {
        enforce!NoData(settings != null);
        this.settings.set_gid = 1;
        this.settings.gid = gid;
    }

    /// Permissions bits of `umask` are unset in the st_mode of each file.
    void useUmask(uint umask) {
        enforce!NoData(settings != null);
        this.settings.set_mode = 1;
        this.settings.umask = umask;
    }

    /**
     * Allows requests to be interrupted.
     * Params:
     *     intrSignal = Signal to send to the FS when a request is interrupted.
     */
    void interruptible(int intrSignal = unix_sig.SIGUSR1) {
        enforce!NoData(settings != null);
        this.settings.intr = 1;
        this.settings.intr_signal = intrSignal;
    }

    /**
     * Typically, FUSE assigns inodes to paths only while the kernel knows them.
     * The enabled `remember` option retains inodes for a set duration;
     * with -1 meaning inodes are remembered for the FS process life-time.
     */
    void rememberInodes(int seconds = -1) {
        enforce!NoData(settings != null);
        this.settings.remember = seconds;
    }

    /**
     * Enables inode numbers in getattr(). By default, inode numbers
     * are auto-assigned, which may not suit your needs.
     */
    void useIno() {
        enforce!NoData(settings != null);
        this.settings.use_ino = 1;
    }

    /// Disables the kernel page cache.
    void enableDirectIo() {
        enforce!NoData(settings != null);
        this.settings.direct_io = 1;
    }

    /**
     * Disable flushing the file content cache on each `open()`.
     * Use only for filesystems where file data is never modified
     * outside the mounted FUSE filesystem.
     * When enabled, FUSE sets `fuse_file_info.keep_cache`,
     * overriding any value provided by the filesystem.
     */
    void enableKernelCache() {
        enforce!NoData(settings != null);
        this.settings.kernel_cache = 1;
    }

    /**
     * It is an alternative to `enableKernelCache()`.
     * Instead of unconditionally keeping cached data, the cached data is
     * invalidated on `open()` if the modification time or the
     * size of the file has changed since it was last opened.
     * Params:
     *     timeout = The timeout in seconds for which file attributes are
     *               cached for the purpose of checking if autocache mode
     *               should flush the file data on open.
     *               With negative value, the timeout is not assigned.
     */
    void enableAutoCache(double timeout = -1.0) {
        enforce!NoData(settings != null);
        this.settings.auto_cache = 1;
        if (timeout >= 0.0) {
            this.settings.ac_attr_timeout_set = 1;
            this.settings.ac_attr_timeout = timeout;
        }
    }

    /**
     * By default, FUSE waits for all pending writes to complete
     * and calls the flush() operation on close() of every fuse fd.
     * With the `no_rofd_flush` option, wait and flush are not done for
     * read-only fuse fd, similar to the behavior of NFS/SMB clients.
     * Works with libfuse v3.14.1+.
     */
    void noRoFlush() {
        static if (__traits(compiles, { this.settings.no_rofd_flush; })) {
            enforce!NoData(settings != null);
            this.settings.no_rofd_flush = 1;
        } else {
            enum m = "Warning: noRoFlush() has no effect: libfuse " ~ V;
            stderr.writeln(m);
        }
    }
}
