package nfs

import (
	"context"
	"io"
	"os"
	"time"

	"github.com/royalcat/ctxio"
)

// FSStat returns metadata about a file system
type FSStat struct {
	TotalSize      uint64
	FreeSize       uint64
	AvailableSize  uint64
	TotalFiles     uint64
	FreeFiles      uint64
	AvailableFiles uint64
	// CacheHint is called "invarsec" in the nfs standard
	CacheHint time.Duration
}

type Filesystem interface {
	// Create creates the named file with mode 0666 (before umask), truncating
	// it if it already exists. If successful, methods on the returned File can
	// be used for I/O; the associated file descriptor has mode O_RDWR.
	Create(ctx context.Context, filename string) (File, error)
	// OpenFile is the generalized open call; most users will use Open or Create
	// instead. It opens the named file with specified flag (O_RDONLY etc.) and
	// perm, (0666 etc.) if applicable. If successful, methods on the returned
	// File can be used for I/O.
	OpenFile(ctx context.Context, filename string, flag int, perm os.FileMode) (File, error)
	// Stat returns a FileInfo describing the named file.
	Stat(ctx context.Context, filename string) (os.FileInfo, error)
	// Rename renames (moves) oldpath to newpath. If newpath already exists and
	// is not a directory, Rename replaces it. OS-specific restrictions may
	// apply when oldpath and newpath are in different directories.
	Rename(ctx context.Context, oldpath, newpath string) error
	// Remove removes the named file or directory.
	Remove(ctx context.Context, filename string) error
	// Join joins any number of path elements into a single path, adding a
	// Separator if necessary. Join calls filepath.Clean on the result; in
	// particular, all empty strings are ignored. On Windows, the result is a
	// UNC path if and only if the first path element is a UNC path.
	Join(elem ...string) string

	// ReadDir reads the directory named by d(irname and returns a list of
	// directory entries sorted by filename.
	ReadDir(ctx context.Context, path string) ([]os.FileInfo, error)
	// MkdirAll creates a directory named path, along with any necessary
	// parents, and returns nil, or else returns an error. The permission bits
	// perm are used for all directories that MkdirAll creates. If path is/
	// already a directory, MkdirAll does nothing and returns nil.
	MkdirAll(ctx context.Context, filename string, perm os.FileMode) error

	// Lstat returns a FileInfo describing the named file. If the file is a
	// symbolic link, the returned FileInfo describes the symbolic link. Lstat
	// makes no attempt to follow the link.
	Lstat(ctx context.Context, filename string) (os.FileInfo, error)
	// Symlink creates a symbolic-link from link to target. target may be an
	// absolute or relative path, and need not refer to an existing node.
	// Parent directories of link are created as necessary.
	Symlink(ctx context.Context, target, link string) error
	// Readlink returns the target path of link.
	Readlink(ctx context.Context, link string) (string, error)
}

type File interface {
	// Name returns the name of the file as presented to Open.
	Name() string
	ctxio.Writer
	// ctxio.Reader
	ctxio.ReaderAt
	io.Seeker
	ctxio.Closer

	// Truncate the file.
	Truncate(ctx context.Context, size int64) error
}

// Change abstract the FileInfo change related operations in a storage-agnostic
// interface as an extension to the Basic interface
type Change interface {
	// Chmod changes the mode of the named file to mode. If the file is a
	// symbolic link, it changes the mode of the link's target.
	Chmod(ctx context.Context, name string, mode os.FileMode) error
	// Lchown changes the numeric uid and gid of the named file. If the file is
	// a symbolic link, it changes the uid and gid of the link itself.
	Lchown(ctx context.Context, name string, uid, gid int) error
	// Chtimes changes the access and modification times of the named file,
	// similar to the Unix utime() or utimes() functions.
	//
	// The underlying filesystem may truncate or round the values to a less
	// precise time unit.
	Chtimes(ctx context.Context, name string, atime time.Time, mtime time.Time) error
}