// +build linux darwin freebsd

package mount

import (
	"time"

	"bazil.org/fuse"
	fusefs "bazil.org/fuse/fs"
	"github.com/ncw/rclone/cmd/mountlib"
	"github.com/ncw/rclone/fs"
	"github.com/pkg/errors"
	"golang.org/x/net/context"
)

// File represents a file
type File struct {
	*mountlib.File
	// size           int64        // size of file - read and written with atomic int64 - must be 64 bit aligned
	// d              *Dir         // parent directory - read only
	// mu             sync.RWMutex // protects the following
	// o              fs.Object    // NB o may be nil if file is being written
	// writers        int          // number of writers for this file
	// pendingModTime time.Time    // will be applied once o becomes available, i.e. after file was written
}

// Check interface satisfied
var _ fusefs.Node = (*File)(nil)

// Attr fills out the attributes for the file
func (f *File) Attr(ctx context.Context, a *fuse.Attr) (err error) {
	defer fs.Trace(f, "")("a=%+v, err=%v", a, &err)
	modTime, Size, Blocks, err := f.File.Attr(mountlib.NoModTime)
	if err != nil {
		return translateError(err)
	}
	a.Gid = mountlib.GID
	a.Uid = mountlib.UID
	a.Mode = mountlib.FilePerms
	a.Size = Size
	a.Atime = modTime
	a.Mtime = modTime
	a.Ctime = modTime
	a.Crtime = modTime
	a.Blocks = Blocks
	return nil
}

// Check interface satisfied
var _ fusefs.NodeSetattrer = (*File)(nil)

// Setattr handles attribute changes from FUSE. Currently supports ModTime only.
func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) (err error) {
	defer fs.Trace(f, "a=%+v", req)("err=%v", &err)
	if mountlib.NoModTime {
		return nil
	}
	if req.Valid.MtimeNow() {
		err = f.File.SetModTime(time.Now())
	} else if req.Valid.Mtime() {
		err = f.File.SetModTime(req.Mtime)
	}
	return translateError(err)
}

// Check interface satisfied
var _ fusefs.NodeOpener = (*File)(nil)

// Open the file for read or write
func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fh fusefs.Handle, err error) {
	defer fs.Trace(f, "flags=%v", req.Flags)("fh=%v, err=%v", &fh, &err)
	switch {
	case req.Flags.IsReadOnly():
		if mountlib.NoSeek {
			resp.Flags |= fuse.OpenNonSeekable
		}
		var rfh *mountlib.ReadFileHandle
		rfh, err = f.File.OpenRead()
		fh = &ReadFileHandle{rfh}
	case req.Flags.IsWriteOnly() || (req.Flags.IsReadWrite() && (req.Flags&fuse.OpenTruncate) != 0):
		resp.Flags |= fuse.OpenNonSeekable
		var wfh *mountlib.WriteFileHandle
		wfh, err = f.File.OpenWrite()
		fh = &WriteFileHandle{wfh}
	case req.Flags.IsReadWrite():
		err = errors.New("can't open for read and write simultaneously")
	default:
		err = errors.Errorf("can't figure out how to open with flags %v", req.Flags)
	}

	/*
	   // File was opened in append-only mode, all writes will go to end
	   // of file. OS X does not provide this information.
	   OpenAppend    OpenFlags = syscall.O_APPEND
	   OpenCreate    OpenFlags = syscall.O_CREAT
	   OpenDirectory OpenFlags = syscall.O_DIRECTORY
	   OpenExclusive OpenFlags = syscall.O_EXCL
	   OpenNonblock  OpenFlags = syscall.O_NONBLOCK
	   OpenSync      OpenFlags = syscall.O_SYNC
	   OpenTruncate  OpenFlags = syscall.O_TRUNC
	*/

	if err != nil {
		return nil, translateError(err)
	}
	return fh, nil
}

// Check interface satisfied
var _ fusefs.NodeFsyncer = (*File)(nil)

// Fsync the file
//
// Note that we don't do anything except return OK
func (f *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) {
	defer fs.Trace(f, "")("err=%v", &err)
	return nil
}