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

import "core:c"

when ODIN_OS == .Darwin {
	foreign import lib "system:System"
} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
	foreign import lib "system:dl"
} else {
	foreign import lib {
		"system:c",
		"system:dl",
	}
}

// dlfcn.h - dynamic linking

foreign lib {
	/*
	inform the system that the object referenced by a handle returned from a previous dlopen() 
	invocation is no longer needed by the application.

	Returns: 0 on success, non-zero on failure (use dlerror() for more information)

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlclose.html ]]
	*/
	dlclose :: proc(handle: Symbol_Table) -> c.int ---

	/*
	return a null-terminated character string (with no trailing <newline>) that describes
	the last error that occurred during dynamic linking processing.

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

	/*
	Make the symbols (function identifiers and data object identifiers) in the executable object
	file specified by file available to the calling program.

	Returns: a reference to the symbol table on success, nil on failure (use dlerror() for more information)

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlopen.html ]]
	*/
	dlopen :: proc(file: cstring, mode: RTLD_Flags) -> Symbol_Table ---

	/*
	Obtain the address of a symbol (a function identifier or a data object identifier)
	defined in the symbol table identified by the handle argument.

	Returns: the address of the matched symbol on success, nil on failure (use dlerror() for more information)

	Example:
		handle := posix.dlopen("/usr/home/me/libfoo.so", posix.RTLD_LOCAL + { .RTLD_LAZY })
		defer posix.dlclose(handle)

		if handle == nil {
			panic(string(posix.dlerror()))
		}

		foo: proc(a, b: int) -> int
		foo = auto_cast posix.dlsym(handle, "foo")

		if foo == nil {
			panic(string(posix.dlerror()))
		}

		fmt.printfln("foo(%v, %v) == %v", 1, 2, foo(1, 2))

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html ]]
	*/
	dlsym :: proc(handle: Symbol_Table, name: cstring) -> rawptr ---
}

RTLD_Flag_Bits :: enum c.int {
	LAZY   = log2(RTLD_LAZY),
	NOW    = log2(RTLD_NOW),
	GLOBAL = log2(RTLD_GLOBAL),

 	// NOTE: use with `posix.RTLD_LOCAL + { .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

	_MAX = 31,
}
RTLD_Flags :: bit_set[RTLD_Flag_Bits; c.int]

Symbol_Table :: distinct rawptr

when ODIN_OS == .Darwin {

	RTLD_LAZY    :: 0x1
	RTLD_NOW     :: 0x2
	_RTLD_LOCAL  :: 0x4
	RTLD_GLOBAL  :: 0x8

	RTLD_LOCAL   :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))}

} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD {

	RTLD_LAZY    :: 1
	RTLD_NOW     :: 2
	_RTLD_LOCAL  :: 0
	RTLD_GLOBAL  :: 0x100

	RTLD_LOCAL   :: RTLD_Flags{}

} else when ODIN_OS == .NetBSD {

	RTLD_LAZY    :: 0x1
	RTLD_NOW     :: 0x2
	_RTLD_LOCAL  :: 0x200
	RTLD_GLOBAL  :: 0x100

	RTLD_LOCAL   :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))}

} else when ODIN_OS == .Linux {

	RTLD_LAZY    :: 0x001
	RTLD_NOW     :: 0x002
	RTLD_GLOBAL  :: 0x100

	_RTLD_LOCAL  :: 0
	RTLD_LOCAL   :: RTLD_Flags{}

}

