// FUSE directory tree, for servers that wish to use it with the service loop.

package fs

import (
	"context"
	"os"
	pathpkg "path"
	"strings"
	"syscall"

	"bazil.org/fuse"
)

// A Tree implements a basic read-only directory tree for FUSE.
// The Nodes contained in it may still be writable.
type Tree struct {
	tree
}

func (t *Tree) Root() (Node, error) {
	return &t.tree, nil
}

// Add adds the path to the tree, resolving to the given node.
// If path or a prefix of path has already been added to the tree,
// Add panics.
//
// Add is only safe to call before starting to serve requests.
func (t *Tree) Add(path string, node Node) {
	path = pathpkg.Clean("/" + path)[1:]
	elems := strings.Split(path, "/")
	dir := Node(&t.tree)
	for i, elem := range elems {
		dt, ok := dir.(*tree)
		if !ok {
			panic("fuse: Tree.Add for " + strings.Join(elems[:i], "/") + " and " + path)
		}
		n := dt.lookup(elem)
		if n != nil {
			if i+1 == len(elems) {
				panic("fuse: Tree.Add for " + path + " conflicts with " + elem)
			}
			dir = n
		} else {
			if i+1 == len(elems) {
				dt.add(elem, node)
			} else {
				dir = &tree{}
				dt.add(elem, dir)
			}
		}
	}
}

type treeDir struct {
	name string
	node Node
}

type tree struct {
	dir []treeDir
}

func (t *tree) lookup(name string) Node {
	for _, d := range t.dir {
		if d.name == name {
			return d.node
		}
	}
	return nil
}

func (t *tree) add(name string, n Node) {
	t.dir = append(t.dir, treeDir{name, n})
}

func (t *tree) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0o555
	return nil
}

func (t *tree) Lookup(ctx context.Context, name string) (Node, error) {
	n := t.lookup(name)
	if n != nil {
		return n, nil
	}
	return nil, syscall.ENOENT
}

func (t *tree) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	var out []fuse.Dirent
	for _, d := range t.dir {
		out = append(out, fuse.Dirent{Name: d.name})
	}
	return out, nil
}
