#+build linux, darwin, netbsd, openbsd, freebsd
package posix

import "core:c"

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

// mman.h - memory management declarations

foreign lib {
	/*
	Establish a mapping between an address space of a process and a memory object.

	Returns: MAP_FAILED (setting errno) on failure, the address in memory otherwise

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html ]]
	*/
	mmap :: proc(
		addr:  rawptr,
		len:   c.size_t,
		prot:  Prot_Flags,
		flags: Map_Flags,
		fd:    FD    = -1,
		off:   off_t = 0,
	) -> rawptr ---

	/*
	Unmaps pages of memory.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/munmap.html ]]
	*/
	munmap :: proc(addr: rawptr, len: c.size_t) -> result ---

	/*
	Locks a range of the process address space.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html ]]
	*/
	mlock :: proc(addr: rawptr, len: c.size_t) -> result ---

	/*
	Unlocks a range of the process address space.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html ]]
	*/
	munlock :: proc(addr: rawptr, len: c.size_t) -> result ---

	/*
	Locks all pages of the process address space.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html ]]
	*/
	mlockall :: proc(flags: Lock_Flags) -> result ---

	/*
	Unlocks all pages of the process address space.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html ]]
	*/
	munlockall :: proc() -> result ---

	/*
	Set protection of a memory mapping.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mprotect.html ]]
	*/
	mprotect :: proc(addr: rawptr, len: c.size_t, prot: Prot_Flags) -> result ---

	/*
	Write all modified data to permanent storage locations.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msync.html ]]
	*/
	@(link_name=LMSYNC)
	msync :: proc(addr: rawptr, len: c.size_t, flags: Sync_Flags) -> result ---

	/*
	Advise the implementation of expected behavior of the application.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_madvise.html ]]
	*/
	posix_madvise :: proc(addr: rawptr, len: c.size_t, advice: MAdvice) -> Errno ---

	/*
	Open a shared memory object.

	Returns: -1 (setting errno) on failure, an open file descriptor otherwise

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html ]]
	*/
	when ODIN_OS == .Darwin {
		// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/shm_open.2.html
		shm_open :: proc(name: cstring, oflag: O_Flags, #c_vararg args: ..any) -> FD ---
	} else {
		shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD ---
	}

	/*
	Removes a shared memory object.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_unlink.html ]]
	*/
	shm_unlink :: proc(name: cstring) -> result ---
}

#assert(_PROT_NONE == 0)
PROT_NONE :: Prot_Flags{}

Prot_Flag_Bits :: enum c.int {
	// Data can be executed.
	EXEC  = log2(PROT_EXEC),
	// Data can be read.
	READ  = log2(PROT_READ),
	// Data can be written.
	WRITE = log2(PROT_WRITE),
}
Prot_Flags :: bit_set[Prot_Flag_Bits; c.int]

Map_Flag_Bits :: enum c.int {
	// Map anonymous memory.
	ANONYMOUS = log2(MAP_ANONYMOUS),
	// Interpret addr exactly.
	FIXED     = log2(MAP_FIXED),
	// Changes are private.
	PRIVATE   = log2(MAP_PRIVATE),
	// Changes are shared.
	SHARED    = log2(MAP_SHARED),
}
Map_Flags :: bit_set[Map_Flag_Bits; c.int]

Lock_Flag_Bits :: enum c.int {
	// Lock all pages currently mapped into the address space of the process.
	CURRENT = log2(MCL_CURRENT),
	// Lock all pages that become mapped into the address space of the process in the future, 
	// when those mappings are established.
	FUTURE  = log2(MCL_FUTURE),
}
Lock_Flags :: bit_set[Lock_Flag_Bits; c.int]

Sync_Flags_Bits :: enum c.int {
	// Perform asynchronous writes.
	ASYNC      = log2(MS_ASYNC),
	// Invalidate cached data.
	INVALIDATE = log2(MS_INVALIDATE),

	// Perform synchronous writes.
 	// NOTE: use with `posix.MS_SYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in
	// this bit set enum because it is 0 on some platforms and a value on others.
	// LOCAL = RTLD_LOCAL
	// SYNC       = MS_SYNC,

	_MAX = 31,
}
Sync_Flags :: bit_set[Sync_Flags_Bits; c.int]

MAdvice :: enum c.int {
	DONTNEED   = POSIX_MADV_DONTNEED,
	NORMAL     = POSIX_MADV_NORMAL,
	RANDOM     = POSIX_MADV_RANDOM,
	SEQUENTIAL = POSIX_MADV_SEQUENTIAL,
	WILLNEED   = POSIX_MADV_WILLNEED,
}

when ODIN_OS == .NetBSD {
	@(private) LMSYNC :: "__msync13"
} else {
	@(private) LMSYNC :: "msync"
}

when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux {

	PROT_EXEC   :: 0x04
	_PROT_NONE  :: 0x00
	PROT_READ   :: 0x01
	PROT_WRITE  :: 0x02

	MAP_FIXED     :: 0x0010
	MAP_PRIVATE   :: 0x0002
	MAP_SHARED    :: 0x0001
	MAP_ANONYMOUS :: 0x0020 when ODIN_OS == .Linux else 0x1000

	when ODIN_OS == .Darwin || ODIN_OS == .Linux {
		MS_INVALIDATE :: 0x0002
		_MS_SYNC      :: 0x0010
	} else when ODIN_OS == .NetBSD {
		MS_INVALIDATE :: 0x0002
		_MS_SYNC      :: 0x0004
	} else when ODIN_OS == .OpenBSD {
		MS_INVALIDATE :: 0x0004
		_MS_SYNC      :: 0x0002
	}

	MS_ASYNC :: 0x0001
	MS_SYNC  :: Sync_Flags{Sync_Flags_Bits(log2(_MS_SYNC))}

	MCL_CURRENT :: 0x0001
	MCL_FUTURE  :: 0x0002

	MAP_FAILED :: rawptr(~uintptr(0))

	POSIX_MADV_DONTNEED   :: 4
	POSIX_MADV_NORMAL     :: 0
	POSIX_MADV_RANDOM     :: 1
	POSIX_MADV_SEQUENTIAL :: 2
	POSIX_MADV_WILLNEED   :: 3

} else when ODIN_OS == .FreeBSD {

	PROT_EXEC   :: 0x04
	_PROT_NONE  :: 0x00
	PROT_READ   :: 0x01
	PROT_WRITE  :: 0x02

	MAP_FIXED     :: 0x0010
	MAP_PRIVATE   :: 0x0002
	MAP_SHARED    :: 0x0001
	MAP_ANONYMOUS :: 0x1000

	MS_ASYNC      :: 0x0001
	MS_INVALIDATE :: 0x0002
	MS_SYNC       :: Sync_Flags{}

	MCL_CURRENT :: 0x0001
	MCL_FUTURE  :: 0x0002

	MAP_FAILED :: rawptr(~uintptr(0))

	POSIX_MADV_DONTNEED   :: 4
	POSIX_MADV_NORMAL     :: 0
	POSIX_MADV_RANDOM     :: 1
	POSIX_MADV_SEQUENTIAL :: 2
	POSIX_MADV_WILLNEED   :: 3

}
