#+build darwin, netbsd, openbsd, freebsd
package kqueue

when ODIN_OS == .Darwin {
	foreign import lib "system:System"
} else {
	foreign import lib "system:c"
}

import "base:intrinsics"

import "core:c"
import "core:sys/posix"

KQ :: posix.FD

kqueue :: proc() -> (kq: KQ, err: posix.Errno) {
	kq = _kqueue()
	if kq == -1 {
		err = posix.errno()
	}
	return
}

kevent :: proc(kq: KQ, change_list: []KEvent, event_list: []KEvent, timeout: ^posix.timespec) -> (n_events: c.int, err: posix.Errno) {
	n_events = _kevent(
		kq,
		raw_data(change_list),
		c.int(len(change_list)),
		raw_data(event_list),
		c.int(len(event_list)),
		timeout,
	)
	if n_events == -1 {
		n_events = 0
		err = posix.errno()
	}
	return
}

Flag :: enum _Flags_Backing {
	Add      = log2(0x0001), // Add event to kq (implies .Enable).
	Delete   = log2(0x0002), // Delete event from kq.
	Enable   = log2(0x0004), // Enable event.
	Disable  = log2(0x0008), // Disable event (not reported).
	One_Shot = log2(0x0010), // Only report one occurrence.
	Clear    = log2(0x0020), // Clear event state after reporting.
	Receipt  = log2(0x0040), // Force immediate event output.
	Dispatch = log2(0x0080), // Disable event after reporting.

	Error    = log2(0x4000), // Error, data contains errno.
	EOF      = log2(0x8000), // EOF detected.
}
Flags :: bit_set[Flag; _Flags_Backing]

Filter :: enum _Filter_Backing {
	Read   = _FILTER_READ,   // Check for read availability on the file descriptor.
	Write  = _FILTER_WRITE,  // Check for write availability on the file descriptor.
	AIO    = _FILTER_AIO,    // Attached to AIO requests.
	VNode  = _FILTER_VNODE,  // Check for changes to the subject file.
	Proc   = _FILTER_PROC,   // Check for changes to the subject process.
	Signal = _FILTER_SIGNAL, // Check for signals delivered to the process.
	Timer  = _FILTER_TIMER,  // Timers.
	User   = _FILTER_USER,   // User events.
}

RW_Flag :: enum u32 {
	Low_Water_Mark = log2(0x00000001),
}
RW_Flags :: bit_set[RW_Flag; u32]

VNode_Flag :: enum u32 {
	Delete = log2(0x00000001), // Deleted.
	Write  = log2(0x00000002), // Contents changed.
	Extend = log2(0x00000004), // Size increased.
	Attrib = log2(0x00000008), // Attributes changed.
	Link   = log2(0x00000010), // Link count changed.
	Rename = log2(0x00000020), // Renamed.
	Revoke = log2(0x00000040), // Access was revoked.
}
VNode_Flags :: bit_set[VNode_Flag; u32]

Proc_Flag :: enum u32 {
	Exit   = log2(0x80000000), // Process exited.
	Fork   = log2(0x40000000), // Process forked.
	Exec   = log2(0x20000000), // Process exec'd.
}
Proc_Flags :: bit_set[Proc_Flag; u32]

Timer_Flag :: enum u32 {
	Seconds   = log2(0x00000001),     // Data is seconds.
	USeconds  = log2(_NOTE_USECONDS), // Data is microseconds.
	Absolute  = log2(_NOTE_ABSOLUTE), // Absolute timeout.
}
Timer_Flags :: bit_set[Timer_Flag; u32]

User_Flag :: enum u32 {
	Trigger = log2(0x01000000),
	FFAnd   = log2(0x40000000),
	FFOr    = log2(0x80000000),
}
User_Flags :: bit_set[User_Flag; u32]

USER_FLAGS_COPY         :: User_Flags{.FFOr, .FFAnd}
USER_FLAGS_CONTROL_MASK :: transmute(User_Flags)u32(0xc0000000)
USER_FLAGS_MASK         :: transmute(User_Flags)u32(0x00FFFFFF)

// Data is nanoseconds.
TIMER_FLAGS_NSECONDS :: _TIMER_FLAGS_NSECONDS

when ODIN_OS == .Darwin {

	_Filter_Backing :: distinct i16
	_Flags_Backing  :: distinct u16

	_FILTER_READ   :: -1
	_FILTER_WRITE  :: -2
	_FILTER_AIO    :: -3
	_FILTER_VNODE  :: -4
	_FILTER_PROC   :: -5
	_FILTER_SIGNAL :: -6
	_FILTER_TIMER  :: -7
	_FILTER_USER   :: -10

	_NOTE_USECONDS :: 0x00000002
	_NOTE_NSECONDS :: 0x00000004
	_NOTE_ABSOLUTE :: 0x00000008

	_TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}

	KEvent :: struct #align(4) {
		// Value used to identify this event. The exact interpretation is determined by the attached filter.
		ident:  uintptr,
		// Filter for event.
		filter: Filter,
		// General flags.
		flags:  Flags,
		// Filter specific flags.
		fflags: struct #raw_union {
			rw:    RW_Flags    `raw_union_tag:"filter=.Read, filter=.Write"`,
			vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
			fproc: Proc_Flags  `raw_union_tag:"filter=.Proc"`,
			// vm:    VM_Flags,
			timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
			user:  User_Flags `raw_union_tag:"filter=.User"`,
		},
		// Filter specific data.
		data:   c.long /* intptr_t */,
		// Opaque user data passed through the kernel unchanged.
		udata:  rawptr,
	}

} else when ODIN_OS == .FreeBSD {

	_Filter_Backing :: distinct i16
	_Flags_Backing  :: distinct u16

	_FILTER_READ   :: -1
	_FILTER_WRITE  :: -2
	_FILTER_AIO    :: -3
	_FILTER_VNODE  :: -4
	_FILTER_PROC   :: -5
	_FILTER_SIGNAL :: -6
	_FILTER_TIMER  :: -7
	_FILTER_USER   :: -11

	_NOTE_USECONDS :: 0x00000004
	_NOTE_NSECONDS :: 0x00000008
	_NOTE_ABSOLUTE :: 0x00000010

	_TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}

	KEvent :: struct {
		// Value used to identify this event. The exact interpretation is determined by the attached filter.
		ident:  uintptr,
		// Filter for event.
		filter: Filter,
		// General flags.
		flags:  Flags,
		// Filter specific flags.
		fflags: struct #raw_union {
			rw:    RW_Flags    `raw_union_tag:"filter=.Read, filter=.Write"`,
			vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
			fproc: Proc_Flags  `raw_union_tag:"filter=.Proc"`,
			// vm:    VM_Flags,
			timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
			user:  User_Flags `raw_union_tag:"filter=.User"`,
		},
		// Filter specific data.
		data:   i64,
		// Opaque user data passed through the kernel unchanged.
		udata:  rawptr,
		// Extensions.
		ext: [4]u64,
	}
} else when ODIN_OS == .NetBSD {

	_Filter_Backing :: distinct u32
	_Flags_Backing  :: distinct u32

	_FILTER_READ   :: 0
	_FILTER_WRITE  :: 1
	_FILTER_AIO    :: 2
	_FILTER_VNODE  :: 3
	_FILTER_PROC   :: 4
	_FILTER_SIGNAL :: 5
	_FILTER_TIMER  :: 6
	_FILTER_USER   :: 8

	_NOTE_USECONDS :: 0x00000002
	_NOTE_ABSOLUTE :: 0x00000010

	_TIMER_FLAGS_NSECONDS :: Timer_Flags{.Seconds, .USeconds}

	KEvent :: struct {
		// Value used to identify this event. The exact interpretation is determined by the attached filter.
		ident:  uintptr,
		// Filter for event.
		filter: Filter,
		// General flags.
		flags:  Flags,
		// Filter specific flags.
		fflags: struct #raw_union {
			rw:    RW_Flags    `raw_union_tag:"filter=.Read, filter=.Write"`,
			vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
			fproc: Proc_Flags  `raw_union_tag:"filter=.Proc"`,
			// vm:    VM_Flags,
			timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
			user:  User_Flags `raw_union_tag:"filter=.User"`,
		},
		// Filter specific data.
		data:   i64,
		// Opaque user data passed through the kernel unchanged.
		udata:  rawptr,
	}
} else when ODIN_OS == .OpenBSD {

	_Filter_Backing :: distinct i16
	_Flags_Backing  :: distinct u16

	_FILTER_READ   :: -1
	_FILTER_WRITE  :: -2
	_FILTER_AIO    :: -3
	_FILTER_VNODE  :: -4
	_FILTER_PROC   :: -5
	_FILTER_SIGNAL :: -6
	_FILTER_TIMER  :: -7
	_FILTER_USER   :: -10

	_NOTE_USECONDS :: 0x00000002
	_NOTE_NSECONDS :: 0x00000004
	_NOTE_ABSOLUTE :: 0x00000010

	_TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}

	KEvent :: struct #align(4) {
		// Value used to identify this event. The exact interpretation is determined by the attached filter.
		ident:  uintptr,
		// Filter for event.
		filter: Filter,
		// General flags.
		flags:  Flags,
		// Filter specific flags.
		fflags: struct #raw_union {
			rw:    RW_Flags    `raw_union_tag:"filter=.Read, filter=.Write"`,
			vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
			fproc: Proc_Flags  `raw_union_tag:"filter=.Proc"`,
			// vm:    VM_Flags,
			timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
			user:  User_Flags `raw_union_tag:"filter=.User"`,
		},
		// Filter specific data.
		data:   i64,
		// Opaque user data passed through the kernel unchanged.
		udata:  rawptr,
	}
}

when ODIN_OS == .NetBSD {
	@(private)
	LKEVENT :: "__kevent50"
} else {
	@(private)
	LKEVENT :: "kevent"
}

@(private)
log2 :: intrinsics.constant_log2

foreign lib {
	@(link_name="kqueue")
	_kqueue :: proc() -> KQ ---
	@(link_name=LKEVENT)
	_kevent :: proc(kq: KQ, change_list: [^]KEvent, n_changes: c.int, event_list: [^]KEvent, n_events: c.int, timeout: ^posix.timespec) -> c.int ---
}
