// Package vfs provides a virtual filing system layer over rclone's
// native objects.
//
// It attempts to behave in a similar way to Go's filing system
// manipulation code in the os package.  The same named function
// should behave in an identical fashion.  The objects also obey Go's
// standard interfaces.
//
// It also includes directory caching
package vfs

import (
	"fmt"
	"os"
	"strings"
	"sync/atomic"
	"time"

	"github.com/ncw/rclone/fs"
)

// DefaultOpt is the default values uses for Opt
var DefaultOpt = Options{
	NoModTime:    false,
	NoChecksum:   false,
	NoSeek:       false,
	DirCacheTime: 5 * 60 * time.Second,
	PollInterval: time.Minute,
	ReadOnly:     false,
	Umask:        0,
	UID:          ^uint32(0), // these values instruct WinFSP-FUSE to use the current user
	GID:          ^uint32(0), // overriden for non windows in mount_unix.go
	DirPerms:     os.FileMode(0777),
	FilePerms:    os.FileMode(0666),
}

// Node represents either a directory (*Dir) or a file (*File)
type Node interface {
	os.FileInfo
	IsFile() bool
	Inode() uint64
	SetModTime(modTime time.Time) error
	Fsync() error
	Remove() error
	RemoveAll() error
	DirEntry() fs.DirEntry
	VFS() *VFS
}

// Check interfaces
var (
	_ Node = (*File)(nil)
	_ Node = (*Dir)(nil)
)

// Nodes is a slice of Node
type Nodes []Node

// Sort functions
func (ns Nodes) Len() int           { return len(ns) }
func (ns Nodes) Swap(i, j int)      { ns[i], ns[j] = ns[j], ns[i] }
func (ns Nodes) Less(i, j int) bool { return ns[i].DirEntry().Remote() < ns[j].DirEntry().Remote() }

// Noder represents something which can return a node
type Noder interface {
	fmt.Stringer
	Node() Node
}

// Check interfaces
var (
	_ Noder = (*File)(nil)
	_ Noder = (*Dir)(nil)
	_ Noder = (*ReadFileHandle)(nil)
	_ Noder = (*WriteFileHandle)(nil)
)

// VFS represents the top level filing system
type VFS struct {
	f    fs.Fs
	root *Dir
	Opt  Options
}

// Options is options for creating the vfs
type Options struct {
	NoSeek       bool          // don't allow seeking if set
	NoChecksum   bool          // don't check checksums if set
	ReadOnly     bool          // if set VFS is read only
	NoModTime    bool          // don't read mod times for files
	DirCacheTime time.Duration // how long to consider directory listing cache valid
	PollInterval time.Duration
	Umask        int
	UID          uint32
	GID          uint32
	DirPerms     os.FileMode
	FilePerms    os.FileMode
}

// New creates a new VFS and root directory.  If opt is nil, then
// DefaultOpt will be used
func New(f fs.Fs, opt *Options) *VFS {
	fsDir := fs.NewDir("", time.Now())
	vfs := &VFS{
		f: f,
	}

	// Make a copy of the options
	if opt != nil {
		vfs.Opt = *opt
	} else {
		vfs.Opt = DefaultOpt
	}

	// Mask the permissions with the umask
	vfs.Opt.DirPerms &= ^os.FileMode(vfs.Opt.Umask)
	vfs.Opt.FilePerms &= ^os.FileMode(vfs.Opt.Umask)

	// Create root directory
	vfs.root = newDir(vfs, f, nil, fsDir)

	// Start polling if required
	if vfs.Opt.PollInterval > 0 {
		if do := vfs.f.Features().DirChangeNotify; do != nil {
			do(vfs.root.ForgetPath, vfs.Opt.PollInterval)
		}
	}
	return vfs
}

// Root returns the root node
func (vfs *VFS) Root() (*Dir, error) {
	// fs.Debugf(vfs.f, "Root()")
	return vfs.root, nil
}

var inodeCount uint64

// newInode creates a new unique inode number
func newInode() (inode uint64) {
	return atomic.AddUint64(&inodeCount, 1)
}

// Stat finds the Node by path starting from the root
//
// It is the equivalent of os.Stat - Node contains the os.FileInfo
// interface.
func (vfs *VFS) Stat(path string) (node Node, err error) {
	node = vfs.root
	for path != "" {
		i := strings.IndexRune(path, '/')
		var name string
		if i < 0 {
			name, path = path, ""
		} else {
			name, path = path[:i], path[i+1:]
		}
		if name == "" {
			continue
		}
		dir, ok := node.(*Dir)
		if !ok {
			// We need to look in a directory, but found a file
			return nil, ENOENT
		}
		node, err = dir.Stat(name)
		if err != nil {
			return nil, err
		}
	}
	return
}