// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

package notify

import (
	"fmt"
	"strings"
)

// Event represents the type of filesystem action.
//
// Number of available event values is dependent on the target system or the
// watcher implmenetation used (e.g. it's possible to use either kqueue or
// FSEvents on Darwin).
//
// Please consult documentation for your target platform to see list of all
// available events.
type Event uint32

// Create, Remove, Write and Rename are the only event values guaranteed to be
// present on all platforms.
const (
	Create = osSpecificCreate
	Remove = osSpecificRemove
	Write  = osSpecificWrite
	Rename = osSpecificRename

	// All is handful alias for all platform-independent event values.
	All = Create | Remove | Write | Rename
)

const internal = recursive | omit

// String implements fmt.Stringer interface.
func (e Event) String() string {
	var s []string
	for _, strmap := range []map[Event]string{estr, osestr} {
		for ev, str := range strmap {
			if e&ev == ev {
				s = append(s, str)
			}
		}
	}
	return strings.Join(s, "|")
}

// EventInfo describes an event reported by the underlying filesystem notification
// subsystem.
//
// It always describes single event, even if the OS reported a coalesced action.
// Reported path is absolute and clean.
//
// For non-recursive watchpoints its base is always equal to the path passed
// to corresponding Watch call.
//
// The value of Sys if system-dependent and can be nil.
//
// # Sys
//
// Under Darwin (FSEvents) Sys() always returns a non-nil *notify.FSEvent value,
// which is defined as:
//
//	type FSEvent struct {
//	    Path  string // real path of the file or directory
//	    ID    uint64 // ID of the event (FSEventStreamEventId)
//	    Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
//	}
//
// For possible values of Flags see Darwin godoc for notify or FSEvents
// documentation for FSEventStreamEventFlags constants:
//
//	https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags
//
// Under Linux (inotify) Sys() always returns a non-nil *unix.InotifyEvent
// value, defined as:
//
//	type InotifyEvent struct {
//	    Wd     int32    // Watch descriptor
//	    Mask   uint32   // Mask describing event
//	    Cookie uint32   // Unique cookie associating related events (for rename(2))
//	    Len    uint32   // Size of name field
//	    Name   [0]uint8 // Optional null-terminated name
//	}
//
// More information about inotify masks and the usage of inotify_event structure
// can be found at:
//
//	http://man7.org/linux/man-pages/man7/inotify.7.html
//
// Under Darwin, DragonFlyBSD, FreeBSD, NetBSD, OpenBSD (kqueue) Sys() always
// returns a non-nil *notify.Kevent value, which is defined as:
//
//	type Kevent struct {
//	    Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure
//	    FI     os.FileInfo       // FI describes file/dir
//	}
//
// More information about syscall.Kevent_t can be found at:
//
//	https://www.freebsd.org/cgi/man.cgi?query=kqueue
//
// Under Windows (ReadDirectoryChangesW) Sys() always returns nil. The documentation
// of watcher's WinAPI function can be found at:
//
//	https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx
type EventInfo interface {
	Event() Event     // event value for the filesystem action
	Path() string     // real path of the file or directory
	Sys() interface{} // underlying data source (can return nil)
}

type isDirer interface {
	isDir() (bool, error)
}

var _ fmt.Stringer = (*event)(nil)
var _ isDirer = (*event)(nil)

// String implements fmt.Stringer interface.
func (e *event) String() string {
	return e.Event().String() + `: "` + e.Path() + `"`
}

var estr = map[Event]string{
	Create: "notify.Create",
	Remove: "notify.Remove",
	Write:  "notify.Write",
	Rename: "notify.Rename",
	// Display name for recursive event is added only for debugging
	// purposes. It's an internal event after all and won't be exposed to the
	// user. Having Recursive event printable is helpful, e.g. for reading
	// testing failure messages:
	//
	//    --- FAIL: TestWatchpoint (0.00 seconds)
	//    watchpoint_test.go:64: want diff=[notify.Remove notify.Create|notify.Remove];
	//    got [notify.Remove notify.Remove|notify.Create] (i=1)
	//
	// Yup, here the diff have Recursive event inside. Go figure.
	recursive: "recursive",
	omit:      "omit",
}
