///////////////////////////////////////////////////////////////////////////////
// GameTalkProtocol.cpp
//
// Copyright (c) 2006 Electronic Arts Inc. All rights reserved.
// Created by Cyril Saint Girons
///////////////////////////////////////////////////////////////////////////////

#include <GameTalk/UTFSockets/GameTalkProtocol.h>

namespace EA{
namespace GameTalk{


GameTalkProtocol::GameTalkProtocol(const char* const pNumericAddress, const int port)
    : IGameTalkProtocol(pNumericAddress, port),
    mListener(),
    mConnection(),
	mReceiveBufferIndex(0),
	mDisconnected(false)
{
	Connect();
}

GameTalkProtocol::GameTalkProtocol(const int port)
    : IGameTalkProtocol(port),
    mListener(),
    mConnection(),
	mReceiveBufferIndex(0),
	mDisconnected(false)
{
	// Create a non-blocking listening socket
	mListener.Create();
	mListener.SetNonBlockingOption(true);

	// Create and set the binding address
	Sockets::IPAddress ipAddress(EA::Sockets::IPAddress::kAddressAny,Sockets::IPAddress::ConvertToNetworkOrder(static_cast<uint16_t>(mPort)));
	if (!mListener.Bind(ipAddress))
		USER_GAMETALK_FAIL("Wasn't able to bind to local socket");

	// Start listening to the port
	mListener.StartListen(1);

	Connect();
}

GameTalkProtocol::~GameTalkProtocol()
{
	// Try to close both sockets regardless of their state
	mListener.Close();
	mConnection.Close();
}

bool GameTalkProtocol::IsConnected()
{
	// Don't reconnect in this function if we're in active mode - if we did - we
	// wouldn't resend our registered channels.
	// We could also use the following condition - since we will always try to Connect before we call IsConnected for "graceful reconnect"
	//if(!mConnection.IsCreated())
	if ((this->Mode() == EA::GameTalk::IGameTalkProtocol::kPassive) && (!mConnection.IsConnected()))
		return Connect();

	bool isConnected = mConnection.IsConnected();
	return isConnected;
}

bool GameTalkProtocol::Send(void* buffer, size_t msgSize)
{
	int signedMsgSize = static_cast<int>(msgSize);
	bool bSuccess = false;

	// FIFA_BEGIN | SENDBACK | cburns | 30-03-2015 | Backport fixes to UTFInternet implementation of GameTalk protocol from GameTalk 4.5.10
	if (IsConnected())
	{
		// Send what we can through the connection
		while (signedMsgSize != 0)
		{
			int nSent = mConnection.Write(buffer, signedMsgSize);
			if (nSent < 0) // No data was sent - but we still may be connected so check socket error
			{
				EA::Sockets::Error socketErr = mConnection.GetLastError();
				EA::Sockets::ErrorType errType = socketErr.GetType();
				if ((errType != EA::Sockets::kETNone) && (errType != EA::Sockets::kETWouldBlock) && (errType != EA::Sockets::kETInProgress))
				{
					EA::Sockets::ConnectStatus connectStatus = mConnection.GetConnectStatus();
					if ((connectStatus != EA::Sockets::kCSConnectPending))
					{
						Disconnect();
						break;
					}
				}
				nSent = 0;
			}

			buffer = nSent + static_cast<char*>(buffer);
			signedMsgSize -= nSent;
		}
		bSuccess = (signedMsgSize == 0);
	}
	// FIFA_END

	return bSuccess;
}

int GameTalkProtocol::Poll()
{
	// FIFA_BEGIN | SENDBACK | cburns | 30-03-2015 | Backport fixes to UTFInternet implementation of GameTalk protocol from GameTalk 4.5.10
	if (IsConnected())
	{
		if (GAMETALK_RECEIVE_BUFFER_SIZE - mReceiveBufferIndex <= 0) // We're likely still connected - there just isn't any room in our receiving buffer
		{
			// We retest the same condition to avoid the const expression compiler error/warning
			USER_GAMETALK_ASSERT(GAMETALK_RECEIVE_BUFFER_SIZE - mReceiveBufferIndex > 0, "There is no room left in the receiving buffer - try increasing GAMETALK_RECEIVE_BUFFER_SIZE.");
			return 0;
		}

		char * receiveBuffer = &(mReceiveBuffer[mReceiveBufferIndex]);
		// Try to recieve bytes
		int nReceived = mConnection.Read(receiveBuffer, GAMETALK_RECEIVE_BUFFER_SIZE - mReceiveBufferIndex);
		if (nReceived > 0)
		{
			mReceiveBufferIndex += nReceived;
			// digest the bytes with the standard GameTalk function
			mParser->DigestReceivedData(receiveBuffer, static_cast<uint32_t>(nReceived));
			mReceiveBufferIndex -= nReceived;
		}

		if (nReceived < 0) // Sometimes UTFSockets will return a -1 when we haven't actually lost our connection - so we need to check if an error other than WouldBlock has occurred
		{
			EA::Sockets::Error error = mConnection.GetLastError();
			if ((error.GetType() == EA::Sockets::kETWouldBlock) || (error.GetType() == EA::Sockets::kETInProgress))
				nReceived = 0;
			else
				Disconnect();
		}

		return nReceived;
	}

	return -1;
	// FIFA_END
}

bool EA::GameTalk::GameTalkProtocol::Connect (void)
{
	bool connected = false;
	if (mIpAddress) // In active mode - we will have an IP address to connect to
	{
		if (!mConnection.IsCreated())
		{
			// Create a non-blocking connection socket
			mConnection.Create();
			mConnection.SetNonBlockingOption(true);

			// Bind the connection socket to the default
			if (!mConnection.Bind())
				USER_GAMETALK_FAIL("Wasn't able to bind to local socket");

			// Create and set the peer address
			Sockets::IPAddress ipAddress(mIpAddress,Sockets::IPAddress::ConvertToNetworkOrder(static_cast<uint16_t>(mPort)));
			mConnection.SetPeerAddress(ipAddress);
		}
		// Try to connect
		if(!mConnection.Connect(connected))
		{
			// Make sure there were no real errors
			EA::Sockets::ErrorType errorType = mConnection.GetLastError().GetType();
			USER_GAMETALK_USE_VAR_TO_AVOID_WARNING(errorType);
			EA::Sockets::ConnectStatus connectStatus = mConnection.GetConnectStatus();
			USER_GAMETALK_USE_VAR_TO_AVOID_WARNING(connectStatus);
			// for both Wii and BSD sockets 'in progress' is returned instead of 'would block' for the Connect call
			// kETInProgress is returned for non-blocking connect calls but kETWouldBlock is returned for non-blocking read/write calls
			// If the GameTalkServer isn't running - we will not successfully connect - hence this assert would be annoying
			//USER_GAMETALK_ASSERT((errorType == Sockets::kETWouldBlock)||(errorType == Sockets::kETInProgress), "Wasn't able to connect to GameTalkServer");
			
			if (errorType == EA::Sockets::kETInvalidArg) // force the protocol to disconnect (since the socket we're trying to use is now invalid)
				mDisconnected = false;

#if defined(EA_PLATFORM_XENON)
			// For some reason on Xenon we can get into this state where we're definitely NOT connected (in fact the server we were connected to may be shut-down entirely) - but our Winsock error will be "we're already connected on this socket"
			if ((errorType == Sockets::kETIsConnected) && (connectStatus == Sockets::kCSNotConnected))
				mDisconnected = false;
#endif

			connected = mConnection.IsConnected();
		}
	}
	else
	{
		// Try to accept
		if(!mListener.Accept(&mConnection, connected))
		{
			bool compResult = mListener.GetLastError().GetType()==Sockets::kETWouldBlock;
			// prevent warning C4189: 'compResult' : local variable is initialized but not referenced
			USER_GAMETALK_USE_VAR_TO_AVOID_WARNING(compResult);	
			// Make sure there were no real errors
			USER_GAMETALK_ASSERT(compResult, "Tried to accept an incoming connection - but failed");
		}
		else // Make the connection non-blocking
			mConnection.SetNonBlockingOption(true);
	}

	if (connected)
		mDisconnected = false;

	return connected;
}

void EA::GameTalk::GameTalkProtocol::Disconnect()
{
	if (mDisconnected) // Multiply disconnecting (i.e. not using this flag) seems to result in multiple connections getting made and then getting destroyed immediately after connection - likely to do with the non-blocking nature of the sockets and destroying the socket before we're certain the socket connection failed
		return;

	mDisconnected = true;
	// In the case of a reconnection - we'll need to reset our index into our string buffer
	mReceiveBufferIndex = 0;
	mConnection.Close();
}

bool EA::GameTalk::GameTalkProtocol::SetSocketBlocking(bool blocking)
{
	if (blocking) // Make the connection blocking
		mConnection.SetNonBlockingOption(false);
	else // Make the connection non-blocking
		mConnection.SetNonBlockingOption(true);

	return true;
}

bool EA::GameTalk::GameTalkProtocol::InitNetwork()
{
	EA::Sockets::Manager manager;
	return manager.Init();
}

bool EA::GameTalk::GameTalkProtocol::ShutdownNetwork()
{
	// Someone should implement this
	return false;
}

} // namespace GameTalk
} // namespace EA






