////
// MacOS X GameTalkProtocol v1.0 12apr2005 Andrew Brownsword
//
////
#include "GameTalk/MacOSX/GameTalkProtocol.h"
#include "GameTalk/GameTalkBase.h"

#include <string>
#include <time.h>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <ppc_intrinsics.h>

#define TEST_AND_FAIL(cond, message) if (cond) { perror(message); exit(1); }

namespace EA
{
	namespace GameTalk
	{
		const int HOST_NAME_LENGTH	= 255;
		const int EXIT_MSG_SIZE		= 512;
		const int MAX_EXIT_CODES	= 3;
		const int MAX_RECV_LEN		= 8096;
		const int MAX_MSG_LEN		= 1024;
		const int MAX_MSG_QUEUE		= 1024;
		const int MSG_HEADER_LEN	= 4;

		enum hostType 
		{
			NAME, 
			ADDRESS
		};

		class HostInfo
		{
		public:
			HostInfo();
			// Retrieves the host entry based on the host name or address
			HostInfo( const std::string &hostName, hostType type );

			~HostInfo()
			{
			}

			// Retrieves the hosts IP address
			char *getHostIPAddress() 
			{
				struct in_addr *addr_ptr;
				// the first address in the list of host addresses
				addr_ptr = (struct in_addr *)*hostPtr->h_addr_list;
				// changed the address format to the Internet address in standard dot notation
				return inet_ntoa(*addr_ptr);
			}    

			// Retrieves the hosts name
			char *getHostName()
			{
				return hostPtr->h_name;
			}

		private:
			void detectErrorGethostbyname( int *, std::string & );
			void detectErrorGethostbyaddr( int *, std::string & );

			struct hostent *hostPtr;    // Entry within the host address database
		};

		class Message
		{
		public:
			Message (void) : mBufferSize(0) { std::memset(mBuffer, 0, sizeof(mBuffer)); };
			size_t		mBufferSize;
			char		mBuffer[];
			
		};
		
		class Socket
		{
		public:
			Socket (int portID);        // given a port number, create a socket
			~Socket() { close(socketId); }

			// socket options : ON/OFF
			void SetDebug(int);
			void SetReuseAddr(int);
			void SetKeepAlive(int);
			void SetLingerOnOff(bool);
			void SetLingerSeconds(int);
			void SetSocketBlocking(int);

			// size of the send and receive buffer
			void SetSendBufSize(int);
			void SetReceiveBufSize(int);

			// retrieve socket option settings
			int  GetDebug();
			int  GetReuseAddr();
			int  GetKeepAlive();
			int  GetSendBufSize();
			int  GetReceiveBufSize();
			int  GetSocketBlocking() { return blocking; }
			int  GetLingerSeconds();
			bool GetLingerOnOff();

			// returns the socket file descriptor
			int GetSocketId()		{ return socketId; }

			// returns the port number
			int GetPortNumber()		{ return portNumber; }

			// Sends a message to a connected host. The number of bytes sent is returned.
			int SendMessage(void *sendMsg, int size);

			// receive messages and stores the message in a buffer
			int ReceiveMessage(char *receiveMsg , int size);

			// Binds the socket to an address and port number - server call
			void BindSocket();

			// accepts a connecting client.  The address of the connected client 
			// is stored in the parameter - server call
			Socket *AcceptClient(std::string &);

			// Listens to connecting clients - server call
			void ListenToClient(int numPorts = 5);

			// connect to the server - client call
			virtual void ConnectToServer(std::string &, hostType);

			// wait for a message or exception on the socket -- returns >0 if data has arrived, >0 if error, 0 if nothing happened (may return before timed out)
			int Select (struct timeval& timeout);

		private:
			Socket() {}
			void setSocketId(int socketFd) { socketId = socketFd; }

			int portNumber;        // Socket port number    
			int socketId;          // Socket file descriptor

			int blocking;          // Blocking flag
			int bindFlag;          // Binding flag

			struct sockaddr_in clientAddr;    // Address of the client that sent data
		};

		class Manager
		{
		public:
			Manager(GameTalkProtocol& gtp);
			~Manager();

			Socket *GetConnection() { return mConnection; }

			void Initialize(const std::string &toolName, const std::string& gtServerName, int port);
			void Shutdown();
			int  Send(void* msg, unsigned bytes);
			void DispatchMessages();
			void ReceiveThread();

		private:
			int  CreateReceiveThread();
			void HandleMessageArrival();

			GameTalkProtocol&	mGTP;
			Socket*				mConnection;
			int					mPort;

			volatile bool		mDone;
			bool				mInited;
			pthread_t			mThread;
			pthread_mutex_t		mMutex;

			unsigned			mNumMessages;
			Message*			mMessages[MAX_MSG_QUEUE];
		};

		static void* GameTalkThread_Run (void* param)
		{
			Manager* mgr = static_cast<Manager*>(param);
			mgr->ReceiveThread();
			return(NULL);
		}
		
		Manager::Manager (GameTalkProtocol& gtp) :
			mGTP(gtp), mConnection(NULL), mPort(0), mDone(false), mInited(false), mNumMessages(0)
		{
		}

		Manager::~Manager()
		{
			if (mInited)
			{
				delete mConnection;
				pthread_join(mThread, NULL);

				if (mNumMessages > 0)
				{
					pthread_mutex_lock(&mMutex);
					for (unsigned i = 0; i < mNumMessages; ++i)
						delete mMessages[i];
					pthread_mutex_unlock(&mMutex);
				}
			
				pthread_mutex_destroy(&mMutex);
				mInited = false;
			}
		}

		void Manager::Initialize (const std::string &toolName, const std::string& gtServerName, int port)
		{
			mPort = port;

			// get server's information - local host always
			HostInfo		serverInfo(gtServerName, NAME);
			std::string		serverName		= serverInfo.getHostName();
			std::string		serverIPAddress	= serverInfo.getHostIPAddress();

			printf("Name: %s\nAddress:  %s\n", serverName.c_str(), serverIPAddress.c_str());

			// create the socket for client
			mConnection = new Socket(port);
		
			// connect to the server.
			printf("Connecting to server %s...\n", serverName.c_str());
			mConnection->ConnectToServer(serverIPAddress, ADDRESS);
		
			// create the thread to listen to receive network event
			CreateReceiveThread();
			mInited = true;
		}

		void Manager::Shutdown()
		{
			mDone = true;

			if( mConnection )
			{
				delete mConnection;
				mConnection = NULL;
			}
		}

		int Manager::Send(void* ptr, unsigned bytes)
		{
			if (mConnection)
				return(mConnection->SendMessage(ptr, bytes));
			else
				return(0);
		}
		
		void Manager::HandleMessageArrival()
		{
			Message* msg = new Message;
			int recvBytes = mConnection->ReceiveMessage(msg->mBuffer, MAX_RECV_LEN);
			msg->mBufferSize = recvBytes;
			if(recvBytes > 0 )
			{
				pthread_mutex_lock(&mMutex);
				mMessages[mNumMessages++] = msg;
				pthread_mutex_unlock(&mMutex);
			}
			else
				delete msg;
		}

		void Manager::DispatchMessages ()
		{
			if (mNumMessages > 0)
			{
				pthread_mutex_lock(&mMutex);
				for (unsigned i = 0; i < mNumMessages; ++i)
				{
					Message* msg = mMessages[i];
					mGTP.Deliver(msg);
					delete msg;
				}
				mNumMessages = 0;
				pthread_mutex_unlock(&mMutex);
			}
		}

		int Manager::CreateReceiveThread()
		{
			// create mutex for common data structures
			pthread_mutexattr_t mattr;
			pthread_mutexattr_init(&mattr);
			pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
			pthread_mutex_init(&mMutex, &mattr);
			pthread_mutexattr_destroy(&mattr);
			
			//
			// create a thread to handle the network events
			pthread_attr_t attr;
			int thread_policy;
			struct sched_param thread_param;
			
			int minprio = sched_get_priority_min(SCHED_FIFO);
			int maxprio = sched_get_priority_max(SCHED_FIFO);
			size_t stacksize = 0;
			
			pthread_attr_init(&attr);
			pthread_attr_getschedpolicy(&attr, &thread_policy);
			pthread_attr_getschedparam(&attr, &thread_param);
			pthread_attr_getstacksize(&attr, &stacksize);
			
			thread_param.sched_priority += 1;
			pthread_attr_setschedparam(&attr, &thread_param);
			pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
			pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
			
			int err = pthread_create(&mThread, &attr, GameTalkThread_Run, this);
			TEST_AND_FAIL(err != 0, "failed pthread_create");
			
			pthread_attr_destroy(&attr);
			return 1;
		}
		
		void Manager::ReceiveThread()
		{
			while( !mDone )
			{
				struct timeval timeout;
				timeout.tv_sec = 0;
				timeout.tv_usec = (1000*1000)*1/20;	// 1/20th of a second
				int status = mConnection->Select(timeout);

				if (status < 0)
					mDone = true;
				else if (status > 0)
					this->HandleMessageArrival();
			}
		}
		
		//
		//
		HostInfo::HostInfo()
		{
			char sName[HOST_NAME_LENGTH+1];
			memset(sName,0,sizeof(sName));
			gethostname(sName,HOST_NAME_LENGTH);

			hostPtr = gethostbyname(sName);
			TEST_AND_FAIL(hostPtr == NULL, "failed to gethostbyname");
		}

		HostInfo::HostInfo(const std::string &hostName, hostType type)
		{
			if (type == NAME)
			{	// Retrieve host by name
				hostPtr = gethostbyname(hostName.c_str());
				TEST_AND_FAIL(hostPtr == NULL, "failed to gethostbyname");
			}
			else if (type == ADDRESS)
			{
				// Retrieve host by address
				unsigned long netAddr = inet_addr(hostName.c_str());
				TEST_AND_FAIL(netAddr == -1, "failed inet_addr");

				hostPtr = gethostbyaddr((char *)&netAddr, sizeof(netAddr), AF_INET);
				TEST_AND_FAIL(hostPtr == NULL, "failed gethostbyaddr");
			}
			else
			{
				printf("unknown host type: host name/address has to be given");
				exit(1);
			}
		}
	
		//
		//
		Socket::Socket (int pNumber)
		{
			portNumber	= pNumber;
			blocking	= 1;

			socketId = socket(AF_INET,SOCK_STREAM,0);
			TEST_AND_FAIL(socketId == -1, "failed to create socket");

			clientAddr.sin_family		= AF_INET;
			clientAddr.sin_addr.s_addr	= htonl(INADDR_ANY);
			clientAddr.sin_port			= htons(portNumber);
		}
		    
		void Socket::SetDebug(int debugToggle)
		{
			int err = setsockopt(socketId,SOL_SOCKET,SO_DEBUG,(char *)&debugToggle,sizeof(debugToggle));
			TEST_AND_FAIL(err == -1, "failed setsockopt");
		}

		void Socket::SetReuseAddr(int reuseToggle)
		{
			int err = setsockopt(socketId,SOL_SOCKET,SO_REUSEADDR,(char *)&reuseToggle,sizeof(reuseToggle));
			TEST_AND_FAIL(err == -1, "failed setsockopt");
		} 

		void Socket::SetKeepAlive(int aliveToggle)
		{
			int err = setsockopt(socketId,SOL_SOCKET,SO_KEEPALIVE,(char *)&aliveToggle,sizeof(aliveToggle));
			TEST_AND_FAIL(err == -1, "failed setsockopt");
		} 

		void Socket::SetLingerSeconds(int seconds)
		{
			struct linger lingerOption;
			
			if( seconds > 0 )
			{
				lingerOption.l_linger = seconds;
				lingerOption.l_onoff = 1;
			}
			else 
			{
				lingerOption.l_onoff = 0;
			}
			 
			int err = setsockopt(socketId,SOL_SOCKET,SO_LINGER,(char *)&lingerOption,sizeof(struct linger));
			TEST_AND_FAIL(err == -1, "failed setsockopt");
		}

		void Socket::SetLingerOnOff(bool lingerOn)
		{
			struct linger lingerOption;

			if( lingerOn ) 
				lingerOption.l_onoff = 1;
			else 
				lingerOption.l_onoff = 0;

			int err = setsockopt(socketId,SOL_SOCKET,SO_LINGER,(char *)&lingerOption,sizeof(struct linger));
			TEST_AND_FAIL(err == -1, "failed setsockopt");
		}

		void Socket::SetSendBufSize(int sendBufSize)
		{
			int err = setsockopt(socketId,SOL_SOCKET,SO_SNDBUF,(char *)&sendBufSize,sizeof(sendBufSize));
			TEST_AND_FAIL(err == -1, "failed setsockopt");
		} 

		void Socket::SetReceiveBufSize(int receiveBufSize)
		{
			int err = setsockopt(socketId,SOL_SOCKET,SO_RCVBUF,(char *)&receiveBufSize,sizeof(receiveBufSize));
			TEST_AND_FAIL(err == -1, "failed setsockopt");
		}

		void Socket::SetSocketBlocking(int blockingToggle)
		{
			if(blockingToggle)
			{
				if(GetSocketBlocking()) 
					return;
				else 
					blocking = 1;
			}
			else
			{
				if(!GetSocketBlocking()) 
					return;
				else 
					blocking = 0;
			}

			int err = fcntl(socketId, F_SETFL, O_NONBLOCK);
			TEST_AND_FAIL(err == -1, "failed fcntl");
		}

		int Socket::GetDebug()
		{
			int Option;
			int OptionLen = sizeof(Option);

			int err = getsockopt(socketId,SOL_SOCKET,SO_DEBUG,(char *)&Option,(socklen_t*)&OptionLen);
			TEST_AND_FAIL(err == -1, "failed getsockopt");
			return Option;
		}

		int Socket::GetReuseAddr()
		{
			int Option;        
			int OptionLen = sizeof(Option);

			int err = getsockopt(socketId,SOL_SOCKET,SO_REUSEADDR,(char *)&Option,(socklen_t*)&OptionLen);
			TEST_AND_FAIL(err == -1, "failed getsockopt");
			return Option;
		}

		int Socket::GetKeepAlive()
		{
			int Option;        
			int OptionLen = sizeof(Option);

			int err = getsockopt(socketId,SOL_SOCKET,SO_KEEPALIVE,(char *)&Option,(socklen_t*)&OptionLen);
			TEST_AND_FAIL(err == -1, "failed getsockopt");
			return Option;    
		}

		int Socket::GetLingerSeconds()
		{
			struct linger	lingerOption;
			int				OptionLen = sizeof(struct linger);

			int err = getsockopt(socketId,SOL_SOCKET,SO_LINGER,(char *)&lingerOption,(socklen_t*)&OptionLen);
			TEST_AND_FAIL(err == -1, "failed getsockopt");
			return lingerOption.l_linger;
		}

		bool Socket::GetLingerOnOff()
		{
			struct linger	lingerOption;
			int				OptionLen = sizeof(struct linger);

			int err = getsockopt(socketId,SOL_SOCKET,SO_LINGER,(char *)&lingerOption,(socklen_t*)&OptionLen);
			TEST_AND_FAIL(err == -1, "failed getsockopt");

			if ( lingerOption.l_onoff == 1 ) 
				return true;
			else 
				return false;
		}

		int Socket::GetSendBufSize()
		{
			int sendBuf;
			int OptionLen = sizeof(sendBuf);

			int err = getsockopt(socketId, SOL_SOCKET, SO_SNDBUF, (char *)&sendBuf, (socklen_t*)&OptionLen);
			TEST_AND_FAIL(err == -1, "failed getsockopt");
			return sendBuf;
		}    

		int Socket::GetReceiveBufSize()
		{
			int rcvBuf;
			int OptionLen = sizeof(rcvBuf);

			int err = getsockopt(socketId, SOL_SOCKET,SO_RCVBUF, (char *)&rcvBuf, (socklen_t*)&OptionLen);
			TEST_AND_FAIL(err == -1, "failed getsockopt");
			return rcvBuf;
		}

		void Socket::BindSocket()
		{
			int err = bind(socketId, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
			TEST_AND_FAIL(err == -1, "failed bind");
		}

		void Socket::ConnectToServer(std::string &serverNameOrAddr, hostType hType)
		{ 
			HostInfo serverInfo(serverNameOrAddr,hType);
			
			// Store the IP address and socket port number	
			struct sockaddr_in serverAddress;
			serverAddress.sin_family		= AF_INET;
			serverAddress.sin_addr.s_addr	= inet_addr(serverInfo.getHostIPAddress());
			serverAddress.sin_port			= htons(portNumber);

			// Connect...
			int err = connect(socketId,(struct sockaddr *)&serverAddress, sizeof(serverAddress));
			TEST_AND_FAIL(err == -1, "failed connect");
		}

		Socket *Socket::AcceptClient( std::string &clientHost )
		{
			int newSocket;   // the new socket file descriptor returned by the accept system call

			// the length of the client's address
			int					clientAddressLen = sizeof(struct sockaddr_in);
			struct sockaddr_in	clientAddress;    // Address of the client that sent data

			// Accepts a new client connection and stores its socket file descriptor
			newSocket = accept(socketId, (struct sockaddr *)&clientAddress, (socklen_t*)&clientAddressLen);
			TEST_AND_FAIL(newSocket == -1, "failed accept");
		    
			// Get the host name given the address
			std::string	sAddress = inet_ntoa((struct in_addr)clientAddress.sin_addr);
			char		*hostName = HostInfo(sAddress, ADDRESS).getHostName();
			clientHost += std::string(hostName);
			
			// Create and return the new myTcpSocket object
			Socket *retSocket = new Socket();
			retSocket->setSocketId(newSocket);

			return retSocket;
		}

		void Socket::ListenToClient(int totalNumPorts)
		{
			int err = listen(socketId,totalNumPorts);
			TEST_AND_FAIL(err == -1, "failed listen");
		}

		int Socket::SendMessage(void *sendMsg, int size )
		{
			int numBytes;  // the number of bytes sent

			// Sends the message to the connected host
			numBytes = send(socketId, sendMsg, size, 0);
			TEST_AND_FAIL(numBytes == -1, "failed send");
			return numBytes;

		}

		int Socket::ReceiveMessage (char *receiveMsg, int size)
		{
			int received		= 0;					// The number of bytes received
			int msgSize			= size;					// The number of bytes wanted to receive
			int numBytes		= 0;					// The number of bytes currently received
			int totalRecvNum	= 0;
			bool headerFinished = false;				// 4 B holding the total length of the message
			char msgLength[MSG_HEADER_LEN+1];
			memset(msgLength, 0, sizeof(msgLength));

			while( received < msgSize )
			{
				numBytes = recv( socketId, receiveMsg+received, 1, 0 );
				if(numBytes == -1)
				{
					int errorcode = errno;
					if (errorcode == ENOTCONN)
						return(0);
					else
					{
						TEST_AND_FAIL(false, "failed recv");
					}
				}
				
				if( !headerFinished )
				{
					msgLength[received] = *(receiveMsg+received);
					received++;

					if( received == MSG_HEADER_LEN )
					{
						headerFinished	= true;
						received		= 0;
						msgSize = __lwbrx(msgLength, 0);
					}
				}
				else
					received++;
			}

			if( msgSize <= size )
				msgSize = 0; // force it to 0 if problems
			return msgSize;
		}

		int Socket::Select (struct timeval& timeout)
		{
			fd_set readfds, exceptfds;
			FD_ZERO(&readfds);
			FD_ZERO(&exceptfds);
			FD_SET(socketId, &readfds);
			FD_SET(socketId, &exceptfds);
			
			int num = select(socketId+1, &readfds, NULL, &exceptfds, /*&timeout*/ NULL);
			if (num == 0)
				return(0);
			else if (num == -1)
				return((errno == EBADF) ? -1 : 0);		// shutdown if descriptor is invalid, otherwise ignore the error
			else if (FD_ISSET(socketId, &exceptfds))
			{
				int optval, optlen = sizeof(int);
				int err = getsockopt(socketId, SOL_SOCKET, SO_ERROR, &optval, (socklen_t*)&optlen);
				if (err == -1)
					return(-1);
				else
					return(0);	// not sure how to interpret socket errors, so just try again -- will fail if socket is now invalid
			}
			else if (FD_ISSET(socketId, &readfds))
				return(1);
			else
				return(0);
		}

		//-------------------------------------------------------------------------------------------------------------
		//- GameTalkProtocol, MacOS X implementation
		//-------------------------------------------------------------------------------------------------------------
		GameTalkProtocol::GameTalkProtocol(const char* const ipAddress, const int port) : IGameTalkProtocol(ipAddress, port), mGTMgr(NULL)
		{
			mGTMgr = new Manager(*this);

			std::string localName = "game.mac";
			std::string gtServerName = ipAddress;
			mGTMgr->Initialize(localName, gtServerName, port);
		}

		GameTalkProtocol::~GameTalkProtocol()
		{
			mGTMgr->Shutdown();
			delete mGTMgr;
		}

		bool GameTalkProtocol::Send(void* buffer, int msgSize)
		{
			mGTMgr->Send(buffer, msgSize);
		}

		int GameTalkProtocol::Poll()
		{
			mGTMgr->DispatchMessages();
			// Need to get the number of bytes sent from the above
			return 0;
		}

		void GameTalkProtocol::Deliver(Message* msg)
		{
			if (mGameTalkReceiverFnc != NULL)
				mGameTalkReceiverFnc(msg->mBuffer, msg->mBufferSize);
		}
		
	} // namespace GameTalk
} // namespace EA

