package fs

import (
	"io"
	"os"
	"syscall"

	"github.com/barakmich/kubelwagen"
	"github.com/hanwen/go-fuse/fuse"
)

func (fs *LocalFs) handleFile(r *kubelwagen.Request) *kubelwagen.Response {
	switch r.Method {
	case kubelwagen.MethodOpen:
		return fs.open(r)
	case kubelwagen.MethodCreate:
		return fs.create(r)
	case kubelwagen.MethodFileRead:
		return fs.fileread(r)
	case kubelwagen.MethodFileWrite:
		return fs.filewrite(r)
	case kubelwagen.MethodFileRelease:
		return fs.filerelease(r)
	case kubelwagen.MethodFileFlock:
		return fs.fileflock(r)
	case kubelwagen.MethodFileGetAttr:
		return fs.filegetattr(r)
	case kubelwagen.MethodFileFsync:
		return fs.filefsync(r)
	case kubelwagen.MethodFileTruncate:
		return fs.filetruncate(r)
	case kubelwagen.MethodFileChown:
		return fs.filechown(r)
	case kubelwagen.MethodFileChmod:
		return fs.filechmod(r)
	case kubelwagen.MethodFileAllocate:
		return fs.fileallocate(r)
	default:
		return &kubelwagen.Response{
			ID:   r.ID,
			Code: fuse.ENOSYS,
		}
	}
}

func (fs *LocalFs) open(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	fh := fs.nextfile
	fs.nextfile++
	f, err := os.OpenFile(fs.getPath(r), int(r.Flags), 0)
	if err != nil {
		return kubelwagen.ErrorResp(r, err)
	}
	fs.openfiles[fh] = f
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       fuse.OK,
		FileHandle: fh,
	}
}

func (fs *LocalFs) create(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	fh := fs.nextfile
	fs.nextfile++
	f, err := os.OpenFile(fs.getPath(r), int(r.Flags)|os.O_CREATE, os.FileMode(r.Mode))
	if err != nil {
		return kubelwagen.ErrorResp(r, err)
	}
	fs.openfiles[fh] = f
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       fuse.OK,
		FileHandle: fh,
	}
}

func (fs *LocalFs) fileflock(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	code := fuse.ToStatus(syscall.Flock(int(f.Fd()), int(r.Flags)))

	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       code,
		FileHandle: r.FileHandle,
	}
}

func (fs *LocalFs) filegetattr(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	var at *fuse.Attr
	fi, err := f.Stat()
	code := fuse.ToStatus(err)
	if err == nil {
		at = fuse.ToAttr(fi)
	}
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       code,
		FileHandle: r.FileHandle,
		Stat:       at,
	}
}

func (fs *LocalFs) filefsync(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	code := fuse.ToStatus(f.Sync())
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       code,
		FileHandle: r.FileHandle,
	}
}

func (fs *LocalFs) filetruncate(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	code := fuse.ToStatus(syscall.Ftruncate(int(f.Fd()), int64(r.Size)))
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       code,
		FileHandle: r.FileHandle,
	}
}

func (fs *LocalFs) filechown(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	code := fuse.ToStatus(f.Chown(int(r.UID), int(r.GID)))
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       code,
		FileHandle: r.FileHandle,
	}
}

func (fs *LocalFs) filechmod(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	code := fuse.ToStatus(f.Chmod(os.FileMode(r.Mode)))
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       code,
		FileHandle: r.FileHandle,
	}
}

//TODO(barakmich): Linux only; support OS X
func (fs *LocalFs) fileallocate(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	code := fuse.ToStatus(syscall.Fallocate(int(f.Fd()), r.Mode, r.Offset, int64(r.Size)))
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       code,
		FileHandle: r.FileHandle,
	}
}

func (fs *LocalFs) fileread(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	buf := make([]byte, r.Size)
	n, err := f.ReadAt(buf, r.Offset)
	if err != nil && err != io.EOF {
		return kubelwagen.ErrorResp(r, err)
	}
	buf = buf[:n]
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       fuse.OK,
		FileHandle: r.FileHandle,
		Data:       buf,
	}
}

func (fs *LocalFs) filewrite(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	n, err := f.WriteAt(r.Data, r.Offset)
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       fuse.ToStatus(err),
		FileHandle: r.FileHandle,
		WriteSize:  n,
	}
}

func (fs *LocalFs) filerelease(r *kubelwagen.Request) *kubelwagen.Response {
	fs.Lock()
	defer fs.Unlock()
	f, ok := fs.openfiles[r.FileHandle]
	if !ok {
		return kubelwagen.ErrorResp(r, os.ErrClosed)
	}
	err := f.Close()
	delete(fs.openfiles, r.FileHandle)
	return &kubelwagen.Response{
		ID:         r.ID,
		Code:       fuse.ToStatus(err),
		FileHandle: r.FileHandle,
	}
}
