// DWBS ------------------------------------------------------------------ DWBS
//
// PLATFORM		: PS2
// PRODUCT		: BIT_DEMON
// VISIBILITY	: PUBLIC
//
// DWBS ------------------------------------------------------------------ DWBS


#include <bdPlatform/bdPlatformSocket/bdPlatformStreamSocket.h>
#if defined BD_PLATFORM_PS2 && defined BD_PS2_USE_LIBEENET


#include <eekernel.h>
#include <eenettypes.h>
#include <netinet/in.h>
#include <libeenet.h>
#include <ifaddrs.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/errno.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <eenetctl.h>


#  undef bind
#  undef send
#  undef connect
#  undef accept

bdInt BD_CALL bdPlatformStreamSocket::create()
{
	// create the socket handle
	bdInt handle = ::sceEENetSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	
	return handle;
}

bdSocketStatusCode BD_CALL bdPlatformStreamSocket::bind(bdInt &handle, 
														const bdInAddr addr, 
														const bdUInt16 port)
{
	if(handle == BD_INVALID_SOCKET_HANDLE)
	{
		return BD_NET_INVALID_HANDLE;
	}

	struct sockaddr_in localAddr;
	// Fill in address for binding locally
	bdMemset(&localAddr, 0, sizeof(localAddr));
	localAddr.sin_family = AF_INET;
	localAddr.sin_addr.s_addr = addr.m_inAddri;
	localAddr.sin_port = htons(port);

	if ( ::sceEENetBind( handle, (struct sockaddr *)&localAddr, sizeof(localAddr) )  == BD_INVALID_SOCKET_HANDLE )
	{
		// couldn't bind
		return BD_NET_ERROR;
	}

	::sceEENetListen(handle, 5);

	return BD_NET_SUCCESS;
}

bdSocketStatusCode BD_CALL bdPlatformStreamSocket::connect(bdInt &handle, 
														   const bdInAddr addr, 
														   const bdUInt16 port)
{
	struct sockaddr_in remoteAddr;
	// Fill in address for binding locally
	bdMemset(&remoteAddr, 0, sizeof(remoteAddr));
	remoteAddr.sin_family = AF_INET;
	remoteAddr.sin_addr.s_addr = addr.m_inAddri;
	remoteAddr.sin_port = htons(port);

	const bdInt retval = ::sceEENetConnect(handle, (struct sockaddr *)&remoteAddr, sizeof(remoteAddr));

	if(retval < 0)
	{
		const bdInt error = getErrno(handle);

		switch(error)
		{
			case EWOULDBLOCK:
				return BD_NET_WOULD_BLOCK;
				break;
			case ECONNRESET:
			case ETIMEDOUT:
			case ENETUNREACH:
			case ECONNABORTED:
			case EHOSTUNREACH:
			case ENETDOWN:
				return BD_NET_CONNECTION_RESET;
				break;
			case EMSGSIZE:
				return BD_NET_MSG_SIZE;
				break;
			case EINTR:
				return BD_NET_BLOCKING_CALL_CANCELLED;
				break;
			case EINVAL:
				// This indicates the socket has not yet been bound (either
				// through bind or a call to sendTo) and we therefore can't
				// receive.
				// This happens as we have scheduled a packet to be sent (causing
				// a bind) but this thread has run before the send thread.
				// return a
				return BD_NET_NOT_BOUND;
				break;
			default:
				return BD_NET_ERROR;
		}
	}

	return BD_NET_SUCCESS;
}

bdBool BD_CALL bdPlatformStreamSocket::accept(bdInt &handle,
											  bdInt &newHandle, 
											  bdInAddr &addr, 
											  bdUInt16 &port)
{
	struct sockaddr_in remoteAddr;
	bdMemset(&remoteAddr, 0, sizeof(remoteAddr));

	sceEENetSocklen_t remoteAddrLen = sizeof(struct sockaddr_in);

	bdInt sd = static_cast<bdInt>(::sceEENetAccept(handle, reinterpret_cast<sockaddr*>(&remoteAddr), &remoteAddrLen));

	if(sd != BD_INVALID_SOCKET_HANDLE)
	{
		port = ntohs(remoteAddr.sin_port);
		addr.m_inAddri = remoteAddr.sin_addr.s_addr;

		newHandle = sd;

		return true;
	}

	return false;
}


bdInt BD_CALL bdPlatformStreamSocket::send(bdInt &handle,
										   const void *data, 
										   const bdUInt length)
{
	if(handle == BD_INVALID_SOCKET_HANDLE)
	{
		return BD_NET_INVALID_HANDLE;
	}

	bdInt sent = ::sceEENetSend(handle,
							reinterpret_cast<const bdChar8 *>(data),
							length,
							0);

	if(sent < 0)
	{
		const bdInt error = getErrno(0);
		switch(error)
		{
			case ENOTCONN:
				if (!checkSocketException(handle))
				{
					return BD_NET_CONNECTION_RESET;
				}
				return BD_NET_NOT_CONNECTED;
			default:
				return BD_NET_ERROR;
		}
	}
	else
	{
		m_totalBytesSent += sent;
	}

	return sent;
}


bdInt BD_CALL bdPlatformStreamSocket::receive(bdInt &handle,
											  void *data,
											  const bdUInt size)
{
	if(handle == BD_INVALID_SOCKET_HANDLE)
	{
		return BD_NET_INVALID_HANDLE;
	}
	// do non-blocking IO
	sceEENetPollfd fds[1];
	fds[0].fd = handle;
	fds[0].events = POLLIN;
	int dataAvail = sceEENetPoll (fds, 1, 0);
	bdInt received = 0;

	if (dataAvail)
	{
		received = ::sceEENetRecv(handle,
								  reinterpret_cast<bdChar8 *>(data),
								  size,
								  0);
	}
	else
	{
		return BD_NET_WOULD_BLOCK;
	}

	if(received < 0)
	{
		const bdInt error = getErrno(handle);

		switch(error)
		{
			case EWOULDBLOCK:
				return BD_NET_WOULD_BLOCK;
				break;
			case ECONNRESET:
			case ETIMEDOUT:
			case ENETUNREACH:
			case ECONNABORTED:
			case EHOSTUNREACH:
			case ENETDOWN:	
				return BD_NET_CONNECTION_RESET;
				break;
			case EMSGSIZE:
				return BD_NET_MSG_SIZE;
				break;
			case EINTR:
				return BD_NET_BLOCKING_CALL_CANCELLED;
				break;
			case EINVAL:
				// This indicates the socket has not yet been bound (either
				// through bind or a call to sendTo) and we therefore can't
				// receive.
				// This happens as we have scheduled a packet to be sent (causing
				// a bind) but this thread has run before the send thread.
				// return a
				return BD_NET_NOT_BOUND;
				break;
			case ENOTCONN:
				if (!checkSocketException(handle))
				{
					return BD_NET_CONNECTION_RESET;
				}
				return BD_NET_NOT_CONNECTED;
			default:
				return BD_NET_ERROR;
		}
	}
	else
	{
		m_totalBytesRecvd += received;
	}

	return received;
}

bdBool BD_CALL bdPlatformStreamSocket::close(bdInt &handle)
{
	if (handle != BD_INVALID_SOCKET_HANDLE)
	{
		sceEENetSocketAbort(handle);
		const bdBool success = ::sceEENetShutdown(handle, SHUT_RDWR) == 0;
		handle = BD_INVALID_SOCKET_HANDLE;
		return success;
	}

	return true;
}

void BD_CALL bdPlatformStreamSocket::unregisterThread()
{
}

bdBool BD_CALL bdPlatformStreamSocket::setBlocking(bdInt &handle, 
												   const bdBool blocking)
{
	if(handle == BD_INVALID_SOCKET_HANDLE)
	{
		return false;
	}

	return true;

}

bdInt BD_CALL bdPlatformStreamSocket::getErrno(bdInt handle)
{
	return  errno;
}

bdBool BD_CALL bdPlatformStreamSocket::checkSocketException(bdInt handle)
{
	struct timeval zero;
	zero.tv_sec = 0;
	zero.tv_usec = 0;
	sceEENetFdSet fdset;
	sceEENetFD_ZERO(&fdset);
	sceEENetFD_SET(handle, &fdset);
	bdInt res = ::sceEENetSelect(0, BD_NULL, BD_NULL, &fdset, &zero);
	if (res > 0)
	{
		return false;
	}
	else if (res == 0)
	{
		return true;
	}
	return false;
}

#endif // BD_PLATFORM_PS2 && BD_PS2_USE_LIBINSOCK
