// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <algorithm>
#ifndef _WIN32
#include <unistd.h>
#endif

#include "Common/FileUtil.h"
#include "Core/Core.h"
#include "Core/IPC_HLE/WII_IPC_HLE.h"
#include "Core/IPC_HLE/WII_IPC_HLE_Device.h"
#include "Core/IPC_HLE/WII_Socket.h" // No Wii socket support while using NetPlay or TAS

#ifdef _WIN32
#define ERRORCODE(name) WSA ## name
#define EITHER(win32, posix) win32
#else
#define ERRORCODE(name) name
#define EITHER(win32, posix) posix
#endif

char* WiiSockMan::DecodeError(s32 ErrorCode)
{
#ifdef _WIN32
	// NOT THREAD SAFE
	static char Message[1024];

	FormatMessageA(
		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
		nullptr, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), Message,
		sizeof(Message), nullptr);

	return Message;
#else
	return strerror(ErrorCode);
#endif
}

static s32 TranslateErrorCode(s32 native_error, bool isRW)
{
	switch (native_error)
	{
	case ERRORCODE(EMSGSIZE):
		ERROR_LOG(WII_IPC_NET, "Find out why this happened, looks like PEEK failure?");
		return -1; // Should be -SO_EMSGSIZE
	case EITHER(WSAENOTSOCK, EBADF):
		return -SO_EBADF;
	case ERRORCODE(EADDRINUSE):
		return -SO_EADDRINUSE;
	case ERRORCODE(ECONNRESET):
		return -SO_ECONNRESET;
	case ERRORCODE(EISCONN):
		return -SO_EISCONN;
	case ERRORCODE(ENOTCONN):
		return -SO_EAGAIN; // After proper blocking SO_EAGAIN shouldn't be needed...
	case ERRORCODE(EINPROGRESS):
		return -SO_EINPROGRESS;
	case ERRORCODE(EALREADY):
		return -SO_EALREADY;
	case ERRORCODE(EACCES):
		return -SO_EACCES;
	case ERRORCODE(ECONNREFUSED):
		return -SO_ECONNREFUSED;
	case ERRORCODE(ENETUNREACH):
		return -SO_ENETUNREACH;
	case ERRORCODE(EHOSTUNREACH):
		return -SO_EHOSTUNREACH;
	case EITHER(WSAEWOULDBLOCK, EAGAIN):
		if (isRW)
		{
			return -SO_EAGAIN;  // EAGAIN
		}
		else
		{
			return -SO_EINPROGRESS; // EINPROGRESS
		}
	default:
		return -1;
	}
}

// Don't use string! (see https://github.com/dolphin-emu/dolphin/pull/3143)
s32 WiiSockMan::GetNetErrorCode(s32 ret, const char* caller, bool isRW)
{
#ifdef _WIN32
	s32 errorCode = WSAGetLastError();
#else
	s32 errorCode = errno;
#endif

	if (ret >= 0)
	{
		WiiSockMan::GetInstance().SetLastNetError(ret);
		return ret;
	}

	INFO_LOG(WII_IPC_NET, "%s failed with error %d: %s, ret= %d",
		caller, errorCode, DecodeError(errorCode), ret);

	s32 ReturnValue = TranslateErrorCode(errorCode, isRW);
	WiiSockMan::GetInstance().SetLastNetError(ReturnValue);

	return ReturnValue;
}

WiiSocket::~WiiSocket()
{
	if (fd >= 0)
	{
		(void)CloseFd();
	}
}

void WiiSocket::SetFd(s32 s)
{
	if (fd >= 0)
		(void)CloseFd();

	nonBlock = false;
	fd = s;

	// Set socket to NON-BLOCK
#ifdef _WIN32
	u_long iMode = 1;
	ioctlsocket(fd, FIONBIO, &iMode);
#else
	int flags;
	if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
		flags = 0;
	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
#endif
}

s32 WiiSocket::CloseFd()
{
	s32 ReturnValue = 0;
	if (fd >= 0)
	{
#ifdef _WIN32
		s32 ret = closesocket(fd);
#else
		s32 ret = close(fd);
#endif
		ReturnValue = WiiSockMan::GetNetErrorCode(ret, "CloseFd", false);
	}
	else
	{
		ReturnValue = WiiSockMan::GetNetErrorCode(EITHER(WSAENOTSOCK, EBADF), "CloseFd", false);
	}
	fd = -1;
	return ReturnValue;
}

s32 WiiSocket::FCntl(u32 cmd, u32 arg)
{
#define F_GETFL     3
#define F_SETFL     4
#define F_NONBLOCK  4
	s32 ret = 0;
	if (cmd == F_GETFL)
	{
		ret = nonBlock ? F_NONBLOCK : 0;
	}
	else if (cmd == F_SETFL)
	{
		nonBlock = (arg & F_NONBLOCK) == F_NONBLOCK;
	}
	else
	{
		ERROR_LOG(WII_IPC_NET, "SO_FCNTL unknown command");
	}

	INFO_LOG(WII_IPC_NET, "IOCTL_SO_FCNTL(%08x, %08X, %08X)",
		fd, cmd, arg);

	return ret;
}

void WiiSocket::Update(bool read, bool write, bool except)
{
	auto it = pending_sockops.begin();
	while (it != pending_sockops.end())
	{
		s32 ReturnValue = 0;
		bool forceNonBlock = false;
		IPCCommandType ct = static_cast<IPCCommandType>(Memory::Read_U32(it->_CommandAddress));
		if (!it->is_ssl && ct == IPC_CMD_IOCTL)
		{
			u32 BufferIn = Memory::Read_U32(it->_CommandAddress + 0x10);
			u32 BufferInSize = Memory::Read_U32(it->_CommandAddress + 0x14);
			u32 BufferOut = Memory::Read_U32(it->_CommandAddress + 0x18);
			u32 BufferOutSize = Memory::Read_U32(it->_CommandAddress + 0x1C);

			switch (it->net_type)
			{
			case IOCTL_SO_FCNTL:
			{
				u32 cmd = Memory::Read_U32(BufferIn + 4);
				u32 arg = Memory::Read_U32(BufferIn + 8);
				ReturnValue = FCntl(cmd, arg);
				break;
			}
			case IOCTL_SO_BIND:
			{
				//u32 has_addr = Memory::Read_U32(BufferIn + 0x04);
				sockaddr_in local_name;
				WiiSockAddrIn* wii_name = (WiiSockAddrIn*)Memory::GetPointer(BufferIn + 0x08);
				WiiSockMan::Convert(*wii_name, local_name);

				int ret = bind(fd, (sockaddr*)&local_name, sizeof(local_name));
				ReturnValue = WiiSockMan::GetNetErrorCode(ret, "SO_BIND", false);

				INFO_LOG(WII_IPC_NET, "IOCTL_SO_BIND (%08X %s:%d) = %d ", fd,
					inet_ntoa(local_name.sin_addr), Common::swap16(local_name.sin_port), ret);
				break;
			}
			case IOCTL_SO_CONNECT:
			{
				//u32 has_addr = Memory::Read_U32(BufferIn + 0x04);
				sockaddr_in local_name;
				WiiSockAddrIn* wii_name = (WiiSockAddrIn*)Memory::GetPointer(BufferIn + 0x08);
				WiiSockMan::Convert(*wii_name, local_name);

				int ret = connect(fd, (sockaddr*)&local_name, sizeof(local_name));
				ReturnValue = WiiSockMan::GetNetErrorCode(ret, "SO_CONNECT", false);

				INFO_LOG(WII_IPC_NET,"IOCTL_SO_CONNECT (%08x, %s:%d)",
					fd, inet_ntoa(local_name.sin_addr), Common::swap16(local_name.sin_port));
				break;
			}
			case IOCTL_SO_ACCEPT:
			{
				if (BufferOutSize > 0)
				{
					sockaddr_in local_name;
					WiiSockAddrIn* wii_name = (WiiSockAddrIn*)Memory::GetPointer(BufferOut);
					WiiSockMan::Convert(*wii_name, local_name);

					socklen_t addrlen = sizeof(sockaddr_in);
					int ret = (s32)accept(fd, (sockaddr*)&local_name, &addrlen);
					ReturnValue = WiiSockMan::GetNetErrorCode(ret, "SO_ACCEPT", true);

					WiiSockMan::Convert(local_name, *wii_name, addrlen);
				}
				else
				{
					int ret = (s32)accept(fd, nullptr, nullptr);
					ReturnValue = WiiSockMan::GetNetErrorCode(ret, "SO_ACCEPT", true);
				}

				WiiSockMan::GetInstance().AddSocket(ReturnValue);

				INFO_LOG(WII_IPC_NET, "IOCTL_SO_ACCEPT "
					"BufferIn: (%08x, %i), BufferOut: (%08x, %i)",
					BufferIn, BufferInSize, BufferOut, BufferOutSize);

				break;
			}
			default:
				break;
			}

			// Fix blocking error codes
			if (!nonBlock)
			{
				if (it->net_type == IOCTL_SO_CONNECT &&
				    ReturnValue == -SO_EISCONN)
				{
					ReturnValue = SO_SUCCESS;
				}
			}
		}
		else if (ct == IPC_CMD_IOCTLV)
		{
			SIOCtlVBuffer CommandBuffer(it->_CommandAddress);
			u32 BufferIn = 0, BufferIn2 = 0;
			u32 BufferInSize = 0, BufferInSize2 = 0;
			u32 BufferOut = 0, BufferOut2 = 0;
			u32 BufferOutSize = 0, BufferOutSize2 = 0;

			if (CommandBuffer.InBuffer.size() > 0)
			{
				BufferIn = CommandBuffer.InBuffer.at(0).m_Address;
				BufferInSize = CommandBuffer.InBuffer.at(0).m_Size;
			}

			if (CommandBuffer.PayloadBuffer.size() > 0)
			{
				BufferOut = CommandBuffer.PayloadBuffer.at(0).m_Address;
				BufferOutSize = CommandBuffer.PayloadBuffer.at(0).m_Size;
			}

			if (CommandBuffer.PayloadBuffer.size() > 1)
			{
				BufferOut2 = CommandBuffer.PayloadBuffer.at(1).m_Address;
				BufferOutSize2 = CommandBuffer.PayloadBuffer.at(1).m_Size;
			}

			if (CommandBuffer.InBuffer.size() > 1)
			{
				BufferIn2 = CommandBuffer.InBuffer.at(1).m_Address;
				BufferInSize2 = CommandBuffer.InBuffer.at(1).m_Size;
			}

			if (it->is_ssl)
			{
				int sslID = Memory::Read_U32(BufferOut) - 1;
				if (SSLID_VALID(sslID))
				{
					switch (it->ssl_type)
					{
					case IOCTLV_NET_SSL_DOHANDSHAKE:
					{

						int ret = mbedtls_ssl_handshake(&CWII_IPC_HLE_Device_net_ssl::_SSL[sslID].ctx);
						switch (ret)
						{
						case 0:
							Memory::Write_U32(SSL_OK, BufferIn);
							break;
						case MBEDTLS_ERR_SSL_WANT_READ:
							Memory::Write_U32(SSL_ERR_RAGAIN, BufferIn);
							if (!nonBlock)
								ReturnValue = SSL_ERR_RAGAIN;
							break;
						case MBEDTLS_ERR_SSL_WANT_WRITE:
							Memory::Write_U32(SSL_ERR_WAGAIN, BufferIn);
							if (!nonBlock)
								ReturnValue = SSL_ERR_WAGAIN;
							break;
						default:
							Memory::Write_U32(SSL_ERR_FAILED, BufferIn);
							break;
						}

						INFO_LOG(WII_IPC_SSL, "IOCTLV_NET_SSL_DOHANDSHAKE = (%d) "
							"BufferIn: (%08x, %i), BufferIn2: (%08x, %i), "
							"BufferOut: (%08x, %i), BufferOut2: (%08x, %i)",
							ret,
							BufferIn, BufferInSize, BufferIn2, BufferInSize2,
							BufferOut, BufferOutSize, BufferOut2, BufferOutSize2);
						break;
					}
					case IOCTLV_NET_SSL_WRITE:
					{
						int ret = mbedtls_ssl_write(&CWII_IPC_HLE_Device_net_ssl::_SSL[sslID].ctx, Memory::GetPointer(BufferOut2), BufferOutSize2);

#ifdef DEBUG_SSL
						File::IOFile("ssl_write.bin", "ab").WriteBytes(Memory::GetPointer(BufferOut2), BufferOutSize2);
#endif
						if (ret >= 0)
						{
							// Return bytes written or SSL_ERR_ZERO if none
							Memory::Write_U32((ret == 0) ? SSL_ERR_ZERO : ret, BufferIn);
						}
						else
						{
							switch (ret)
							{
							case MBEDTLS_ERR_SSL_WANT_READ:
								Memory::Write_U32(SSL_ERR_RAGAIN, BufferIn);
								if (!nonBlock)
									ReturnValue = SSL_ERR_RAGAIN;
								break;
							case MBEDTLS_ERR_SSL_WANT_WRITE:
								Memory::Write_U32(SSL_ERR_WAGAIN, BufferIn);
								if (!nonBlock)
									ReturnValue = SSL_ERR_WAGAIN;
								break;
							default:
								Memory::Write_U32(SSL_ERR_FAILED, BufferIn);
								break;
							}
						}
						break;
					}
					case IOCTLV_NET_SSL_READ:
					{
						int ret = mbedtls_ssl_read(&CWII_IPC_HLE_Device_net_ssl::_SSL[sslID].ctx, Memory::GetPointer(BufferIn2), BufferInSize2);
#ifdef DEBUG_SSL
						if (ret > 0)
						{
							File::IOFile("ssl_read.bin", "ab").WriteBytes(Memory::GetPointer(BufferIn2), ret);
						}
#endif
						if (ret >= 0)
						{
							// Return bytes read or SSL_ERR_ZERO if none
							Memory::Write_U32((ret == 0) ? SSL_ERR_ZERO : ret, BufferIn);
						}
						else
						{
							switch (ret)
							{
							case MBEDTLS_ERR_SSL_WANT_READ:
								Memory::Write_U32(SSL_ERR_RAGAIN, BufferIn);
								if (!nonBlock)
									ReturnValue = SSL_ERR_RAGAIN;
								break;
							case MBEDTLS_ERR_SSL_WANT_WRITE:
								Memory::Write_U32(SSL_ERR_WAGAIN, BufferIn);
								if (!nonBlock)
									ReturnValue = SSL_ERR_WAGAIN;
								break;
							default:
								Memory::Write_U32(SSL_ERR_FAILED, BufferIn);
								break;
							}
						}
						break;
					}
					default:
						break;
					}
				}
				else
				{
					Memory::Write_U32(SSL_ERR_ID, BufferIn);
				}
			}
			else
			{
				switch (it->net_type)
				{
				case IOCTLV_SO_SENDTO:
				{

					u32 flags = Memory::Read_U32(BufferIn2 + 0x04);
					u32 has_destaddr = Memory::Read_U32(BufferIn2 + 0x08);

                                        // Not a string, Windows requires a const char* for sendto
					const char* data = (const char*)Memory::GetPointer(BufferIn);

					// Act as non blocking when SO_MSG_NONBLOCK is specified
					forceNonBlock = ((flags & SO_MSG_NONBLOCK) == SO_MSG_NONBLOCK);
					// send/sendto only handles MSG_OOB
					flags &= SO_MSG_OOB;

					sockaddr_in local_name = {0};
					if (has_destaddr)
					{
						WiiSockAddrIn* wii_name = (WiiSockAddrIn*)Memory::GetPointer(BufferIn2 + 0x0C);
						WiiSockMan::Convert(*wii_name, local_name);
					}

					int ret = sendto(fd, data, BufferInSize, flags,
						has_destaddr ? (struct sockaddr*)&local_name : nullptr,
						has_destaddr ? sizeof(sockaddr) :  0);
					ReturnValue = WiiSockMan::GetNetErrorCode(ret, "SO_SENDTO", true);

					INFO_LOG(WII_IPC_NET,
						"%s = %d Socket: %08x, BufferIn: (%08x, %i), BufferIn2: (%08x, %i), %u.%u.%u.%u",
						has_destaddr ? "IOCTLV_SO_SENDTO " : "IOCTLV_SO_SEND ",
						ReturnValue, fd, BufferIn, BufferInSize,
						BufferIn2, BufferInSize2,
						local_name.sin_addr.s_addr & 0xFF,
						(local_name.sin_addr.s_addr >> 8) & 0xFF,
						(local_name.sin_addr.s_addr >> 16) & 0xFF,
						(local_name.sin_addr.s_addr >> 24) & 0xFF
						);
					break;
				}
				case IOCTLV_SO_RECVFROM:
				{
					u32 flags = Memory::Read_U32(BufferIn + 0x04);
					// Not a string, Windows requires a char* for recvfrom
					char* data = (char*)Memory::GetPointer(BufferOut);
					int data_len = BufferOutSize;

					sockaddr_in local_name;
					memset(&local_name, 0, sizeof(sockaddr_in));

					if (BufferOutSize2 != 0)
					{
						WiiSockAddrIn* wii_name = (WiiSockAddrIn*)Memory::GetPointer(BufferOut2);
						WiiSockMan::Convert(*wii_name, local_name);
					}

					// Act as non blocking when SO_MSG_NONBLOCK is specified
					forceNonBlock = ((flags & SO_MSG_NONBLOCK) == SO_MSG_NONBLOCK);

					// recv/recvfrom only handles PEEK/OOB
					flags &= SO_MSG_PEEK | SO_MSG_OOB;
#ifdef _WIN32
					if (flags & SO_MSG_PEEK)
					{
						unsigned long totallen = 0;
						ioctlsocket(fd, FIONREAD, &totallen);
						ReturnValue = totallen;
						break;
					}
#endif
					socklen_t addrlen = sizeof(sockaddr_in);
					int ret = recvfrom(fd, data, data_len, flags,
									BufferOutSize2 ? (struct sockaddr*) &local_name : nullptr,
									BufferOutSize2 ? &addrlen : nullptr);
					ReturnValue = WiiSockMan::GetNetErrorCode(ret, BufferOutSize2 ? "SO_RECVFROM" : "SO_RECV", true);

					INFO_LOG(WII_IPC_NET, "%s(%d, %p) Socket: %08X, Flags: %08X, "
					"BufferIn: (%08x, %i), BufferIn2: (%08x, %i), "
					"BufferOut: (%08x, %i), BufferOut2: (%08x, %i)",
					BufferOutSize2 ? "IOCTLV_SO_RECVFROM " : "IOCTLV_SO_RECV ",
					ReturnValue, data, fd, flags,
					BufferIn, BufferInSize, BufferIn2, BufferInSize2,
					BufferOut, BufferOutSize, BufferOut2, BufferOutSize2);

					if (BufferOutSize2 != 0)
					{
						WiiSockAddrIn* wii_name = (WiiSockAddrIn*)Memory::GetPointer(BufferOut2);
						WiiSockMan::Convert(local_name, *wii_name, addrlen);
					}
					break;
				}
				default:
					break;
				}
			}
		}

		if (nonBlock ||
			forceNonBlock ||
		    (!it->is_ssl &&
		     ReturnValue != -SO_EAGAIN &&
		     ReturnValue != -SO_EINPROGRESS &&
		     ReturnValue != -SO_EALREADY) ||
		    (it->is_ssl &&
		     ReturnValue != SSL_ERR_WAGAIN &&
		     ReturnValue != SSL_ERR_RAGAIN))
		{
			DEBUG_LOG(WII_IPC_NET,
			          "IOCTL(V) Sock: %08x ioctl/v: %d returned: %d nonBlock: %d forceNonBlock: %d",
			          fd, it->is_ssl ? (int) it->ssl_type : (int) it->net_type, ReturnValue, nonBlock, forceNonBlock);
			WiiSockMan::EnqueueReply(it->_CommandAddress, ReturnValue, ct);
			it = pending_sockops.erase(it);
		}
		else
		{
			++it;
		}
	}
}

void WiiSocket::DoSock(u32 _CommandAddress, NET_IOCTL type)
{
	sockop so = {_CommandAddress, false};
	so.net_type = type;
	pending_sockops.push_back(so);
}

void WiiSocket::DoSock(u32 _CommandAddress, SSL_IOCTL type)
{
	sockop so = {_CommandAddress, true};
	so.ssl_type = type;
	pending_sockops.push_back(so);
}

void WiiSockMan::AddSocket(s32 fd)
{
	if (fd >= 0)
	{
		WiiSocket& sock = WiiSockets[fd];
		sock.SetFd(fd);
	}
}

s32 WiiSockMan::NewSocket(s32 af, s32 type, s32 protocol)
{
	s32 fd = (s32)socket(af, type, protocol);
	s32 ret = GetNetErrorCode(fd, "NewSocket", false);
	AddSocket(ret);
	return ret;
}

s32 WiiSockMan::DeleteSocket(s32 s)
{
	auto socket_entry = WiiSockets.find(s);
	s32 ReturnValue = socket_entry->second.CloseFd();
	WiiSockets.erase(socket_entry);
	return ReturnValue;
}

void WiiSockMan::Update()
{
	s32 nfds = 0;
	fd_set read_fds, write_fds, except_fds;
	struct timeval t = {0,0};
	FD_ZERO(&read_fds);
	FD_ZERO(&write_fds);
	FD_ZERO(&except_fds);

	auto socket_iter = WiiSockets.begin();
	auto end_socks = WiiSockets.end();

	while (socket_iter != end_socks)
	{
		WiiSocket& sock = socket_iter->second;
		if (sock.IsValid())
		{
			FD_SET(sock.fd, &read_fds);
			FD_SET(sock.fd, &write_fds);
			FD_SET(sock.fd, &except_fds);
			nfds = std::max(nfds, sock.fd+1);
			++socket_iter;
		}
		else
		{
			// Good time to clean up invalid sockets.
			socket_iter = WiiSockets.erase(socket_iter);
		}
	}
	s32 ret = select(nfds, &read_fds, &write_fds, &except_fds, &t);

	if (ret >= 0)
	{
		for (auto& pair : WiiSockets)
		{
			WiiSocket& sock = pair.second;
			sock.Update(
				FD_ISSET(sock.fd, &read_fds) != 0,
				FD_ISSET(sock.fd, &write_fds) != 0,
				FD_ISSET(sock.fd, &except_fds) != 0
			);
		}
	}
	else
	{
		for (auto& elem : WiiSockets)
		{
			elem.second.Update(false, false, false);
		}
	}
}

void WiiSockMan::EnqueueReply(u32 CommandAddress, s32 ReturnValue, IPCCommandType CommandType)
{
	// The original hardware overwrites the command type with the async reply type.
	Memory::Write_U32(IPC_REP_ASYNC, CommandAddress);
	// IOS also seems to write back the command that was responded to in the FD field.
	Memory::Write_U32(CommandType, CommandAddress + 8);

	// Return value
	Memory::Write_U32(ReturnValue, CommandAddress + 4);

	WII_IPC_HLE_Interface::EnqueueReply(CommandAddress);
}


void WiiSockMan::Convert(WiiSockAddrIn const & from, sockaddr_in& to)
{
	to.sin_addr.s_addr = from.addr.addr;
	to.sin_family = from.family;
	to.sin_port = from.port;
}

void WiiSockMan::Convert(sockaddr_in const & from, WiiSockAddrIn& to, s32 addrlen)
{
	to.addr.addr = from.sin_addr.s_addr;
	to.family = from.sin_family & 0xFF;
	to.port = from.sin_port;
	if (addrlen < 0 || addrlen > (s32) sizeof(WiiSockAddrIn))
		to.len = sizeof(WiiSockAddrIn);
	else
		to.len = addrlen;
}

void WiiSockMan::UpdateWantDeterminism(bool want)
{
	// If we switched into movie recording, kill existing sockets.
	if (want)
		Clean();
}

#undef ERRORCODE
#undef EITHER
