// FUSE main Fs

// +build linux darwin freebsd

package mount

import (
	"syscall"

	"bazil.org/fuse"
	fusefs "bazil.org/fuse/fs"
	"github.com/ncw/rclone/cmd/mountlib"
	"github.com/ncw/rclone/fs"
	"github.com/ncw/rclone/fs/log"
	"github.com/ncw/rclone/vfs"
	"github.com/ncw/rclone/vfs/vfsflags"
	"github.com/pkg/errors"
	"golang.org/x/net/context" // switch to "context" when we stop supporting go1.8
)

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

// Check interface satistfied
var _ fusefs.FS = (*FS)(nil)

// NewFS makes a new FS
func NewFS(f fs.Fs) *FS {
	fsys := &FS{
		VFS: vfs.New(f, &vfsflags.Opt),
		f:   f,
	}
	return fsys
}

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

// Check interface satsified
var _ fusefs.FSStatfser = (*FS)(nil)

// Statfs is called to obtain file system metadata.
// It should write that data to resp.
func (f *FS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) (err error) {
	defer log.Trace("", "")("stat=%+v, err=%v", resp, &err)
	const blockSize = 4096
	const fsBlocks = (1 << 50) / blockSize
	resp.Blocks = fsBlocks  // Total data blocks in file system.
	resp.Bfree = fsBlocks   // Free blocks in file system.
	resp.Bavail = fsBlocks  // Free blocks in file system if you're not root.
	resp.Files = 1E9        // Total files in file system.
	resp.Ffree = 1E9        // Free files in file system.
	resp.Bsize = blockSize  // Block size
	resp.Namelen = 255      // Maximum file name length?
	resp.Frsize = blockSize // Fragment size, smallest addressable data size in the file system.
	total, used, free := f.VFS.Statfs()
	if total >= 0 {
		resp.Blocks = uint64(total) / blockSize
	}
	if used >= 0 {
		resp.Bfree = resp.Blocks - uint64(used)/blockSize
	}
	if free >= 0 {
		resp.Bavail = uint64(free) / blockSize
	}
	mountlib.ClipBlocks(&resp.Blocks)
	mountlib.ClipBlocks(&resp.Bfree)
	mountlib.ClipBlocks(&resp.Bavail)
	return nil
}

// Translate errors from mountlib
func translateError(err error) error {
	if err == nil {
		return nil
	}
	switch errors.Cause(err) {
	case vfs.OK:
		return nil
	case vfs.ENOENT:
		return fuse.ENOENT
	case vfs.EEXIST:
		return fuse.EEXIST
	case vfs.EPERM:
		return fuse.EPERM
	case vfs.ECLOSED:
		return fuse.Errno(syscall.EBADF)
	case vfs.ENOTEMPTY:
		return fuse.Errno(syscall.ENOTEMPTY)
	case vfs.ESPIPE:
		return fuse.Errno(syscall.ESPIPE)
	case vfs.EBADF:
		return fuse.Errno(syscall.EBADF)
	case vfs.EROFS:
		return fuse.Errno(syscall.EROFS)
	case vfs.ENOSYS:
		return fuse.ENOSYS
	case vfs.EINVAL:
		return fuse.Errno(syscall.EINVAL)
	}
	return err
}
