// FUSE main Fs

//go:build linux || (darwin && amd64)

package mount2

import (
	"os"
	"syscall"
	"time"

	"github.com/hanwen/go-fuse/v2/fuse"
	"github.com/rclone/rclone/cmd/mountlib"
	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/fs/fserrors"
	"github.com/rclone/rclone/fs/log"
	"github.com/rclone/rclone/vfs"
)

// FS represents the top level filing system
type FS struct {
	VFS *vfs.VFS
	f   fs.Fs
	opt *mountlib.Options
}

// NewFS creates a pathfs.FileSystem from the fs.Fs passed in
func NewFS(VFS *vfs.VFS, opt *mountlib.Options) *FS {
	fsys := &FS{
		VFS: VFS,
		f:   VFS.Fs(),
		opt: opt,
	}
	return fsys
}

// Root returns the root node
func (f *FS) Root() (node *Node, err error) {
	defer log.Trace("", "")("node=%+v, err=%v", &node, &err)
	root, err := f.VFS.Root()
	if err != nil {
		return nil, err
	}
	return newNode(f, root), nil
}

// SetDebug if called, provide debug output through the log package.
func (f *FS) SetDebug(debug bool) {
	fs.Debugf(f.f, "SetDebug %v", debug)
}

// get the Mode from a vfs Node
func getMode(node os.FileInfo) uint32 {
	vfsMode := node.Mode()
	Mode := vfsMode.Perm()
	if vfsMode&os.ModeDir != 0 {
		Mode |= fuse.S_IFDIR
	} else if vfsMode&os.ModeSymlink != 0 {
		Mode |= fuse.S_IFLNK
	} else if vfsMode&os.ModeNamedPipe != 0 {
		Mode |= fuse.S_IFIFO
	} else {
		Mode |= fuse.S_IFREG
	}
	return uint32(Mode)
}

// fill in attr from node
func setAttr(node vfs.Node, attr *fuse.Attr) {
	Size := uint64(node.Size())
	const BlockSize = 512
	Blocks := (Size + BlockSize - 1) / BlockSize
	modTime := node.ModTime()
	// set attributes
	vfs := node.VFS()
	attr.Owner.Gid = vfs.Opt.GID
	attr.Owner.Uid = vfs.Opt.UID
	attr.Mode = getMode(node)
	attr.Size = Size
	attr.Nlink = 1
	attr.Blocks = Blocks
	// attr.Blksize = BlockSize // not supported in freebsd/darwin, defaults to 4k if not set
	s := uint64(modTime.Unix())
	ns := uint32(modTime.Nanosecond())
	attr.Atime = s
	attr.Atimensec = ns
	attr.Mtime = s
	attr.Mtimensec = ns
	attr.Ctime = s
	attr.Ctimensec = ns
	//attr.Rdev
}

// fill in AttrOut from node
func (f *FS) setAttrOut(node vfs.Node, out *fuse.AttrOut) {
	setAttr(node, &out.Attr)
	out.SetTimeout(time.Duration(f.opt.AttrTimeout))
}

// fill in EntryOut from node
func (f *FS) setEntryOut(node vfs.Node, out *fuse.EntryOut) {
	setAttr(node, &out.Attr)
	out.SetEntryTimeout(time.Duration(f.opt.AttrTimeout))
	out.SetAttrTimeout(time.Duration(f.opt.AttrTimeout))
}

// Translate errors from mountlib into Syscall error numbers
func translateError(err error) syscall.Errno {
	if err == nil {
		return 0
	}
	_, uErr := fserrors.Cause(err)
	switch uErr {
	case vfs.OK:
		return 0
	case vfs.ENOENT, fs.ErrorDirNotFound, fs.ErrorObjectNotFound:
		return syscall.ENOENT
	case vfs.EEXIST, fs.ErrorDirExists:
		return syscall.EEXIST
	case vfs.EPERM, fs.ErrorPermissionDenied:
		return syscall.EPERM
	case vfs.ECLOSED:
		return syscall.EBADF
	case vfs.ENOTEMPTY:
		return syscall.ENOTEMPTY
	case vfs.ESPIPE:
		return syscall.ESPIPE
	case vfs.EBADF:
		return syscall.EBADF
	case vfs.EROFS:
		return syscall.EROFS
	case vfs.ENOSYS, fs.ErrorNotImplemented:
		return syscall.ENOSYS
	case vfs.EINVAL:
		return syscall.EINVAL
	case vfs.ELOOP:
		return syscall.ELOOP
	}
	fs.Errorf(nil, "IO error: %v", err)
	return syscall.EIO
}
