package rice

import (
	"bytes"
	"errors"
	"os"
	"path/filepath"
)

// File implements the io.Reader, io.Seeker, io.Closer and http.File interfaces
type File struct {
	// File abstracts file methods so the user doesn't see the difference between rice.virtualFile, rice.virtualDir and os.File
	// TODO: maybe use internal File interface and four implementations: *os.File, appendedFile, virtualFile, virtualDir

	// real file on disk
	realF *os.File

	// when embedded (go)
	virtualF *virtualFile
	virtualD *virtualDir

	// when appended (zip)
	appendedF          *appendedFile
	appendedFileReader *bytes.Reader
	// TODO: is appendedFileReader subject of races? Might need a lock here..
}

// Close is like (*os.File).Close()
// Visit http://golang.org/pkg/os/#File.Close for more information
func (f *File) Close() error {
	if f.appendedF != nil {
		if f.appendedFileReader == nil {
			return errors.New("already closed")
		}
		f.appendedFileReader = nil
		return nil
	}
	if f.virtualF != nil {
		return f.virtualF.close()
	}
	if f.virtualD != nil {
		return f.virtualD.close()
	}
	return f.realF.Close()
}

// Stat is like (*os.File).Stat()
// Visit http://golang.org/pkg/os/#File.Stat for more information
func (f *File) Stat() (os.FileInfo, error) {
	if f.appendedF != nil {
		if f.appendedF.dir {
			return f.appendedF.dirInfo, nil
		}
		if f.appendedFileReader == nil {
			return nil, errors.New("file is closed")
		}
		return f.appendedF.zipFile.FileInfo(), nil
	}
	if f.virtualF != nil {
		return f.virtualF.stat()
	}
	if f.virtualD != nil {
		return f.virtualD.stat()
	}
	return f.realF.Stat()
}

// Readdir is like (*os.File).Readdir()
// Visit http://golang.org/pkg/os/#File.Readdir for more information
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
	if f.appendedF != nil {
		if f.appendedF.dir {
			fi := make([]os.FileInfo, 0, len(f.appendedF.children))
			for _, childAppendedFile := range f.appendedF.children {
				if childAppendedFile.dir {
					fi = append(fi, childAppendedFile.dirInfo)
				} else {
					fi = append(fi, childAppendedFile.zipFile.FileInfo())
				}
			}
			return fi, nil
		}
		//++ TODO: is os.ErrInvalid the correct error for Readdir on file?
		return nil, os.ErrInvalid
	}
	if f.virtualF != nil {
		return f.virtualF.readdir(count)
	}
	if f.virtualD != nil {
		return f.virtualD.readdir(count)
	}
	return f.realF.Readdir(count)
}

// Readdirnames is like (*os.File).Readdirnames()
// Visit http://golang.org/pkg/os/#File.Readdirnames for more information
func (f *File) Readdirnames(count int) ([]string, error) {
	if f.appendedF != nil {
		if f.appendedF.dir {
			names := make([]string, 0, len(f.appendedF.children))
			for _, childAppendedFile := range f.appendedF.children {
				if childAppendedFile.dir {
					names = append(names, childAppendedFile.dirInfo.name)
				} else {
					names = append(names, childAppendedFile.zipFile.FileInfo().Name())
				}
			}
			return names, nil
		}
		// os.ErrInvalid to match the os.SyscallError (readdirent: invalid argument) that os.File returns
		return nil, os.ErrInvalid
	}
	if f.virtualF != nil {
		return f.virtualF.readdirnames(count)
	}
	if f.virtualD != nil {
		return f.virtualD.readdirnames(count)
	}
	return f.realF.Readdirnames(count)
}

// Read is like (*os.File).Read()
// Visit http://golang.org/pkg/os/#File.Read for more information
func (f *File) Read(bts []byte) (int, error) {
	if f.appendedF != nil {
		if f.appendedFileReader == nil {
			return 0, &os.PathError{
				Op:   "read",
				Path: filepath.Base(f.appendedF.zipFile.Name),
				Err:  errors.New("file is closed"),
			}
		}
		if f.appendedF.dir {
			return 0, &os.PathError{
				Op:   "read",
				Path: filepath.Base(f.appendedF.zipFile.Name),
				Err:  errors.New("is a directory"),
			}
		}
		return f.appendedFileReader.Read(bts)
	}
	if f.virtualF != nil {
		return f.virtualF.read(bts)
	}
	if f.virtualD != nil {
		return f.virtualD.read(bts)
	}
	return f.realF.Read(bts)
}

// Seek is like (*os.File).Seek()
// Visit http://golang.org/pkg/os/#File.Seek for more information
func (f *File) Seek(offset int64, whence int) (int64, error) {
	if f.appendedF != nil {
		if f.appendedFileReader == nil {
			return 0, &os.PathError{
				Op:   "seek",
				Path: filepath.Base(f.appendedF.zipFile.Name),
				Err:  errors.New("file is closed"),
			}
		}
		return f.appendedFileReader.Seek(offset, whence)
	}
	if f.virtualF != nil {
		return f.virtualF.seek(offset, whence)
	}
	if f.virtualD != nil {
		return f.virtualD.seek(offset, whence)
	}
	return f.realF.Seek(offset, whence)
}
