// This makes the open test suite. It tries to open a file (existing
// or not existing) with all possible file modes and writes a test
// matrix.
//
// The behaviour is as run on Linux, with the small modification that
// O_TRUNC with O_RDONLY does **not** truncate the file.
//
// Run with go generate (defined in vfs.go)
//
//go:build none

// FIXME include read too?

package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"github.com/rclone/rclone/lib/file"
)

// Interprets err into a vfs error
func whichError(err error) string {
	switch err {
	case nil:
		return "nil"
	case io.EOF:
		return "io.EOF"
	case os.ErrInvalid:
		return "EINVAL"
	}
	s := err.Error()
	switch {
	case strings.Contains(s, "no such file or directory"):
		return "ENOENT"
	case strings.Contains(s, "bad file descriptor"):
		return "EBADF"
	case strings.Contains(s, "file exists"):
		return "EEXIST"
	}
	log.Fatalf("Unknown error: %v", err)
	return ""
}

const accessModeMask = (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)

// test Opening, reading and writing the file handle with the flags given
func test(fileName string, flags int, mode string) {
	// first try with file not existing
	_, err := os.Stat(fileName)
	if !os.IsNotExist(err) {
		log.Fatalf("File must not exist")
	}
	f, openNonExistentErr := file.OpenFile(fileName, flags, 0666)

	var readNonExistentErr error
	var writeNonExistentErr error
	if openNonExistentErr == nil {
		// read some bytes
		buf := []byte{0, 0}
		_, readNonExistentErr = f.Read(buf)

		// write some bytes
		_, writeNonExistentErr = f.Write([]byte("hello"))

		// close
		err = f.Close()
		if err != nil {
			log.Fatalf("failed to close: %v", err)
		}
	}

	// write the file
	f, err = file.Create(fileName)
	if err != nil {
		log.Fatalf("failed to create: %v", err)
	}
	n, err := f.Write([]byte("hello"))
	if n != 5 || err != nil {
		log.Fatalf("failed to write n=%d: %v", n, err)
	}
	// close
	err = f.Close()
	if err != nil {
		log.Fatalf("failed to close: %v", err)
	}

	// then open file and try with file existing

	f, openExistingErr := file.OpenFile(fileName, flags, 0666)
	var readExistingErr error
	var writeExistingErr error
	if openExistingErr == nil {
		// read some bytes
		buf := []byte{0, 0}
		_, readExistingErr = f.Read(buf)

		// write some bytes
		_, writeExistingErr = f.Write([]byte("HEL"))

		// close
		err = f.Close()
		if err != nil {
			log.Fatalf("failed to close: %v", err)
		}
	}

	// read the file
	f, err = file.Open(fileName)
	if err != nil {
		log.Fatalf("failed to open: %v", err)
	}
	var buf = make([]byte, 64)
	n, err = f.Read(buf)
	if err != nil && err != io.EOF {
		log.Fatalf("failed to read n=%d: %v", n, err)
	}
	err = f.Close()
	if err != nil {
		log.Fatalf("failed to close: %v", err)
	}
	contents := string(buf[:n])

	// remove file
	err = os.Remove(fileName)
	if err != nil {
		log.Fatalf("failed to remove: %v", err)
	}

	// http://pubs.opengroup.org/onlinepubs/7908799/xsh/open.html
	// The result of using O_TRUNC with O_RDONLY is undefined.
	// Linux seems to truncate the file, but we prefer to return EINVAL
	if (flags&accessModeMask) == os.O_RDONLY && flags&os.O_TRUNC != 0 {
		openNonExistentErr = os.ErrInvalid // EINVAL
		readNonExistentErr = nil
		writeNonExistentErr = nil
		openExistingErr = os.ErrInvalid // EINVAL
		readExistingErr = nil
		writeExistingErr = nil
		contents = "hello"
	}

	// output the struct
	fmt.Printf(`{
	flags: %s,
	what: %q,
	openNonExistentErr: %s,
	readNonExistentErr: %s,
	writeNonExistentErr: %s,
	openExistingErr: %s,
	readExistingErr: %s,
	writeExistingErr: %s,
	contents: %q,
},`,
		mode,
		mode,
		whichError(openNonExistentErr),
		whichError(readNonExistentErr),
		whichError(writeNonExistentErr),
		whichError(openExistingErr),
		whichError(readExistingErr),
		whichError(writeExistingErr),
		contents)
}

func main() {
	fmt.Printf(`// Code generated by make_open_tests.go - use go generate to rebuild - DO NOT EDIT

package vfs

import (
	"os"
	"io"
)

// openTest describes a test of OpenFile
type openTest struct{
	flags int
	what string
	openNonExistentErr error
	readNonExistentErr error
	writeNonExistentErr error
	openExistingErr error
	readExistingErr error
	writeExistingErr error
	contents string
}

// openTests is a suite of tests for OpenFile with all possible
// combination of flags.  This obeys Unix semantics even on Windows.
var openTests = []openTest{
`)
	f, err := os.CreateTemp("", "open-test")
	if err != nil {
		log.Fatal(err)
	}
	fileName := f.Name()
	_ = f.Close()
	err = os.Remove(fileName)
	if err != nil {
		log.Fatalf("failed to remove: %v", err)
	}
	for _, rwMode := range []int{os.O_RDONLY, os.O_WRONLY, os.O_RDWR} {
		flags0 := rwMode
		parts0 := []string{"os.O_RDONLY", "os.O_WRONLY", "os.O_RDWR"}[rwMode : rwMode+1]
		for _, appendMode := range []int{0, os.O_APPEND} {
			flags1 := flags0 | appendMode
			parts1 := parts0
			if appendMode != 0 {
				parts1 = append(parts1, "os.O_APPEND")
			}
			for _, createMode := range []int{0, os.O_CREATE} {
				flags2 := flags1 | createMode
				parts2 := parts1
				if createMode != 0 {
					parts2 = append(parts2, "os.O_CREATE")
				}
				for _, exclMode := range []int{0, os.O_EXCL} {
					flags3 := flags2 | exclMode
					parts3 := parts2
					if exclMode != 0 {
						parts3 = append(parts2, "os.O_EXCL")
					}
					for _, syncMode := range []int{0, os.O_SYNC} {
						flags4 := flags3 | syncMode
						parts4 := parts3
						if syncMode != 0 {
							parts4 = append(parts4, "os.O_SYNC")
						}
						for _, truncMode := range []int{0, os.O_TRUNC} {
							flags5 := flags4 | truncMode
							parts5 := parts4
							if truncMode != 0 {
								parts5 = append(parts5, "os.O_TRUNC")
							}
							textMode := strings.Join(parts5, "|")
							flags := flags5

							test(fileName, flags, textMode)
						}
					}
				}
			}
		}
	}
	fmt.Printf("\n}\n")
}
