// Package fs is a generic file system interface for rclone object storage systems
package fs

import (
	"context"
	"errors"
	"fmt"
	"io"
	"math"
	"time"
)

// Constants
const (
	// ModTimeNotSupported is a very large precision value to show
	// mod time isn't supported on this Fs
	ModTimeNotSupported = 100 * 365 * 24 * time.Hour
	// MaxLevel is a sentinel representing an infinite depth for listings
	MaxLevel = math.MaxInt32
	// The suffix added to a translated symbolic link
	LinkSuffix = ".rclonelink"
)

// Globals
var (
	// ErrorNotFoundInConfigFile is returned by NewFs if not found in config file
	ErrorNotFoundInConfigFile        = errors.New("didn't find section in config file")
	ErrorCantPurge                   = errors.New("can't purge directory")
	ErrorCantCopy                    = errors.New("can't copy object - incompatible remotes")
	ErrorCantMove                    = errors.New("can't move object - incompatible remotes")
	ErrorCantDirMove                 = errors.New("can't move directory - incompatible remotes")
	ErrorCantUploadEmptyFiles        = errors.New("can't upload empty files to this remote")
	ErrorDirExists                   = errors.New("can't copy directory - destination already exists")
	ErrorCantSetModTime              = errors.New("can't set modified time")
	ErrorCantSetModTimeWithoutDelete = errors.New("can't set modified time without deleting existing object")
	ErrorDirNotFound                 = errors.New("directory not found")
	ErrorObjectNotFound              = errors.New("object not found")
	ErrorLevelNotSupported           = errors.New("level value not supported")
	ErrorListAborted                 = errors.New("list aborted")
	ErrorListBucketRequired          = errors.New("bucket or container name is needed in remote")
	ErrorIsFile                      = errors.New("is a file not a directory")
	ErrorIsDir                       = errors.New("is a directory not a file")
	ErrorNotAFile                    = errors.New("is not a regular file")
	ErrorNotDeleting                 = errors.New("not deleting files as there were IO errors")
	ErrorNotDeletingDirs             = errors.New("not deleting directories as there were IO errors")
	ErrorOverlapping                 = errors.New("can't sync or move files on overlapping remotes (try excluding the destination with a filter rule)")
	ErrorDirectoryNotEmpty           = errors.New("directory not empty")
	ErrorImmutableModified           = errors.New("immutable file modified")
	ErrorPermissionDenied            = errors.New("permission denied")
	ErrorCantShareDirectories        = errors.New("this backend can't share directories with link")
	ErrorNotImplemented              = errors.New("optional feature not implemented")
	ErrorCommandNotFound             = errors.New("command not found")
	ErrorFileNameTooLong             = errors.New("file name too long")
	ErrorCantListRoot                = errors.New("can't list root")
	ErrorFileTooSmall                = errors.New("file too small for multipart upload")
)

// FileTooSmallError is returned by OpenChunkWriter when a file is below the
// backend's minimum size for multipart uploads. It wraps ErrorFileTooSmall
// and carries the minimum size so callers can retry with a larger file.
type FileTooSmallError struct {
	MinSize int64
}

// Error implements the error interface.
func (e *FileTooSmallError) Error() string {
	return fmt.Sprintf("file too small for multipart upload (minimum %d bytes)", e.MinSize)
}

// Unwrap returns ErrorFileTooSmall so errors.Is works.
func (e *FileTooSmallError) Unwrap() error {
	return ErrorFileTooSmall
}

// CheckClose is a utility function used to check the return from
// Close in a defer statement.
func CheckClose(c io.Closer, err *error) {
	cerr := c.Close()
	if *err == nil {
		*err = cerr
	}
}

// FileExists returns true if a file remote exists.
// If remote is a directory, FileExists returns false.
func FileExists(ctx context.Context, fs Fs, remote string) (bool, error) {
	_, err := fs.NewObject(ctx, remote)
	if err != nil {
		if err == ErrorObjectNotFound || err == ErrorNotAFile || err == ErrorPermissionDenied {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

// GetModifyWindow calculates the maximum modify window between the given Fses
// and the Config.ModifyWindow parameter.
func GetModifyWindow(ctx context.Context, fss ...Info) time.Duration {
	window := time.Duration(GetConfig(ctx).ModifyWindow)
	for _, f := range fss {
		if f != nil {
			precision := f.Precision()
			if precision == ModTimeNotSupported {
				return ModTimeNotSupported
			}
			if precision > window {
				window = precision
			}
		}
	}
	return window
}
