// Copyright (C) 2019 Marius Schellenberger

package authdav

import (
	"context"
	"errors"
	"golang.org/x/net/webdav"
	"os"
)

// ErrWonly indicates forbidden operation
var ErrWonly = errors.New("authdav: only file write once operations permitted")

const writeFlag = os.O_RDWR | os.O_CREATE | os.O_TRUNC

// WriteOnlyOnceFileSystem is a file write only wrapper for a webdav.FileSystem
type WriteOnlyOnceFileSystem struct {
	Filters []Filter
	fs      webdav.FileSystem
}

// NewWriteOnlyOnceFileSystem returns a new WriteOnlyOnceFileSystem wrapping a webdav.FileSystem
func NewWriteOnlyOnceFileSystem(fs webdav.FileSystem) *WriteOnlyOnceFileSystem {
	return &WriteOnlyOnceFileSystem{fs: fs}
}

// Mkdir is not permitted
func (fs *WriteOnlyOnceFileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
	return ErrWonly
}

// OpenFile allows write only once operations
func (fs *WriteOnlyOnceFileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
	fi, err := fs.Stat(ctx, name)
	if err == nil && fi.Mode().IsRegular() {
		f, ok := fi.(*FileInfo)
		if ok && flag == writeFlag && f.RealSize() > 0 {
			return nil, ErrWonly
		}
	}
	for _, filter := range fs.Filters {
		err = filter.Filter(name)
		if err != nil {
			return nil, err
		}
	}
	f, err := fs.fs.OpenFile(ctx, name, flag, perm)
	if err != nil {
		return nil, err
	}
	return &File{f}, nil
}

// RemoveAll only permits deleting empty files
func (fs *WriteOnlyOnceFileSystem) RemoveAll(ctx context.Context, name string) error {
	fi, err := fs.fs.Stat(ctx, name)
	if err != nil {
		return err
	}
	if fi.Mode().IsRegular() && fi.Size() == 0 {
		return fs.fs.RemoveAll(ctx, name)
	}
	return ErrWonly
}

// Rename is not permitted
func (fs *WriteOnlyOnceFileSystem) Rename(ctx context.Context, oldName, newName string) error {
	return ErrWonly
}

// Stat returns os.FileInfo wrapped in FileInfo
func (fs *WriteOnlyOnceFileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) {
	fi, err := fs.fs.Stat(ctx, name)
	if err != nil {
		return nil, err
	}
	return &FileInfo{fi}, nil
}
