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

import "core:c"

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

// sys/socket.h - main sockets header

#assert(Protocol.IP == Protocol(0), "socket() assumes this")

foreign libc {
	/*
	Creates a socket.

	Returns: -1 (setting errno) on failure, file descriptor of socket otherwise

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html ]]
	*/
	@(link_name=LSOCKET)
	socket :: proc(domain: AF, type: Sock, protocol: Protocol = .IP) -> FD ---

	/*
	Extracts the first connection on the queue of pending connections.

	Blocks (if not O_NONBLOCK) if there is no pending connection.

	Returns: -1 (setting errno) on failure, file descriptor of accepted socket otherwise

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html ]]
	*/
	accept :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> FD ---

	/*
	Assigns a local socket address to the socket.

	Example:
		sfd := posix.socket(.UNIX, .STREAM)
		if sfd == -1 {
			/* Handle error */
		}

		addr: posix.sockaddr_un
		addr.sun_family = .UNIX
		copy(addr.sun_path[:], "/somepath\x00")

		/*
			unlink the socket before binding in case
			of previous runs not cleaning up the socket
		*/
		posix.unlink("/somepath")

		if posix.bind(sfd, (^posix.sockaddr)(&addr), size_of(addr)) != .OK {
			/* Handle error */
		}

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html ]]
	*/
	bind :: proc(socket: FD, address: ^sockaddr, address_len: socklen_t) -> result ---

	/*
	Attempt to make a connection.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html ]]
	*/
	connect :: proc(socket: FD, address: ^sockaddr, address_len: socklen_t) -> result ---

	/*
	Get the peer address of the specified socket.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html ]]
	*/
	getpeername :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> result ---

	/*
	Get the socket name.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html ]]
	*/
	getsockname :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> result ---

	/*
	Retrieves the value for the option specified by option_name.

	level: either `c.int(posix.Protocol(...))` to specify a protocol level or `posix.SOL_SOCKET`
	to specify the socket local level.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html ]]
	*/
	getsockopt :: proc(
		socket:       FD,
		level:        c.int,
		option_name:  Sock_Option,
		option_value: rawptr,
		option_len:   ^socklen_t,
	) -> result ---

	/*
	Sets the specified option.

	level: either `c.int(posix.Protocol(...))` to specify a protocol level or `posix.SOL_SOCKET`
	to specify the socket local level.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html ]]
	*/
	setsockopt :: proc(
		socket:       FD,
		level:        c.int,
		option_name:  Sock_Option,
		option_value: rawptr,
		option_len:   socklen_t,
	) -> result ---

	/*
	Mark the socket as a socket accepting connections.

	backlog provides a hint to limit the number of connections on the listen queue.
	Implementation may silently reduce the backlog, additionally `SOMAXCONN` specifies the maximum
	an implementation has to support.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html ]]
	*/
	listen :: proc(socket: FD, backlog: c.int) -> result ---

	/*
	Receives a message from a socket.

	Blocks (besides with O_NONBLOCK) if there is nothing to receive.

	Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html ]]
	*/
	recv :: proc(socket: FD, buffer: rawptr, length: c.size_t, flags: Msg_Flags) -> c.ssize_t ---

	/*
	Receives a message from a socket.

	Equivalent to recv() but retrieves the source address too.

	Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html ]]
	*/
	recvfrom :: proc(
		socket:      FD,
		buffer:      rawptr,
		length:      c.size_t,
		flags:       Msg_Flags,
		address:     ^sockaddr,
		address_len: ^socklen_t,
	) -> c.ssize_t ---

	/*
	Receives a message from a socket.

	Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html ]]
	*/
	recvmsg :: proc(socket: FD, message: ^msghdr, flags: Msg_Flags) -> c.ssize_t ---

	/*
	Sends a message on a socket.

	Returns: -1 (setting errno) on failure, the amount of bytes received on success

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html ]]
	*/
	send :: proc(socket: FD, buffer: rawptr, length: c.size_t, flags: Msg_Flags) -> c.ssize_t ---

	/*
	Sends a message on a socket.

	Returns: -1 (setting errno) on failure, the amount of bytes received on success

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html ]]
	*/
	sendmsg :: proc(socket: FD, message: ^msghdr, flags: Msg_Flags) -> c.ssize_t ---

	/*
	Sends a message on a socket.

	If the socket is connectionless, the dest_addr is used to send to.

	Returns: -1 (setting errno) on failure, the amount of bytes received on success

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html ]]
	*/
	sendto :: proc(
		socket:    FD,
		message:   rawptr,
		length:    c.size_t,
		flags:     Msg_Flags,
		dest_addr: ^sockaddr,
		dest_len:  socklen_t,
	) -> c.ssize_t ---

	/*
	Shuts down a socket end or both.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html ]]
	*/
	shutdown :: proc(socket: FD, how: Shut) -> result ---

	/*
	Determine wheter a socket is at the out-of-band mark.

	Returns: -1 (setting errno) on failure, 0 if not at the mark, 1 if it is

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sockatmark.html ]]
	*/
	sockatmark :: proc(socket: FD) -> c.int ---

	/*
	Create a pair of connected sockets.

	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html ]]
	*/
	socketpair :: proc(domain: AF, type: Sock, protocol: Protocol, socket_vector: ^[2]FD) -> result ---
}

AF_UNSPEC :: 0

AF :: enum c.int {
	// Unspecified.
	UNSPEC = AF_UNSPEC,
	// Internet domain sockets for use with IPv4 addresses.
	INET   = AF_INET,
	// Internet domain sockets for use with IPv6 addresses.
	INET6  = AF_INET6,
	// UNIX domain sockets.
	UNIX   = AF_UNIX,
}

sa_family_t :: enum _sa_family_t {
	// Unspecified.
	UNSPEC = AF_UNSPEC,
	// Internet domain sockets for use with IPv4 addresses.
	INET   = AF_INET,
	// Internet domain sockets for use with IPv6 addresses.
	INET6  = AF_INET6,
	// UNIX domain sockets.
	UNIX   = AF_UNIX,
}

Sock :: enum c.int {
	// Datagram socket.
	DGRAM     = SOCK_DGRAM,
	// Raw Protocol Interface.
	RAW       = SOCK_RAW,
	// Sequenced-packet socket.
	SEQPACKET = SOCK_SEQPACKET,
	// Byte-stream socket.
	STREAM    = SOCK_STREAM,
}

Shut :: enum c.int {
	// Disables further receive operations.
	RD   = SHUT_RD,
	// Disables further send and receive operations.
	RDWR = SHUT_RDWR,
	// Disables further send operations.
	WR   = SHUT_WR,
}

Msg_Flag_Bits :: enum c.int {
	// Control data truncated.
	CTRUNC    = log2(MSG_CTRUNC),
	// Send without using routing table.
	DONTROUTE = log2(MSG_DONTROUTE),
	// Terminates a record (if supported by protocol).
	EOR       = log2(MSG_EOR),
	// Out-of-band data.
	OOB       = log2(MSG_OOB),
	// No SIGPIPE is generated when an attempt to send is made on a stream-oriented socket that is
	// no longer connected.
	NOSIGNAL  = log2(MSG_NOSIGNAL),
	// Leave received data in queue.
	PEEK      = log2(MSG_PEEK),
	// Normal data truncated.
	TRUNC     = log2(MSG_TRUNC),
	// Attempt to fill the read buffer.
	WAITALL   = log2(MSG_WAITALL),
}
Msg_Flags :: bit_set[Msg_Flag_Bits; c.int]

Sock_Option :: enum c.int {
	// Transmission of broadcast message is supported.
	BROADCAST  = SO_BROADCAST,
	// Debugging information is being recorded.
	DEBUG      = SO_DEBUG,
	// Bypass normal routing.
	DONTROUTE  = SO_DONTROUTE,
	// Socket error status.
	ERROR      = SO_ERROR,
	// Connections are kept alive with periodic messages.
	KEEPALIVE  = SO_KEEPALIVE,
	// Socket lingers on close.
	LINGER     = SO_LINGER,
	// Out-of-band data is transmitted in line.
	OOBINLINE  = SO_OOBINLINE,
	// Receive buffer size.
	RCVBUF     = SO_RCVBUF,
	// Receive low water mark.
	RCVLOWAT   = SO_RCVLOWAT,
	// Receive timeout.
	RCVTIMEO   = SO_RCVTIMEO,
	// Reuse of local addresses is supported.
	REUSEADDR  = SO_REUSEADDR,
	// Send buffer size.
	SNDBUF     = SO_SNDBUF,
	// Send low water mark.
	SNDLOWAT   = SO_SNDLOWAT,
	// Send timeout.
	SNDTIMEO   = SO_SNDTIMEO,
	// Socket type.
	TYPE       = SO_TYPE,
}

when ODIN_OS == .NetBSD {
	@(private) LSOCKET :: "__socket30"
} else {
	@(private) LSOCKET :: "socket"
}

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

	socklen_t :: distinct c.uint

	when ODIN_OS == .Haiku {
		@(private)
		_SA_DATASIZE :: 30
	} else {
		@(private)
		_SA_DATASIZE :: 14
	}

	when ODIN_OS == .Linux {
		_sa_family_t :: distinct c.ushort

		sockaddr :: struct {
			sa_family: sa_family_t,           /* [PSX] address family */
			sa_data:   [_SA_DATASIZE]c.char,  /* [PSX] socket address */
		}
	} else {
		_sa_family_t :: distinct c.uint8_t

		sockaddr :: struct {
			sa_len:    c.uint8_t,             /* total length */
			sa_family: sa_family_t,           /* [PSX] address family */
			sa_data:   [_SA_DATASIZE]c.char,  /* [PSX] socket address */
		}
	}


	when ODIN_OS == .OpenBSD {
		@(private)
		_SS_PAD1SIZE :: 6
		@(private)
		_SS_PAD2SIZE :: 240
	} else when ODIN_OS == .Haiku {
		@(private)
		_SS_PAD1SIZE :: 6
		@(private)
		_SS_PAD2SIZE :: 112
	} else when ODIN_OS == .Linux {
		@(private)
		_SS_SIZE :: 128
		@(private)
		_SS_PADSIZE :: _SS_SIZE - size_of(c.uint16_t) - size_of(c.uint64_t)
	} else {
		@(private)
		_SS_MAXSIZE   :: 128
		@(private)
		_SS_ALIGNSIZE :: size_of(c.int64_t)
		@(private)
		_SS_PAD1SIZE  :: _SS_ALIGNSIZE - size_of(c.uint8_t) - size_of(sa_family_t)
		@(private)
		_SS_PAD2SIZE  :: _SS_MAXSIZE - size_of(c.uint8_t) - size_of(sa_family_t) - _SS_PAD1SIZE - _SS_ALIGNSIZE
	}

	when ODIN_OS == .Linux {
		sockaddr_storage :: struct {
			ss_family:    sa_family_t,          /* [PSX] address family */
			__ss_padding: [_SS_PADSIZE]c.char,
			__ss_align:   c.uint64_t,           /* force structure storage alignment */
		}

		msghdr :: struct {
			msg_name:       rawptr,    /* [PSX] optional address */
			msg_namelen:    socklen_t, /* [PSX] size of address */
			msg_iov:        [^]iovec,  /* [PSX] scatter/gather array */
			msg_iovlen:     c.size_t,  /* [PSX] members in msg_iov */
			msg_control:    rawptr,    /* [PSX] ancillary data */
			msg_controllen: c.size_t,  /* [PSX] ancillary data buffer length */
			msg_flags:      Msg_Flags, /* [PSX] flags on received message */
		}

		cmsghdr :: struct {
			cmsg_len:   c.size_t, /* [PSX] data byte count, including cmsghdr */
			cmsg_level: c.int,     /* [PSX] originating protocol */
			cmsg_type:  c.int,     /* [PSX] protocol-specific type */
		}
	} else {
		sockaddr_storage :: struct {
			ss_len:     c.uint8_t,            /* address length */
			ss_family:  sa_family_t,          /* [PSX] address family */
			__ss_pad1:  [_SS_PAD1SIZE]c.char,
			__ss_align: c.int64_t,            /* force structure storage alignment */
			__ss_pad2:  [_SS_PAD2SIZE]c.char,
		}

		msghdr :: struct {
			msg_name:       rawptr,    /* [PSX] optional address */
			msg_namelen:    socklen_t, /* [PSX] size of address */
			msg_iov:        [^]iovec,  /* [PSX] scatter/gather array */
			msg_iovlen:     c.int,     /* [PSX] members in msg_iov */
			msg_control:    rawptr,    /* [PSX] ancillary data */
			msg_controllen: socklen_t, /* [PSX] ancillary data buffer length */
			msg_flags:      Msg_Flags, /* [PSX] flags on received message */
		}

		cmsghdr :: struct {
			cmsg_len:   socklen_t, /* [PSX] data byte count, including cmsghdr */
			cmsg_level: c.int,     /* [PSX] originating protocol */
			cmsg_type:  c.int,     /* [PSX] protocol-specific type */
		}
	}

	SCM_RIGHTS :: 0x01

	@(private)
	__ALIGN32 :: #force_inline proc "contextless" (p: uintptr) -> uintptr {
		__ALIGNBYTES32 :: size_of(c.uint32_t) - 1
		return (p + __ALIGNBYTES32) &~ __ALIGNBYTES32
	}

	// Returns a pointer to the data array.
	CMSG_DATA :: #force_inline proc "contextless" (cmsg: ^cmsghdr) -> [^]c.uchar {
		return ([^]c.uchar)(uintptr(cmsg) + __ALIGN32(size_of(cmsghdr)))
	}

	// Returns a pointer to the next cmsghdr or nil.
	CMSG_NXTHDR :: #force_inline proc "contextless" (mhdr: ^msghdr, cmsg: ^cmsghdr) -> ^cmsghdr {
		if cmsg == nil {
			return CMSG_FIRSTHDR(mhdr)
		}

		ptr := uintptr(cmsg) + __ALIGN32(uintptr(cmsg.cmsg_len))
		if ptr + __ALIGN32(size_of(cmsghdr)) > uintptr(mhdr.msg_control) + uintptr(mhdr.msg_controllen) {
			return nil
		}

		return (^cmsghdr)(ptr)
	}

	// Returns a pointer to the first cmsghdr or nil.
	CMSG_FIRSTHDR :: #force_inline proc "contextless" (mhdr: ^msghdr) -> ^cmsghdr {
		if mhdr.msg_controllen >= size_of(cmsghdr) {
			return (^cmsghdr)(mhdr.msg_control)
		}

		return nil
	}

	linger :: struct {
		l_onoff:  c.int, /* [PSX] indicates whether linger option is enabled */
		l_linger: c.int, /* [PSX] linger time in seconds */
	}

	SOCK_DGRAM     :: 2
	SOCK_RAW       :: 3
	SOCK_SEQPACKET :: 5
	SOCK_STREAM    :: 1

	// Options to be accessed at socket level, not protocol level.
	when ODIN_OS == .Linux {
		SOL_SOCKET :: 1

		SO_ACCEPTCONN :: 30
		SO_BROADCAST  :: 6
		SO_DEBUG      :: 1
		SO_DONTROUTE  :: 5
		SO_ERROR      :: 4
		SO_KEEPALIVE  :: 9
		SO_OOBINLINE  :: 10
		SO_RCVBUF     :: 8
		SO_RCVLOWAT   :: 18
		SO_REUSEADDR  :: 2
		SO_SNDBUF     :: 7
		SO_SNDLOWAT   :: 19
		SO_TYPE       :: 3
		SO_LINGER     :: 13

		SO_RCVTIMEO   :: 66
		SO_SNDTIMEO   :: 67
	} else when ODIN_OS == .Haiku {
		SOL_SOCKET :: -1

		SO_ACCEPTCONN :: 0x00000001
		SO_BROADCAST  :: 0x00000002
		SO_DEBUG      :: 0x00000004
		SO_DONTROUTE  :: 0x00000008
		SO_ERROR      :: 0x40000007
		SO_KEEPALIVE  :: 0x00000010
		SO_OOBINLINE  :: 0x00000020
		SO_RCVBUF     :: 0x40000004
		SO_RCVLOWAT   :: 0x40000005
		SO_REUSEADDR  :: 0x00000040
		SO_SNDBUF     :: 0x40000001
		SO_SNDLOWAT   :: 0x40000002
		SO_TYPE       :: 0x40000008

		SO_LINGER     :: 0x00000200
		SO_RCVTIMEO   :: 0x40000006
		SO_SNDTIMEO   :: 0x40000003
	} else {
		SOL_SOCKET :: 0xffff

		SO_ACCEPTCONN :: 0x0002
		SO_BROADCAST  :: 0x0020
		SO_DEBUG      :: 0x0001
		SO_DONTROUTE  :: 0x0010
		SO_ERROR      :: 0x1007
		SO_KEEPALIVE  :: 0x0008
		SO_OOBINLINE  :: 0x0100
		SO_RCVBUF     :: 0x1002
		SO_RCVLOWAT   :: 0x1004
		SO_REUSEADDR  :: 0x0004
		SO_SNDBUF     :: 0x1001
		SO_SNDLOWAT   :: 0x1003
		SO_TYPE       :: 0x1008

		when ODIN_OS == .Darwin {
			SO_LINGER   :: 0x1080
			SO_RCVTIMEO :: 0x1006
			SO_SNDTIMEO :: 0x1005
		} else when ODIN_OS == .FreeBSD {
			SO_LINGER   :: 0x0080
			SO_RCVTIMEO :: 0x1006
			SO_SNDTIMEO :: 0x1005
		} else when ODIN_OS == .NetBSD {
			SO_LINGER   :: 0x0080
			SO_RCVTIMEO :: 0x100c
			SO_SNDTIMEO :: 0x100b
		} else when ODIN_OS == .OpenBSD {
			SO_LINGER   :: 0x0080
			SO_RCVTIMEO :: 0x1006
			SO_SNDTIMEO :: 0x1005
		}
	}

	// The maximum backlog queue length for listen().
	when ODIN_OS == .Haiku {
		SOMAXCONN :: 32
	} else {
		SOMAXCONN :: 128
	}

	when ODIN_OS == .Linux {
		MSG_CTRUNC    :: 0x008
		MSG_DONTROUTE :: 0x004
		MSG_EOR       :: 0x080
		MSG_OOB       :: 0x001
		MSG_PEEK      :: 0x002
		MSG_TRUNC     :: 0x020
		MSG_WAITALL   :: 0x100
		MSG_NOSIGNAL  :: 0x4000
	} else {
		MSG_CTRUNC    :: 0x20
		MSG_DONTROUTE :: 0x4
		MSG_EOR       :: 0x8
		MSG_OOB       :: 0x1
		MSG_PEEK      :: 0x2
		MSG_TRUNC     :: 0x10
		MSG_WAITALL   :: 0x40

		when ODIN_OS == .Darwin {
			MSG_NOSIGNAL :: 0x80000
		} else when ODIN_OS == .FreeBSD {
			MSG_NOSIGNAL :: 0x00020000
		} else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD {
			MSG_NOSIGNAL :: 0x0400
		} else when ODIN_OS == .Haiku {
			MSG_NOSIGNAL :: 0x800
		}
	}

	when ODIN_OS == .Haiku {
		AF_INET :: 1
		AF_UNIX :: 9
	} else {
		AF_INET :: 2
		AF_UNIX :: 1
	}

	when ODIN_OS == .Darwin {
		AF_INET6 :: 30
	} else when ODIN_OS == .FreeBSD {
		AF_INET6 :: 28
	} else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD {
		AF_INET6 :: 24
	} else when ODIN_OS == .Linux {
		AF_INET6 :: 10
	} else when ODIN_OS == .Haiku {
		AF_INET6 :: 5
	}

	SHUT_RD   :: 0
	SHUT_RDWR :: 2
	SHUT_WR   :: 1

}
