#pragma once

#include <string>
#include <vector>
#include <cassert>

#include "il2cpp-config.h"

#include "os/ErrorCodes.h"
#include "os/Atomic.h"
#include "os/Mutex.h"
#include "os/WaitStatus.h"
#include "utils/NonCopyable.h"

namespace il2cpp
{
namespace os
{

class SocketImpl;

enum AddressFamily
{
	kAddressFamilyError 			= -1,
	kAddressFamilyUnspecified 		= 0, // AF_UNSPEC
	kAddressFamilyUnix 				= 1, // AF_UNIX
	kAddressFamilyInterNetwork 		= 2, // AF_INET
	kAddressFamilyIpx 				= 3, // AF_IPX
	kAddressFamilySna 				= 4, // AF_SNA
	kAddressFamilyDecNet 			= 5, // AF_DECnet
	kAddressFamilyAppleTalk 		= 6, // AF_APPLETALK
	kAddressFamilyInterNetworkV6	= 7, // AF_INET6
	kAddressFamilyIrda 				= 8, // AF_IRDA
};

enum SocketType
{
	kSocketTypeError 		= -1,
	kSocketTypeStream 		= 0, // SOCK_STREAM
	kSocketTypeDgram 		= 1, // SOCK_DGRAM
	kSocketTypeRaw 			= 2, // SOCK_RAW
	kSocketTypeRdm 			= 3, // SOCK_RDM
	kSocketTypeSeqpacket	= 4, // SOCK_SEQPACKET
};

enum ProtocolType
{
	kProtocolTypeUnknown 							= -1,
	kProtocolTypeIP 								= 0,
	kProtocolTypeIcmp 								= 1,
	kProtocolTypeIgmp 								= 2,
	kProtocolTypeGgp 								= 3,
	kProtocolTypeTcp 								= 6,
	kProtocolTypePup 								= 12,
	kProtocolTypeUdp 								= 17,
	kProtocolTypeIdp 								= 22,
	kProtocolTypeND 								= 77,
	kProtocolTypeRaw 								= 255,
	kProtocolTypeUnspecified 						= 0,
	kProtocolTypeIpx 								= 1000,
	kProtocolTypeSpx 								= 1256,
	kProtocolTypeSpxII 								= 1257,
	
// #if NET_1_1
	kProtocolTypeIPv6 								= 41,
// #endif

// #if NET_2_0
	kProtocolTypeIPv4 								= 4,
	kProtocolTypeIPv6RoutingHeader 					= 43,
	kProtocolTypeIPv6FragmentHeader 				= 44,
	kProtocolTypeIPSecEncapsulatingSecurityPayload	= 50,
	kProtocolTypeIPSecAuthenticationHeader 			= 51,
	kProtocolTypeIcmpV6 							= 58,
	kProtocolTypeIPv6NoNextHeader 					= 59,
	kProtocolTypeIPv6DestinationOptions 			= 60,
	kProtocolTypeIPv6HopByHopOptions 				= 0,
// #endif
};

enum SocketFlags
{
	kSocketFlagsNone					= 0x00000000,
	kSocketFlagsOutOfBand				= 0x00000001,
	kSocketFlagsPeek					= 0x00000002,
	kSocketFlagsDontRoute				= 0x00000004,
	kSocketFlagsMaxIOVectorLength		= 0x00000010,
// #if NET_2_0
	kSocketFlagsTruncated				= 0x00000100,
	kSocketFlagsControlDataTruncated	= 0x00000200,
	kSocketFlagsBroadcast				= 0x00000400,
	kSocketFlagsMulticast				= 0x00000800,
// #endif
	kSocketFlagsPartial					= 0x00008000,
};

enum SocketOptionLevel
{
	kSocketOptionLevelSocket 	= 65535,
	kSocketOptionLevelIP 		= 0,
	kSocketOptionLevelTcp 		= 6,
	kSocketOptionLevelUdp 		= 17,

//#if NET_1_1
	kSocketOptionLevelIPv6 		= 41,
//#endif
};

enum SocketOptionName
{
	kSocketOptionNameDebug 					= 1,
	kSocketOptionNameAcceptConnection 		= 2,
	kSocketOptionNameReuseAddress 			= 4,
	kSocketOptionNameKeepAlive 				= 8,
	kSocketOptionNameDontRoute 				= 16,
	kSocketOptionNameBroadcast 				= 32,
	kSocketOptionNameUseLoopback 			= 64,
	kSocketOptionNameLinger 				= 128,
	kSocketOptionNameOutOfBandInline 		= 256,
	kSocketOptionNameDontLinger 			= -129,
	kSocketOptionNameExclusiveAddressUse 	= -5,
	kSocketOptionNameSendBuffer 			= 4097,
	kSocketOptionNameReceiveBuffer 			= 4098,
	kSocketOptionNameSendLowWater 			= 4099,
	kSocketOptionNameReceiveLowWater 		= 4100,
	kSocketOptionNameSendTimeout 			= 4101,
	kSocketOptionNameReceiveTimeout 		= 4102,
	kSocketOptionNameError 					= 4103,
	kSocketOptionNameType 					= 4104,
	kSocketOptionNameMaxConnections 		= 2147483647,
	kSocketOptionNameIPOptions 				= 1,
	kSocketOptionNameHeaderIncluded 		= 2,
	kSocketOptionNameTypeOfService 			= 3,
	kSocketOptionNameIpTimeToLive 			= 4,
	kSocketOptionNameMulticastInterface 	= 9,
	kSocketOptionNameMulticastTimeToLive 	= 10,
	kSocketOptionNameMulticastLoopback 		= 11,
	kSocketOptionNameAddMembership 			= 12,
	kSocketOptionNameDropMembership 		= 13,
	kSocketOptionNameDontFragment 			= 14,
	kSocketOptionNameAddSourceMembership 	= 15,
	kSocketOptionNameDropSourceMembership 	= 16,
	kSocketOptionNameBlockSource 			= 17,
	kSocketOptionNameUnblockSource 			= 18,
	kSocketOptionNamePacketInformation 		= 19,
	kSocketOptionNameNoDelay 				= 1,
	kSocketOptionNameBsdUrgent 				= 2,
	kSocketOptionNameExpedited 				= 2,
	kSocketOptionNameNoChecksum				= 1,
	kSocketOptionNameChecksumCoverage 		= 20,

// #if NET_2_0
	kSocketOptionNameHopLimit 				= 21,
	kSocketOptionNameUpdateAcceptContext	= 28683,
	kSocketOptionNameUpdateConnectContext	= 28688,
// #endif

};

enum PollFlags
{
	kPollFlagsNone 	= 0,
	kPollFlagsIn 	= 1,
	kPollFlagsPri	= 2,
	kPollFlagsOut	= 4,
	kPollFlagsErr	= 8,
	kPollFlagsHup	= 0x10,
	kPollFlagsNVal	= 0x20,
	kPollFlagsAny	= 0xffffffff
};

enum TransmitFileOptions
{
	kTransmitFileOptionsUseDefaultWorkerThread	= 0x00000000,
	kTransmitFileOptionsDisconnect				= 0x00000001,
	kTransmitFileOptionsReuseSocket				= 0x00000002,
	kTransmitFileOptionsWriteBehind				= 0x00000004,
	kTransmitFileOptionsUseSystemThread			= 0x00000010,
	kTransmitFileOptionsUseKernelApc			= 0x00000020,
};

class Socket;

struct PollRequest
{
	Socket *socket;
	PollFlags events;
	PollFlags revents;
};

// TODO: this should really be UNIX_PATH_MAX or SUN_LEN(n)
#define END_POINT_MAX_PATH_LEN	255

struct EndPointInfo
{
	AddressFamily family;
	
	union {
		struct {
			uint32_t port;
			uint32_t address;
		} inet;
		char path[END_POINT_MAX_PATH_LEN];
		uint8_t raw[IL2CPP_ZERO_LEN_ARRAY];
	} data;
};

// NOTE(gab): this must be binary compatible with Windows's WSABUF
struct WSABuf
{
	uint32_t length;
	void *buffer;
};

// NOTE(gab): this must be binary compatible with Window's TRANSMIT_FILE_BUFFERS
struct TransmitFileBuffers
{
	void *head;
	uint32_t head_length;
	void *tail;
	uint32_t tail_length;
};

// Note: this callback can be invoked by the os-specific implementation when an
// interrupt is received or when the native code is looping in a potentially long
// loop.
// If the callback retun false, the executiong of the os-specific method is
// gracefully interrupted, and an error is supposed to be returned by the
// os-specific implementation.
// The callback is allowed to throw exceptions (for example a ThreadAborted exception):
// in this case, it is up to the os-specific implementation to properly deal with
// cleaning up the temporarely allocated memory (if any).
typedef bool (*ThreadStatusCallback)();

class Socket : public il2cpp::utils::NonCopyable
{
public:
	
	Socket (ThreadStatusCallback thread_status_callback);
	~Socket ();
	
	// Note: this Create is only used internally
	WaitStatus Create (int64_t fd, int32_t family, int32_t type, int32_t protocol);
	WaitStatus Create (AddressFamily family, SocketType type, ProtocolType protocol);

	bool IsClosed ();
	void Close ();

	int64_t GetDescriptor ();
	
	ErrorCode GetLastError () const;
	
	WaitStatus SetBlocking (bool blocking);
	
	WaitStatus Listen (int32_t blacklog);
	
	WaitStatus Bind (const char *path);
	WaitStatus Bind (uint32_t address, uint16_t port);
	WaitStatus Bind (const char *address, uint16_t port);
	
	WaitStatus Connect (const char *path);
	WaitStatus Connect (uint32_t address, uint16_t port);
	
	WaitStatus Disconnect (bool reuse);
	WaitStatus Shutdown (int32_t how);
	
	WaitStatus GetLocalEndPointInfo (EndPointInfo &info);
	WaitStatus GetRemoteEndPointInfo (EndPointInfo &info);
	
	WaitStatus Receive (const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	WaitStatus Send (const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	
	WaitStatus SendArray (WSABuf *wsabufs, int32_t count, int32_t *sent, SocketFlags c_flags);
	WaitStatus ReceiveArray (WSABuf *wsabufs, int32_t count, int32_t *len, SocketFlags c_flags);
	
	WaitStatus SendTo (uint32_t address, uint16_t port, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	WaitStatus SendTo (const char *path, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	
	WaitStatus RecvFrom (uint32_t address, uint16_t port, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len, os::EndPointInfo &ep);
	WaitStatus RecvFrom (const char *path, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len, os::EndPointInfo &ep);
	
	WaitStatus Available (int32_t *amount);
	
	WaitStatus Accept (Socket **socket);
	
	WaitStatus Ioctl (int32_t command, const uint8_t *in_data, int32_t in_len, uint8_t *out_data, int32_t out_len, int32_t *written);
	
	WaitStatus GetSocketOption (SocketOptionLevel level, SocketOptionName name, uint8_t *buffer, int32_t *length);
	WaitStatus GetSocketOptionFull (SocketOptionLevel level, SocketOptionName name, int32_t *first, int32_t *second);
	
	WaitStatus SetSocketOption (SocketOptionLevel level, SocketOptionName name, int32_t value);
	WaitStatus SetSocketOptionLinger (SocketOptionLevel level, SocketOptionName name, bool enabled, int32_t seconds);
	WaitStatus SetSocketOptionArray (SocketOptionLevel level, SocketOptionName name, const uint8_t *buffer, int32_t length);
	WaitStatus SetSocketOptionMembership (SocketOptionLevel level, SocketOptionName name, uint32_t group_address, uint32_t local_address);
	
	WaitStatus SendFile (const char *filename, TransmitFileBuffers *buffers, TransmitFileOptions options);
	
	static WaitStatus Poll (std::vector<PollRequest> &requests, int32_t timeout, int32_t *result, int32_t *error);
	
	static WaitStatus GetHostName (std::string &name);
	static WaitStatus GetHostByName (const std::string &host, std::string &name, std::vector<std::string> &aliases, std::vector<std::string> &addr_list);
	static WaitStatus GetHostByAddr (const std::string &address, std::string &name, std::vector<std::string> &aliases, std::vector<std::string> &addr_list);
	
	static void Startup ();
	static void Cleanup ();

private:
	SocketImpl* m_Socket;
};

/// Sockets should generally be referenced through SocketHandles for thread-safety.
/// Handles are stored in a table and can be safely used even when the socket has already
/// been deleted.
typedef uint32_t SocketHandle;

enum
{
	kInvalidSocketHandle = 0
};

SocketHandle CreateSocketHandle (Socket* socket);
Socket* AcquireSocketHandle (SocketHandle handle);
void ReleaseSocketHandle (SocketHandle handle);

inline SocketHandle PointerToSocketHandle (void* ptr)
{
	// Double cast to avoid warnings.
	return static_cast<SocketHandle> (reinterpret_cast<size_t> (ptr));
}

/// Helper to automatically acquire and release a Socket within a scope.
struct SocketHandleWrapper
{
	SocketHandleWrapper ()
		: m_Handle (kInvalidSocketHandle)
		, m_Socket (NULL) {}
	SocketHandleWrapper (SocketHandle handle)
		: m_Handle (handle)
	{
		m_Socket = AcquireSocketHandle (handle);
	}
	SocketHandleWrapper (const SocketHandleWrapper& other)
	{
		m_Handle = other.m_Handle;
		if (m_Handle != kInvalidSocketHandle)
			m_Socket = AcquireSocketHandle (m_Handle);
		else
			m_Socket = NULL;
	}
	~SocketHandleWrapper ()
	{
		Release ();
	}

	void Acquire (SocketHandle handle)
	{
		Release ();
		m_Handle = handle;
		m_Socket = AcquireSocketHandle (handle);
	}

	void Release ()
	{
		if (m_Socket)
			ReleaseSocketHandle (m_Handle);
		m_Socket = NULL;
		m_Handle = kInvalidSocketHandle;
	}

	bool IsValid () const
	{
		return (m_Socket != NULL);
	}
	SocketHandle GetHandle () const
	{
		return m_Handle;
	}
	Socket* GetSocket () const
	{
		return m_Socket;
	}
	
	Socket* operator-> () const
	{
		return GetSocket ();
	}
	SocketHandleWrapper& operator= (const SocketHandleWrapper& other)
	{
		Acquire (other.GetHandle ());
		return *this;
	}

private:
	SocketHandle m_Handle;
	Socket* m_Socket;
};

}
}
