package fs

import (
	"io"
	"io/ioutil"
	"os"
	"syscall"
)

// Interface is the filesystem interface type.
type Interface interface {
	ReadDir(string) ([]os.FileInfo, error)
	ReadDirNames(string) ([]string, error)
	ReadDirCount(string) (int, error)
	ReadFile(string) ([]byte, error)
	Lstat(string, *syscall.Stat_t) error
	Stat(string, *syscall.Stat_t) error
	Open(string) (io.ReadWriteCloser, error)
}

type realFS struct{}

// FS is the way you should access the filesystem.
var fs Interface = realFS{}

func (realFS) ReadDir(path string) ([]os.FileInfo, error) {
	return ioutil.ReadDir(path)
}

func (realFS) ReadDirNames(path string) ([]string, error) {
	fh, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer fh.Close()
	return fh.Readdirnames(-1)
}

func (realFS) ReadFile(path string) ([]byte, error) {
	return ioutil.ReadFile(path)
}

func (realFS) Lstat(path string, stat *syscall.Stat_t) error {
	return syscall.Lstat(path, stat)
}

func (realFS) Stat(path string, stat *syscall.Stat_t) error {
	return syscall.Stat(path, stat)
}

func (realFS) Open(path string) (io.ReadWriteCloser, error) {
	return os.Open(path)
}

// trampolines here to allow users to do fs.ReadDir etc

// ReadDir see ioutil.ReadDir
func ReadDir(path string) ([]os.FileInfo, error) {
	return fs.ReadDir(path)
}

// ReadDirNames see os.File.ReadDirNames
func ReadDirNames(path string) ([]string, error) {
	return fs.ReadDirNames(path)
}

// ReadDirCount is an optimized way to call len(ReadDirNames)
func ReadDirCount(path string) (int, error) {
	return fs.ReadDirCount(path)
}

// ReadFile see ioutil.ReadFile
func ReadFile(path string) ([]byte, error) {
	return fs.ReadFile(path)
}

// Lstat see syscall.Lstat
func Lstat(path string, stat *syscall.Stat_t) error {
	return fs.Lstat(path, stat)
}

// Stat see syscall.Stat
func Stat(path string, stat *syscall.Stat_t) error {
	return fs.Stat(path, stat)
}

// Open see os.Open
func Open(path string) (io.ReadWriteCloser, error) {
	return fs.Open(path)
}

// Mock is used to switch out the filesystem for a mock.
func Mock(mock Interface) {
	fs = mock
}

// Restore puts back the real filesystem.
func Restore() {
	fs = realFS{}
}
