#ifndef GAMETALKCPP_HPP
#define GAMETALKCPP_HPP

#include <string>
#include <fstream>
#include <iostream>
#include <time.h>
#include <vector>

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

namespace EA
{
	//
	//
	namespace GameTalk
	{
		class Manager;

		enum hostType 
		{
			NAME, 
			ADDRESS
		};

		const int HOST_NAME_LENGTH = 255;

		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
		};

		//
		//
		const std::ios_base::openmode LOG_WRITE			= std::ios::out;
		const std::ios_base::openmode LOG_APPEND		= std::ios::app;

		const int EXIT_MSG_SIZE		= 512;
		const int MAX_EXIT_CODES	= 3;

		class Log : public std::ofstream
		{
		public:
			enum logLevels 
			{
				LEVEL_0,       // buffer all log messages
				LEVEL_1,       // buffer Level one, two and three log messages
				LEVEL_2,       // buffer Level two and three log messages
				LEVEL_3,       // buffer Level three log messages
				QUIET_MODE     // do not print out any messages
			};
			 
			Log();
			Log(const std::string &);
			Log(const std::string &, enum logLevels);
			virtual ~Log();

		private:
			void	initVars();
			void	init(const std::string &);
			void	init(const std::string &, std::ios_base::openmode );
			void	init();

			char	*getExecTime();
			void	getExecTime(int *, int *);
			void	openLog(const std::string &, std::ios_base::openmode);
			void	printHeader(int);

			std::string	logName;
			enum	logLevels logLevel;
			time_t	startTime;
		};

		const enum Log::logLevels L0				= Log::LEVEL_0;
		const enum Log::logLevels L1				= Log::LEVEL_1;
		const enum Log::logLevels L2				= Log::LEVEL_2;
		const enum Log::logLevels L3				= Log::LEVEL_3;
		const enum Log::logLevels LQUIET			= Log::QUIET_MODE;
		const enum Log::logLevels DEFAULT_LOG_LEVEL = Log::LEVEL_1;

		extern Log theGameTalkLog;

		//
		//
		const int MAX_RECV_LEN	= 8096;
		const int MAX_MSG_LEN	= 1024;
		const int MAX_MSG_QUEUE = 1024;
		const int PORTNUM		= 2000;

		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; }

			// show the socket 
			friend std::ostream& operator<<(std::ostream&, Socket&);

			// Sends a message to a connected host. The number of bytes sent is returned.
			int SendMessage(std::string &);

			// Sends a message to a connected host. The number of bytes sent is returned.
			int SendMessage(char *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 KeyContent
		{
		public:
			std::string Key;
			std::string Content;
		};

		class Message
		{
		public:
			Message( const std::string &messageId );
			Message();
			~Message();

			void	AddKeyContent( const std::string &key, const std::string &content );
			void	ClearKeyContent();

			char	*GetBuffer( char **, int * );
			int		NumberOfKeys();
			// assemble the message form buffer to KeyContent
			int		AssembleFromBuffer();	
			int		ComputeBufferSize();

			std::vector<KeyContent *>	mKeysContents;
			std::string					MessageId;
			int							mBufferSize;
			char						mBuffer[MAX_RECV_LEN];
		};


		typedef void GameTalkMessageHandlerFnc( Message *message );

		class Manager
		{
		public:
			Manager();
			~Manager();

			Socket *GetConnection() { return mConnection; }

			void Initialize(const std::string &toolName, const std::string& gtServerName);
			void Shutdown();
			int  SendInitializePacket();
			int  Send( const std::string &target, Message &message );
			void Receive();
			int  RegisterReceiveMessageHandler( GameTalkMessageHandlerFnc *handlerFnc );
			int  UnRegisterReceiveMessageHandler( GameTalkMessageHandlerFnc *handlerFnc );

		private:
			friend void* GameTalkThread_Run(void*);
			
			Socket			*mConnection;
			int				mPort;
			std::string		mName;
			
			volatile bool	mDone;
			bool			mInited;
			pthread_t		mThread;
			pthread_mutex_t	mMutex;
			
			unsigned		mNumMessages;
			Message*		mMessages[MAX_MSG_QUEUE];
			
			int  CreateReceiveThread();
			void ReceiveThread();
			void HandleMessageArrival();

			GameTalkMessageHandlerFnc *mMsgHandler;
		};
		
		extern Manager theGameTalkManager;
	}; // namespace GameTalk
}; // namespace EA

#endif GAMETALKCPP_HPP

