///////////////////////////////////////////////////////////////////////////////
//
// File: Asura_QosSocket_BSD.cpp
//
// Created by Martin G.Harris (15/4/2009)
// 
// Copyright (C) 2009 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Network/Asura_QosSocket_BSD.h"


#ifdef ASURA_ENABLE_QOS_SYSTEM

// Includes
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "Network/Asura_NetSocket_BSD.h"
#include "Network/Asura_NetTransport_System.h"

// on PS3 these are defined to check for negative numbers, not just 
// SOCKET_ERROR/INVALID_SOCKET. This is because PS3 "helpfully" returns 
// the actual error code for many functions although this seems to be 
// undocumented, the docs just say "negative number: error" and to check
// errno in the usual way. On Win32 not all functions return signed types
// (e.g. socket()) so we must check for equality with the error value.
#ifndef ASURA_NETSOCKET_IS_ERROR_CODE
#define ASURA_NETSOCKET_IS_ERROR_CODE( iReturnValue ) ((iReturnValue) == SOCKET_ERROR)
#endif
#ifndef ASURA_NETSOCKET_IS_INVALID_SOCKET
#define ASURA_NETSOCKET_IS_INVALID_SOCKET( iReturnValue ) ((iReturnValue) == INVALID_SOCKET)
#endif

// Size of header for QoS, UDP, IP etc.
// QoS: 8 bytes for password, msg id, packet id.
const u_int uASURA_QOS_DATA_HEADER_LENGTH = sizeof(Asura_Qos_MessageHeader); // 8
const int   iASURA_QOS_DATA_HEADER_LENGTH = static_cast<int>(uASURA_QOS_DATA_HEADER_LENGTH);

///////////////////////////////////////////////////////////////////////////////
// Asura_QosSocket_BSD
///////////////////////////////////////////////////////////////////////////////
Asura_QosSocket_BSD::Asura_QosSocket_BSD()
: m_xSocket(INVALID_SOCKET)
, m_usLocalPort(0)
{
}

Asura_QosSocket_BSD::~Asura_QosSocket_BSD()
{
	Asura_Assert(m_xSocket == INVALID_SOCKET, "Socket still open!");
}

//////////////////
// CreateSocket //
//////////////////
void Asura_QosSocket_BSD::CreateSocket(Asura_Qos_Listener* pxListener, u_short usLocalPort)
{
	Asura_Assert(!m_bCreatedSocket, "Socket has already been created!");
	if (m_bCreatedSocket) return;

	m_usLocalPort = usLocalPort;

	// Create a SOCKET for listening for incoming connection requests
	m_xSocket = socket(AF_INET, ASURA_QOSSOCKET_SOCKETTYPE, ASURA_QOSSOCKET_PROTOCOL);

	if (ASURA_NETSOCKET_IS_INVALID_SOCKET(m_xSocket))
	{
		Asura_Assertf(0, ("Error at socket(): %d", WSAGetLastError()));
		return;
	}

	m_bCreatedSocket = true;

	//----------------------
	// The sockaddr_in structure specifies the address family,
	// IP address, and port for the socket that is being bound.
	Asura_NetSocket_BSD_AddressUnion xAddress;
	Asura_Mem::Set(&xAddress, 0, sizeof(SOCKADDR_IN));
	
	xAddress.xSockAddrIn.sin_family = AF_INET;
	xAddress.xSockAddrIn.sin_addr.s_addr = htonl(INADDR_ANY);
	xAddress.xSockAddrIn.sin_port = htons(m_usLocalPort);

#ifdef ASURA_PLATFORM_PS3
	xAddress.xSockAddrIn.sin_vport = htons(ASURA_PS3_QOSSOCKET_VPORT);
#endif //ASURA_PLATFORM_PS3

	// Bind the socket.
	const int iReturn = bind( m_xSocket, &xAddress.xSockAddr, sizeof(SOCKADDR_IN) );
	if ( ASURA_NETSOCKET_IS_ERROR_CODE(iReturn) )
	{
		Asura_Assertf(0, ("bind() failed (error %d).", WSAGetLastError()));
		CloseSocket();
		return;
	}

#ifdef ASURA_QOS_ENABLE_LOGGING
	// Find the socket name
	int iLength = sizeof(SOCKADDR_IN);
	if (getsockname(m_xSocket, &xAddress.xSockAddr, &iLength) == 0)
	{
		Asura_OutputToDebugger_Retail(("Qos: Bind socket to %s:%u\n", inet_ntoa(xAddress.xSockAddrIn.sin_addr), ntohs(xAddress.xSockAddrIn.sin_port)));
	}
#endif //#ifdef ASURA_QOS_ENABLE_LOGGING

	return;
}

/////////////////
// CloseSocket //
/////////////////
void Asura_QosSocket_BSD::CloseSocket()
{
	if (m_bCreatedSocket)
	{
		m_bCreatedSocket = false;
		closesocket(m_xSocket);
		m_xSocket = INVALID_SOCKET;
	}
}

////////////
// SendTo //
////////////
// Return true if succeeded.
bool Asura_QosSocket_BSD::SendTo(const Asura_Qos_NetConnection* pxDestination, const char* pcData, int iDataLength)
{
	Asura_Assert(pxDestination, "Null Qos destination");
	if (!pxDestination) return false;

	Asura_Assert(pcData, "Null Qos message data");
	if (!pcData) return false;

	Asura_Assertf(iDataLength>0, ("Invalid message data length (%d)", iDataLength));
	if (iDataLength <= 0) return false;

	fd_set xFDSet;

	// Wait (1 second) for socket to become free if necessary.
	// TODO may need to fix the select in the other thread when ported back to Asura.
	struct timeval xTimeOut = {1, 0};

	FD_ZERO(&xFDSet);
	FD_SET(m_xSocket, &xFDSet);

	int iNumSockets = select(FD_SETSIZE, NULL, &xFDSet, NULL, &xTimeOut);
	
	if (ASURA_NETSOCKET_IS_ERROR_CODE(iNumSockets) || iNumSockets == 0 || iNumSockets > FD_SETSIZE)
	{
		Asura_Assertf(0, ("Error selecting socket (%d).", WSAGetLastError()));
		return false;
	}

	Asura_NetSocket_BSD_AddressUnion xAddress;
	Asura_Mem::Set(&xAddress, 0, sizeof(SOCKADDR_IN));

	xAddress.xSockAddrIn.sin_family = AF_INET;
	xAddress.xSockAddrIn.sin_addr.s_addr = Asura_NetSocket::Platform_GetINetAddress(pxDestination->GetRemoteAddress());
	xAddress.xSockAddrIn.sin_port = htons(pxDestination->GetRemotePort());

#ifdef ASURA_PLATFORM_PS3
	xAddress.xSockAddrIn.sin_vport = htons(ASURA_PS3_QOSSOCKET_VPORT);
#endif //ASURA_PLATFORM_PS3

	const int iResult = sendto(m_xSocket, pcData, iDataLength, 0, &xAddress.xSockAddr, sizeof(SOCKADDR_IN));
	const bool bOk = !ASURA_NETSOCKET_IS_ERROR_CODE(iResult);

	Asura_Assertf(bOk, ("Error sending data to socket (%d).", WSAGetLastError()));
	return bOk;
}

// MGH 16/4/2009: Since we are using a small stack for the listener thread
// (with a size of only 16k on the PS3), I've moved this buffer from the stack
// to being a static variable. I've also reduced it from 8k to 2k in size.
static Asura_Qos_Message_Padded g_xBuffer; // Static buffer for receiving messages in.

/////////////
// Receive //
/////////////
bool Asura_QosSocket_BSD::Receive(Asura_Qos_Listener* pxListener, u_int uWaitTimeSeconds /*= 0*/)
{
	Asura_Assert(pxListener, "Null Qos listener");
	if (!pxListener) return false;

	if (!m_bCreatedSocket) return false;

	fd_set xFDSet;
	struct timeval xTimeout = {static_cast<long>(uWaitTimeSeconds), 0};
	int iNumSockets = 0;

	FD_ZERO(&xFDSet);
	FD_SET(m_xSocket, &xFDSet);

	while((iNumSockets = select(FD_SETSIZE, &xFDSet, NULL, NULL, &xTimeout)) > 0)
	{
		xTimeout.tv_sec = 0; // Clear the timer.
	
		const int iBufferLength = sizeof(g_xBuffer);
		char* const pcBuffer = reinterpret_cast<char*>(&g_xBuffer);

		Asura_NetSocket_BSD_AddressUnion xAddress;
		int iAddressLength = sizeof(xAddress);

		const int iBytesReceived = recvfrom(m_xSocket, pcBuffer, iBufferLength, 0, &xAddress.xSockAddr, &iAddressLength);

		if (ASURA_NETSOCKET_IS_ERROR_CODE(iBytesReceived))
		{	
			const int iErrorCode = WSAGetLastError();
			if (iErrorCode == WSAECONNRESET)
			{
				// Other machine probably did a hard disconnect.
				// Just wait for the connection to timeout.
				return true; // Don't take this as being an error.
			}

			Asura_Assertf(0, ("Error receiving Qos data (%d).", iErrorCode));

			if (iErrorCode == WSAEMSGSIZE)
			{
				// Message is too large for the buffer. This should never happen,
				// but if it does then the message will just be ignored and we
				// should be able to recover from the problem.
				return true;
			}

			return false;
		}

		if (iBytesReceived == 0)
		{
			// Gracefully closed.
			return true;
		}

		if (iBytesReceived < iASURA_QOS_DATA_HEADER_LENGTH)
		{
			Asura_Assert(iBytesReceived < iASURA_QOS_DATA_HEADER_LENGTH, "Too few bytes received.");
			return false;
		}

		if (iBytesReceived > ASURA_QOS_RECEIVE_BUFFER_SIZE)
		{
			Asura_Assert(iBytesReceived <= ASURA_QOS_RECEIVE_BUFFER_SIZE , "Too many bytes received, overflowed buffer.");
			return false;
		}

		const Asura_Qos_Message* pxMessage = &g_xBuffer;
		if (pxMessage->IsPasswordValid())
		{
			const u_int uFromIPAddress = xAddress.xSockAddrIn.sin_addr.s_addr; // NBO
			const u_short usFromPort   = ntohs(xAddress.xSockAddrIn.sin_port); // HBO

			Asura_Qos_NetConnectionInfo xFromInfo(uFromIPAddress, usFromPort);

			pxListener->HandleMessageFrom(xFromInfo, pxMessage);
		}
	}

	return true; // No errors
}

#endif //#ifdef ASURA_ENABLE_QOS_SYSTEM

