package winfsp

import (
	cgofuse "github.com/winfsp/cgofuse/fuse"

	"github.com/seaweedfs/seaweedfs/weed/glog"
	"github.com/seaweedfs/seaweedfs/weed/mount/meta_cache"
	"github.com/seaweedfs/seaweedfs/weed/util"
)

// notifier turns the mount's metadata events into the notifications Windows
// listens for. Nothing invalidates a Windows client's cache from this side, so
// without it a change made by another mount, the S3 gateway or the filer API
// stays invisible until the user refreshes by hand.
type notifier struct {
	host      *cgofuse.FileSystemHost
	fs        *WinFS
	mountRoot util.FullPath
}

// notify reports one applied event. Windows wants the path relative to the
// mount, and an action describing what happened to it; the adapter's own path
// cache is out of date for the same path.
func (n *notifier) notify(invalidation meta_cache.EntryInvalidation) {
	if n.host == nil {
		return
	}
	// A rename arrives as two invalidations, one vacating the old path and one
	// describing the new, so reporting RenamedTo here as well would send the
	// destination twice — and as a create even when a directory moved.
	if path, ok := relativeToMount(n.mountRoot, invalidation.Path); ok {
		isDirectory := invalidation.WasDirectory || invalidation.Entry.GetIsDirectory()
		n.fs.invalidatePath(path, isDirectory)
		n.send(path, n.action(invalidation))
	}
}

func (n *notifier) send(path string, action uint32) {
	if !n.host.Notify(path, action) {
		// A rejected notification only costs a stale view until the client
		// looks again, so it is not worth failing an operation over.
		glog.V(4).Infof("winfsp notify %s action %d rejected", path, action)
	}
}

// action picks what to tell Windows happened. It cannot distinguish a created
// entry from a modified one, and Windows treats an unexpected create on an
// existing name as a refresh, so create is the safe report.
func (n *notifier) action(invalidation meta_cache.EntryInvalidation) uint32 {
	if invalidation.Deleted || invalidation.Entry == nil {
		// The entry is gone, so only WasDirectory still says what it was, and
		// Windows watches directory and file removals through different
		// filters.
		if invalidation.WasDirectory {
			return cgofuse.NOTIFY_RMDIR
		}
		return cgofuse.NOTIFY_UNLINK
	}
	if invalidation.Entry.GetIsDirectory() {
		return cgofuse.NOTIFY_MKDIR
	}
	return cgofuse.NOTIFY_CREATE
}
