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

import "base:intrinsics"

import "core:c"

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

// sys/select.h - select types

foreign lib {
	/*
	Examines the file descriptor sets to see whether some of their descriptors are ready for writing,
	or have an exceptional condition pending, respectively.

	Returns: -1 (setting errno) on failure, total amount of bits set in the bit masks otherwise

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html ]]
	*/
	@(link_name=LPSELECT)
	pselect :: proc(
		nfds:     c.int,
		readfds:  ^fd_set,
		writefds: ^fd_set,
		errorfds: ^fd_set,
		timeout:  ^timespec,
		sigmask:  ^sigset_t,
	) -> c.int ---

	/*
	Equivalent to pselect() except a more specific timeout resolution (nanoseconds), 
	does not have a signal mask, and may modify the timeout.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html ]]
	*/
	@(link_name=LSELECT)
	select :: proc(
		nfds:     c.int,
		readfds:  ^fd_set,
		writefds: ^fd_set,
		errorfds: ^fd_set,
		timeout:  ^timeval,
	) -> c.int ---
}

when ODIN_OS == .NetBSD {
	LPSELECT :: "__pselect50"
	LSELECT  :: "__select50"
} else {
	LPSELECT :: "pselect"
	LSELECT  :: "select"
}

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

	suseconds_t :: distinct (c.int32_t when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .Haiku else c.long)

	timeval :: struct {
		tv_sec:  time_t,      /* [PSX] seconds */
		tv_usec: suseconds_t, /* [PSX] microseconds */
	}

	// Maximum number of file descriptors in the fd_set structure.
	FD_SETSIZE :: #config(POSIX_FD_SETSIZE, 256 when ODIN_OS == .NetBSD else 1024)

	@(private)
	__NFDBITS :: size_of(c.int32_t) * 8

	// NOTE: this seems correct for FreeBSD but they do use a set backed by the long type themselves (thus the align change).
	@(private)
	ALIGN ::  align_of(c.long) when ODIN_OS == .FreeBSD || ODIN_OS == .Linux else align_of(c.int32_t)

	when ODIN_OS == .Haiku {
		fd_set :: struct #align(ALIGN) {
			fds_bits: [(FD_SETSIZE + (__NFDBITS - 1)) / __NFDBITS]c.int32_t,
		}
	} else {
		fd_set :: struct #align(ALIGN) {
			fds_bits: [(FD_SETSIZE / __NFDBITS) when (FD_SETSIZE % __NFDBITS) == 0 else (FD_SETSIZE / __NFDBITS) + 1]c.int32_t,
		}
	}

	@(private)
	__check_fd_set :: #force_inline proc "contextless" (_a: FD, _b: rawptr) -> bool {
		if _a < 0 {
			set_errno(.EINVAL)
		}

		if _a >= FD_SETSIZE {
			set_errno(.EINVAL)
		}

		return true
	}

	FD_CLR :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) {
		if __check_fd_set(_fd, _p) {
			_p.fds_bits[cast(c.ulong)_fd / __NFDBITS] &= ~cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS))
		}
	}

	FD_ISSET :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) -> bool {
		if __check_fd_set(_fd, _p) {
			return bool(_p.fds_bits[cast(c.ulong)_fd / __NFDBITS] & cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS)))
		}

		return false
	}

	FD_SET :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) {
		if __check_fd_set(_fd, _p) {
			_p.fds_bits[cast(c.ulong)_fd / __NFDBITS] |= cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS))
		}
	}

	FD_ZERO :: #force_inline proc "contextless" (_p: ^fd_set) {
		intrinsics.mem_zero(_p, size_of(fd_set))
	}

}
