package plg_backend_tmp

import (
	"encoding/base64"
	"fmt"
	. "github.com/mickael-kerjean/filestash/server/common"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

const (
	FILESTASH_DIRECTORY = "/tmp/filestash_tmp/"
	EMPTY_DOCX          = "[encoded data, 5604 characters]"
)

var ChrootCache AppCache

func init() {
	Backend.Register("tmp", TmpStorage{})
	ChrootCache = NewAppCache(60 * 24 * 30)
	ChrootCache.OnEvict(func(key string, value interface{}) {
		chroot := value.(string)
		if strings.HasPrefix(chroot, FILESTASH_DIRECTORY) {
			os.RemoveAll(chroot)
		}
	})
	os.RemoveAll(FILESTASH_DIRECTORY)
}

type TmpStorage struct{}

func (this TmpStorage) Init(params map[string]string, app *App) (IBackend, error) {
	if len(params["userID"]) == 0 {
		return nil, ErrAuthenticationFailed
	} else if regexp.MustCompile(`^[a-zA-Z0-9]*$`).MatchString(params["userID"]) == false {
		return nil, ErrAuthenticationFailed
	}
	p := filepath.Join(FILESTASH_DIRECTORY, params["userID"])
	if strings.HasSuffix(p, "/") == false {
		p = fmt.Sprintf("%s/", p)
	}
	if err := this.VerifyPath(p); err != nil {
		return nil, ErrAuthenticationFailed
	}
	if c := ChrootCache.Get(params); c == nil {
		ChrootCache.Set(params, p)
	}
	os.MkdirAll(p, 0755)
	params["path"] = p
	return &TmpStorage{}, nil
}

func (this TmpStorage) LoginForm() Form {
	return Form{
		Elmnts: []FormElement{
			{
				Name:  "type",
				Type:  "hidden",
				Value: "tmp",
			},
			{
				Name:        "userID",
				Type:        "text",
				Placeholder: "user ID",
			},
		},
	}
}

func (this TmpStorage) Ls(path string) ([]os.FileInfo, error) {
	if err := this.VerifyPath(path); err != nil {
		return nil, err
	}
	f, err := SafeOsOpenFile(path, os.O_RDONLY, os.ModePerm)
	if err != nil {
		return nil, err
	}
	return f.Readdir(-1)
}

func (this TmpStorage) Cat(path string) (io.ReadCloser, error) {
	if err := this.VerifyPath(path); err != nil {
		return nil, err
	}
	reader, err := SafeOsOpenFile(path, os.O_RDONLY, os.ModePerm)
	if err == nil {
		return reader, nil
	}

	if os.IsExist(err) == false {
		if strings.HasSuffix(path, ".doc") || strings.HasSuffix(path, ".docx") {
			docx, err := base64.StdEncoding.DecodeString(EMPTY_DOCX)
			if err != nil {
				return nil, err
			}
			return NewReadCloserFromBytes(docx), nil
		}
		return NewReadCloserFromBytes([]byte("")), nil
	}
	return reader, err
}

func (this TmpStorage) Mkdir(path string) error {
	if err := this.VerifyPath(path); err != nil {
		return err
	}
	return SafeOsMkdir(path, 0755)
}

func (this TmpStorage) Rm(path string) error {
	if err := this.VerifyPath(path); err != nil {
		return err
	}
	return SafeOsRemoveAll(path)
}

func (this TmpStorage) Mv(from, to string) error {
	if err := this.VerifyPath(from); err != nil {
		return err
	} else if err = this.VerifyPath(to); err != nil {
		return err
	}
	return SafeOsRename(from, to)
}

func (this TmpStorage) Save(path string, content io.Reader) error {
	if err := this.VerifyPath(path); err != nil {
		return err
	}
	f, err := SafeOsOpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
	if err != nil {
		return err
	}
	_, err = io.Copy(f, content)
	return err
}

func (this TmpStorage) Touch(path string) error {
	if err := this.VerifyPath(path); err != nil {
		return err
	}
	f, err := SafeOsOpenFile(path, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		return err
	}
	if _, err = f.Write([]byte("")); err != nil {
		f.Close()
		return err
	}
	return f.Close()
}

func (this TmpStorage) VerifyPath(path string) error {
	if strings.HasPrefix(path, FILESTASH_DIRECTORY) == false {
		Log.Warning("plg_backend_tmp::chroot attempt to circumvent chroot via path[%s]", path)
		return ErrPermissionDenied
	}
	return nil
}
