#pragma once

#include "Utilities/bit_set.h"
#include "Utilities/mutex.h"

#include "Emu/Memory/vm_ptr.h"

#include <vector>
#include <utility>
#include <functional>

// Error codes
enum
{
	SYS_NET_ENOENT          = 2,
	SYS_NET_EINTR           = 4,
	SYS_NET_EBADF           = 9,
	SYS_NET_ENOMEM          = 12,
	SYS_NET_EACCES          = 13,
	SYS_NET_EFAULT          = 14,
	SYS_NET_EBUSY           = 16,
	SYS_NET_EINVAL          = 22,
	SYS_NET_EMFILE          = 24,
	SYS_NET_ENOSPC          = 28,
	SYS_NET_EPIPE           = 32,
	SYS_NET_EAGAIN          = 35,
	SYS_NET_EWOULDBLOCK     = SYS_NET_EAGAIN,
	SYS_NET_EINPROGRESS     = 36,
	SYS_NET_EALREADY        = 37,
	SYS_NET_EDESTADDRREQ    = 39,
	SYS_NET_EMSGSIZE        = 40,
	SYS_NET_EPROTOTYPE      = 41,
	SYS_NET_ENOPROTOOPT     = 42,
	SYS_NET_EPROTONOSUPPORT = 43,
	SYS_NET_EOPNOTSUPP      = 45,
	SYS_NET_EPFNOSUPPORT    = 46,
	SYS_NET_EAFNOSUPPORT    = 47,
	SYS_NET_EADDRINUSE      = 48,
	SYS_NET_EADDRNOTAVAIL   = 49,
	SYS_NET_ENETDOWN        = 50,
	SYS_NET_ENETUNREACH     = 51,
	SYS_NET_ECONNABORTED    = 53,
	SYS_NET_ECONNRESET      = 54,
	SYS_NET_ENOBUFS         = 55,
	SYS_NET_EISCONN         = 56,
	SYS_NET_ENOTCONN        = 57,
	SYS_NET_ESHUTDOWN       = 58,
	SYS_NET_ETOOMANYREFS    = 59,
	SYS_NET_ETIMEDOUT       = 60,
	SYS_NET_ECONNREFUSED    = 61,
	SYS_NET_EHOSTDOWN       = 64,
	SYS_NET_EHOSTUNREACH    = 65,
};

// Socket types (prefixed with SYS_NET_)
enum
{
	SYS_NET_SOCK_STREAM     = 1,
	SYS_NET_SOCK_DGRAM      = 2,
	SYS_NET_SOCK_RAW        = 3,
	SYS_NET_SOCK_DGRAM_P2P  = 6,
	SYS_NET_SOCK_STREAM_P2P = 10,
};

// Socket options (prefixed with SYS_NET_)
enum
{
	SYS_NET_SO_SNDBUF       = 0x1001,
	SYS_NET_SO_RCVBUF       = 0x1002,
	SYS_NET_SO_SNDLOWAT     = 0x1003,
	SYS_NET_SO_RCVLOWAT     = 0x1004,
	SYS_NET_SO_SNDTIMEO     = 0x1005,
	SYS_NET_SO_RCVTIMEO     = 0x1006,
	SYS_NET_SO_ERROR        = 0x1007,
	SYS_NET_SO_TYPE         = 0x1008,
	SYS_NET_SO_NBIO         = 0x1100, // Non-blocking IO
	SYS_NET_SO_TPPOLICY     = 0x1101,

	SYS_NET_SO_REUSEADDR    = 0x0004,
	SYS_NET_SO_KEEPALIVE    = 0x0008,
	SYS_NET_SO_BROADCAST    = 0x0020,
	SYS_NET_SO_LINGER       = 0x0080,
	SYS_NET_SO_OOBINLINE    = 0x0100,
	SYS_NET_SO_REUSEPORT    = 0x0200,
	SYS_NET_SO_ONESBCAST    = 0x0800,
	SYS_NET_SO_USECRYPTO    = 0x1000,
	SYS_NET_SO_USESIGNATURE = 0x2000,

	SYS_NET_SOL_SOCKET      = 0xffff,
};

// Family (prefixed with SYS_NET_)
enum
{
	SYS_NET_AF_UNSPEC       = 0,
	SYS_NET_AF_LOCAL        = 1,
	SYS_NET_AF_UNIX         = SYS_NET_AF_LOCAL,
	SYS_NET_AF_INET         = 2,
	SYS_NET_AF_INET6        = 24,
};

// Flags (prefixed with SYS_NET_)
enum
{
	SYS_NET_MSG_OOB         = 0x1,
	SYS_NET_MSG_PEEK        = 0x2,
	SYS_NET_MSG_DONTROUTE   = 0x4,
	SYS_NET_MSG_EOR         = 0x8,
	SYS_NET_MSG_TRUNC       = 0x10,
	SYS_NET_MSG_CTRUNC      = 0x20,
	SYS_NET_MSG_WAITALL     = 0x40,
	SYS_NET_MSG_DONTWAIT    = 0x80,
	SYS_NET_MSG_BCAST       = 0x100,
	SYS_NET_MSG_MCAST       = 0x200,
	SYS_NET_MSG_USECRYPTO   = 0x400,
	SYS_NET_MSG_USESIGNATURE= 0x800,
};

// Shutdown types (prefixed with SYS_NET_)
enum
{
	SYS_NET_SHUT_RD         = 0,
	SYS_NET_SHUT_WR         = 1,
	SYS_NET_SHUT_RDWR       = 2,
};

// TCP options (prefixed with SYS_NET_)
enum
{
	SYS_NET_TCP_NODELAY          = 1,
	SYS_NET_TCP_MAXSEG           = 2,
	SYS_NET_TCP_MSS_TO_ADVERTISE = 3,
};

// IP protocols (prefixed with SYS_NET_)
enum
{
	SYS_NET_IPPROTO_IP     = 0,
	SYS_NET_IPPROTO_ICMP   = 1,
	SYS_NET_IPPROTO_IGMP   = 2,
	SYS_NET_IPPROTO_TCP    = 6,
	SYS_NET_IPPROTO_UDP    = 17,
	SYS_NET_IPPROTO_ICMPV6 = 58,
};

// Poll events (prefixed with SYS_NET_)
enum
{
	SYS_NET_POLLIN         = 0x0001,
	SYS_NET_POLLPRI        = 0x0002,
	SYS_NET_POLLOUT        = 0x0004,
	SYS_NET_POLLERR        = 0x0008, /* revent only */
	SYS_NET_POLLHUP        = 0x0010, /* revent only */
	SYS_NET_POLLNVAL       = 0x0020, /* revent only */
	SYS_NET_POLLRDNORM     = 0x0040,
	SYS_NET_POLLWRNORM     = SYS_NET_POLLOUT,
	SYS_NET_POLLRDBAND     = 0x0080,
	SYS_NET_POLLWRBAND     = 0x0100,
};

// in_addr_t type prefixed with sys_net_
using sys_net_in_addr_t    = u32;

// in_port_t type prefixed with sys_net_
using sys_net_in_port_t    = u16;

// sa_family_t type prefixed with sys_net_
using sys_net_sa_family_t  = u8;

// socklen_t type prefixed with sys_net_
using sys_net_socklen_t    = u32;

// fd_set prefixed with sys_net_
struct sys_net_fd_set
{
	be_t<u32> fds_bits[32];

	u32 bit(s32 s)
	{
		return (fds_bits[(s >> 5) & 31] >> (s & 31)) & 1u;
	}

	void set(s32 s)
	{
		fds_bits[(s >> 5) & 31] |= (1u << (s & 31));
	}
};

// hostent prefixed with sys_net_
struct sys_net_hostent
{
	vm::bptr<char> h_name;
	vm::bpptr<char> h_aliases;
	be_t<s32> h_addrtype;
	be_t<s32> h_length;
	vm::bpptr<char> h_addr_list;
};

// in_addr prefixed with sys_net_
struct sys_net_in_addr
{
	be_t<u32> _s_addr;
};

// iovec prefixed with sys_net_
struct sys_net_iovec
{
	be_t<s32> zero1;
	vm::bptr<void> iov_base;
	be_t<s32> zero2;
	be_t<u32> iov_len;
};

// ip_mreq prefixed with sys_net_
struct sys_net_ip_mreq
{
	be_t<u32> imr_multiaddr;
	be_t<u32> imr_interface;
};

// msghdr prefixed with sys_net_
struct sys_net_msghdr
{
	be_t<s32> zero1;
	vm::bptr<void> msg_name;
	be_t<u32> msg_namelen;
	be_t<s32> pad1;
	be_t<s32> zero2;
	vm::bptr<sys_net_iovec> msg_iov;
	be_t<s32> msg_iovlen;
	be_t<s32> pad2;
	be_t<s32> zero3;
	vm::bptr<void> msg_control;
	be_t<u32> msg_controllen;
	be_t<s32> msg_flags;
};

// pollfd prefixed with sys_net_
struct sys_net_pollfd
{
	be_t<s32> fd;
	be_t<s16> events;
	be_t<s16> revents;
};

// sockaddr prefixed with sys_net_
struct sys_net_sockaddr
{
	u8 sa_len;
	u8 sa_family;
	char sa_data[14];
};

// sockaddr_dl prefixed with sys_net_
struct sys_net_sockaddr_dl
{
	u8 sdl_len;
	u8 sdl_family;
	be_t<u16> sdl_index;
	u8 sdl_type;
	u8 sdl_nlen;
	u8 sdl_alen;
	u8 sdl_slen;
	char sdl_data[12];
};

// sockaddr_in prefixed with sys_net_
struct sys_net_sockaddr_in
{
	u8 sin_len;
	u8 sin_family;
	be_t<u16> sin_port;
	be_t<u32> sin_addr;
	be_t<u64> sin_zero;
};

// sockaddr_in_p2p prefixed with sys_net_
struct sys_net_sockaddr_in_p2p
{
	u8 sin_len;
	u8 sin_family;
	be_t<u16> sin_port;
	be_t<u32> sin_addr;
	be_t<u16> sin_vport;
	char sin_zero[6];
};

// timeval prefixed with sys_net_
struct sys_net_timeval
{
	be_t<s64> tv_sec;
	be_t<s64> tv_usec;
};

// linger prefixed with sys_net_
struct sys_net_linger
{
	be_t<s32> l_onoff;
	be_t<s32> l_linger;
};

// Custom structure for sockets
// We map host sockets to sequential IDs to return as descriptors because syscalls expect socket IDs to be under 1024.
struct lv2_socket final
{
#ifdef _WIN32
	using socket_type = std::uintptr_t;
#else
	using socket_type = int;
#endif

	static const u32 id_base = 24;
	static const u32 id_step = 1;
	static const u32 id_count = 1000;

	// Poll events
	enum class poll
	{
		read,
		write,
		error,

		__bitset_enum_max
	};

	lv2_socket(socket_type s);
	~lv2_socket();

	shared_mutex mutex;

#ifdef _WIN32
	// Remember events (WSAEnumNetworkEvents)
	u32 ev_set = 0;
#endif

	// Native socket (must be non-blocking)
	socket_type socket;

	// Events selected for polling
	atomic_bs_t<poll> events{};

	// Non-blocking IO option
	s32 so_nbio = 0;

	// Connection result
	s32 so_error = 0;

	// Unsupported option
	s32 so_tcp_maxseg = 1500;

	// Value keepers
#ifdef _WIN32
	s32 so_reuseaddr = 0;
	s32 so_reuseport = 0;
#endif

	// Event processing workload (pair of thread id and the processing function)
	std::vector<std::pair<u32, std::function<bool(bs_t<lv2_socket::poll>)>>> queue;
};

class ppu_thread;

// Syscalls

s32 sys_net_bnet_accept(ppu_thread&, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen);
s32 sys_net_bnet_bind(ppu_thread&, s32 s, vm::cptr<sys_net_sockaddr> addr, u32 addrlen);
s32 sys_net_bnet_connect(ppu_thread&, s32 s, vm::ptr<sys_net_sockaddr> addr, u32 addrlen);
s32 sys_net_bnet_getpeername(ppu_thread&, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen);
s32 sys_net_bnet_getsockname(ppu_thread&, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen);
s32 sys_net_bnet_getsockopt(ppu_thread&, s32 s, s32 level, s32 optname, vm::ptr<void> optval, vm::ptr<u32> optlen);
s32 sys_net_bnet_listen(ppu_thread&, s32 s, s32 backlog);
s32 sys_net_bnet_recvfrom(ppu_thread&, s32 s, vm::ptr<void> buf, u32 len, s32 flags, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen);
s32 sys_net_bnet_recvmsg(ppu_thread&, s32 s, vm::ptr<sys_net_msghdr> msg, s32 flags);
s32 sys_net_bnet_sendmsg(ppu_thread&, s32 s, vm::cptr<sys_net_msghdr> msg, s32 flags);
s32 sys_net_bnet_sendto(ppu_thread&, s32 s, vm::cptr<void> buf, u32 len, s32 flags, vm::cptr<sys_net_sockaddr> addr, u32 addrlen);
s32 sys_net_bnet_setsockopt(ppu_thread&, s32 s, s32 level, s32 optname, vm::cptr<void> optval, u32 optlen);
s32 sys_net_bnet_shutdown(ppu_thread&, s32 s, s32 how);
s32 sys_net_bnet_socket(ppu_thread&, s32 family, s32 type, s32 protocol);
s32 sys_net_bnet_close(ppu_thread&, s32 s);
s32 sys_net_bnet_poll(ppu_thread&, vm::ptr<sys_net_pollfd> fds, s32 nfds, s32 ms);
s32 sys_net_bnet_select(ppu_thread&, s32 nfds, vm::ptr<sys_net_fd_set> readfds, vm::ptr<sys_net_fd_set> writefds, vm::ptr<sys_net_fd_set> exceptfds, vm::ptr<sys_net_timeval> timeout);
s32 _sys_net_open_dump(ppu_thread&, s32 len, s32 flags);
s32 _sys_net_read_dump(ppu_thread&, s32 id, vm::ptr<void> buf, s32 len, vm::ptr<s32> pflags);
s32 _sys_net_close_dump(ppu_thread&, s32 id, vm::ptr<s32> pflags);
s32 _sys_net_write_dump(ppu_thread&, s32 id, vm::cptr<void> buf, s32 len, u32 unknown);
s32 sys_net_abort(ppu_thread&, s32 type, u64 arg, s32 flags);
s32 sys_net_infoctl(ppu_thread&, s32 cmd, vm::ptr<void> arg);
s32 sys_net_control(ppu_thread&, u32 arg1, s32 arg2, vm::ptr<void> arg3, s32 arg4);
s32 sys_net_bnet_ioctl(ppu_thread&, s32 arg1, u32 arg2, u32 arg3);
s32 sys_net_bnet_sysctl(ppu_thread&, u32 arg1, u32 arg2, u32 arg3, vm::ptr<void> arg4, u32 arg5, u32 arg6);
s32 sys_net_eurus_post_command(ppu_thread&, s32 arg1, u32 arg2, u32 arg3);
