// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fstest

import (
	
	
	
	
	
	
)

// A MapFS is a simple in-memory file system for use in tests,
// represented as a map from path names (arguments to Open)
// to information about the files or directories they represent.
//
// The map need not include parent directories for files contained
// in the map; those will be synthesized if needed.
// But a directory can still be included by setting the [MapFile.Mode]'s [fs.ModeDir] bit;
// this may be necessary for detailed control over the directory's [fs.FileInfo]
// or to create an empty directory.
//
// File system operations read directly from the map,
// so that the file system can be changed by editing the map as needed.
// An implication is that file system operations must not run concurrently
// with changes to the map, which would be a race.
// Another implication is that opening or reading a directory requires
// iterating over the entire map, so a MapFS should typically be used with not more
// than a few hundred entries or directory reads.
type MapFS map[string]*MapFile

// A MapFile describes a single file in a [MapFS].
type MapFile struct {
	Data    []byte      // file content
	Mode    fs.FileMode // fs.FileInfo.Mode
	ModTime time.Time   // fs.FileInfo.ModTime
	Sys     any         // fs.FileInfo.Sys
}

var _ fs.FS = MapFS(nil)
var _ fs.File = (*openMapFile)(nil)

// Open opens the named file.
func ( MapFS) ( string) (fs.File, error) {
	if !fs.ValidPath() {
		return nil, &fs.PathError{Op: "open", Path: , Err: fs.ErrNotExist}
	}
	 := []
	if  != nil && .Mode&fs.ModeDir == 0 {
		// Ordinary file
		return &openMapFile{, mapFileInfo{path.Base(), }, 0}, nil
	}

	// Directory, possibly synthesized.
	// Note that file can be nil here: the map need not contain explicit parent directories for all its files.
	// But file can also be non-nil, in case the user wants to set metadata for the directory explicitly.
	// Either way, we need to construct the list of children of this directory.
	var  []mapFileInfo
	var  string
	var  = make(map[string]bool)
	if  == "." {
		 = "."
		for ,  := range  {
			 := strings.Index(, "/")
			if  < 0 {
				if  != "." {
					 = append(, mapFileInfo{, })
				}
			} else {
				[[:]] = true
			}
		}
	} else {
		 = [strings.LastIndex(, "/")+1:]
		 :=  + "/"
		for ,  := range  {
			if strings.HasPrefix(, ) {
				 := [len():]
				 := strings.Index(, "/")
				if  < 0 {
					 = append(, mapFileInfo{, })
				} else {
					[[len():len()+]] = true
				}
			}
		}
		// If the directory name is not in the map,
		// and there are no children of the name in the map,
		// then the directory is treated as not existing.
		if  == nil &&  == nil && len() == 0 {
			return nil, &fs.PathError{Op: "open", Path: , Err: fs.ErrNotExist}
		}
	}
	for ,  := range  {
		delete(, .name)
	}
	for  := range  {
		 = append(, mapFileInfo{, &MapFile{Mode: fs.ModeDir | 0555}})
	}
	slices.SortFunc(, func(,  mapFileInfo) int {
		return strings.Compare(.name, .name)
	})

	if  == nil {
		 = &MapFile{Mode: fs.ModeDir | 0555}
	}
	return &mapDir{, mapFileInfo{, }, , 0}, nil
}

// fsOnly is a wrapper that hides all but the fs.FS methods,
// to avoid an infinite recursion when implementing special
// methods in terms of helpers that would use them.
// (In general, implementing these methods using the package fs helpers
// is redundant and unnecessary, but having the methods may make
// MapFS exercise more code paths when used in tests.)
type fsOnly struct{ fs.FS }

func ( MapFS) ( string) ([]byte, error) {
	return fs.ReadFile(fsOnly{}, )
}

func ( MapFS) ( string) (fs.FileInfo, error) {
	return fs.Stat(fsOnly{}, )
}

func ( MapFS) ( string) ([]fs.DirEntry, error) {
	return fs.ReadDir(fsOnly{}, )
}

func ( MapFS) ( string) ([]string, error) {
	return fs.Glob(fsOnly{}, )
}

type noSub struct {
	MapFS
}

func (noSub) () {} // not the fs.SubFS signature

func ( MapFS) ( string) (fs.FS, error) {
	return fs.Sub(noSub{}, )
}

// A mapFileInfo implements fs.FileInfo and fs.DirEntry for a given map file.
type mapFileInfo struct {
	name string
	f    *MapFile
}

func ( *mapFileInfo) () string               { return path.Base(.name) }
func ( *mapFileInfo) () int64                { return int64(len(.f.Data)) }
func ( *mapFileInfo) () fs.FileMode          { return .f.Mode }
func ( *mapFileInfo) () fs.FileMode          { return .f.Mode.Type() }
func ( *mapFileInfo) () time.Time         { return .f.ModTime }
func ( *mapFileInfo) () bool                { return .f.Mode&fs.ModeDir != 0 }
func ( *mapFileInfo) () any                   { return .f.Sys }
func ( *mapFileInfo) () (fs.FileInfo, error) { return , nil }

func ( *mapFileInfo) () string {
	return fs.FormatFileInfo()
}

// An openMapFile is a regular (non-directory) fs.File open for reading.
type openMapFile struct {
	path string
	mapFileInfo
	offset int64
}

func ( *openMapFile) () (fs.FileInfo, error) { return &.mapFileInfo, nil }

func ( *openMapFile) () error { return nil }

func ( *openMapFile) ( []byte) (int, error) {
	if .offset >= int64(len(.f.Data)) {
		return 0, io.EOF
	}
	if .offset < 0 {
		return 0, &fs.PathError{Op: "read", Path: .path, Err: fs.ErrInvalid}
	}
	 := copy(, .f.Data[.offset:])
	.offset += int64()
	return , nil
}

func ( *openMapFile) ( int64,  int) (int64, error) {
	switch  {
	case 0:
		// offset += 0
	case 1:
		 += .offset
	case 2:
		 += int64(len(.f.Data))
	}
	if  < 0 ||  > int64(len(.f.Data)) {
		return 0, &fs.PathError{Op: "seek", Path: .path, Err: fs.ErrInvalid}
	}
	.offset = 
	return , nil
}

func ( *openMapFile) ( []byte,  int64) (int, error) {
	if  < 0 ||  > int64(len(.f.Data)) {
		return 0, &fs.PathError{Op: "read", Path: .path, Err: fs.ErrInvalid}
	}
	 := copy(, .f.Data[:])
	if  < len() {
		return , io.EOF
	}
	return , nil
}

// A mapDir is a directory fs.File (so also an fs.ReadDirFile) open for reading.
type mapDir struct {
	path string
	mapFileInfo
	entry  []mapFileInfo
	offset int
}

func ( *mapDir) () (fs.FileInfo, error) { return &.mapFileInfo, nil }
func ( *mapDir) () error               { return nil }
func ( *mapDir) ( []byte) (int, error) {
	return 0, &fs.PathError{Op: "read", Path: .path, Err: fs.ErrInvalid}
}

func ( *mapDir) ( int) ([]fs.DirEntry, error) {
	 := len(.entry) - .offset
	if  == 0 &&  > 0 {
		return nil, io.EOF
	}
	if  > 0 &&  >  {
		 = 
	}
	 := make([]fs.DirEntry, )
	for  := range  {
		[] = &.entry[.offset+]
	}
	.offset += 
	return , nil
}