/* NetworkIO.cpp-- simple networking class written for CS 559,
Spring 2011, Project 1, by Leslie Watkins
Adapted largely from MSDN's help file, "Creating a Basic Winsock
Application", which can be found here:
http://msdn.microsoft.com/en-us/library/ms737629(VS.85).aspx */

#include "NetworkIO.h"

////////////////////////////////////////////////////////////////////////////
NetworkIO::NetworkIO(char * targetIP, int portNumber)
{	
	timeoutCt = 0;

	char portStr[Len]; //convert portNumber from int to string
	sprintf_s(portStr, "%d", portNumber);

	//step 1: initialize winsock
	WSADATA wsaData;
	int iResult;
	int err;
	iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != 0) throw iResult;
	
	struct addrinfo *result = NULL, hints;
	ZeroMemory(&hints, sizeof (hints));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;

	if (targetIP == NULL) //server-- establish TCP socket and wait for connection
	{
		hints.ai_family = AF_INET;
		hints.ai_flags = AI_PASSIVE;

		//step 2: resolve the local address and port to be used by the server
		iResult = getaddrinfo(NULL, //IP address, NULL for server
							portStr, //port number
							&hints, //structure containing specifications of supported sockets
							&result //return value, structure containing host information
							);
		if (iResult != 0) {
			WSACleanup();
			throw iResult;
		}

		//step 3: create a SOCKET for the server to listen for client connections
		SOCKET ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
		if (ListenSocket == INVALID_SOCKET) {
			err = WSAGetLastError();
			freeaddrinfo(result);
			WSACleanup();
			throw err;
		}

		//step 4: setup the TCP listening socket
		iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
		if (iResult == SOCKET_ERROR) {
			err = WSAGetLastError();
			freeaddrinfo(result);
			closesocket(ListenSocket);
			WSACleanup();
			throw err;
		}
		freeaddrinfo(result);
		if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
			err = WSAGetLastError();
			closesocket(ListenSocket);
			WSACleanup();
			throw err;
		}

		//step 5: accept a client socket
		ClientSocket = accept(ListenSocket, NULL, NULL);
		//NOTE: this is a blocking socket, meaning the program will hang
		//here indefinitely until a connection is made
		if (ClientSocket == INVALID_SOCKET) {
			err = WSAGetLastError();
			closesocket(ListenSocket);
			WSACleanup();
			throw err;
		}

	}
	else //client-- attempt to reach server and begin game
	{
		hints.ai_family = AF_UNSPEC;

		//step 2: resolve the server address and port
		iResult = getaddrinfo(targetIP, portStr, &hints, &result);
		if (iResult != 0) {
			WSACleanup();
			throw iResult;
		}

		//attempt to connect to the first address returned by
		//the call to getaddrinfo

		//step 3: create a SOCKET for connecting to server
		ClientSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);

		if (ClientSocket == INVALID_SOCKET) {
			err = WSAGetLastError();
			freeaddrinfo(result);
			WSACleanup();
			throw err;
		}

		//step 4: connect to server
		iResult = connect( ClientSocket, result->ai_addr, (int)result->ai_addrlen);
		if (iResult == SOCKET_ERROR) {
			closesocket(ClientSocket);
			ClientSocket = INVALID_SOCKET;
		}
		freeaddrinfo(result);
		if (ClientSocket == INVALID_SOCKET) {
			WSACleanup();
			throw iResult;
		}
	}

	//step 5/6: set some socket options
	//Socket should eventually time out on a recv call
	DWORD timeo = 8; //wait time before timeout error in millisecs
	if (setsockopt(ClientSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeo, sizeof(DWORD)) == SOCKET_ERROR)
		throw WSAGetLastError();
	//Send buffers should be sent individually, not bundled together
	bool nodelay = true;
	if (setsockopt(ClientSocket, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(bool)) == SOCKET_ERROR)
		throw WSAGetLastError();
}

NetworkIO::~NetworkIO(void)
{
	shutdown(ClientSocket, SD_SEND); //stop sending information
	shutdown(ClientSocket, SD_RECEIVE); //stop receiving information
	closesocket(ClientSocket); //close socket
	WSACleanup(); //cleanup
}
////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////
//--- Send Methods
void NetworkIO::SendUpdate(int level)
//send level of gameplay
{
	if (level < 0 || level > 9) throw -1; //level must be a single digit int

	char sendBuf[2];
	sprintf_s(sendBuf, 2, "%i", level);

	int iResult = send(ClientSocket, sendBuf, strlen(sendBuf), 0);
	if (iResult ==	SOCKET_ERROR) throw WSAGetLastError();
}


void NetworkIO::SendUpdate(long time, int x, int y, Heading heading, State state)
//send updates during gameplay
{
	char sendBuf[Len];
	sendBuf[0] = '\0';
	
	addVar(sendBuf, time);
	addVar(addVar(sendBuf, x), y);
	addVar(addVar(sendBuf, heading), state);

	int iResult = send(ClientSocket, sendBuf, strlen(sendBuf) + 1, 0);
	if (iResult == SOCKET_ERROR) throw WSAGetLastError();
}
////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////
//--- Recv Methods
void NetworkIO::GetUpdate(int & level)
//receive level of gameplay
{
	char recvBuf[2];
	int iResult = recv(ClientSocket, recvBuf, 2, 0);

	if (iResult == SOCKET_ERROR) 
			throw WSAGetLastError(); //throw an exception

	else level = atoi(recvBuf);
}


void NetworkIO::GetUpdate(long & time, int & x, int & y, Heading & heading, State & state)
//receive updates during gameplay
{
	char recvBuf[Len];
	int iResult = recv(ClientSocket, recvBuf, Len, 0);
	if (iResult == SOCKET_ERROR) {
		if (WSAGetLastError() != WSAETIMEDOUT) //if any error other than timeout
			throw WSAGetLastError(); //immediately throw an exception
		timeoutCt++;
		if (timeoutCt == 4) //after 4 consecutive timeout errors
			throw WSAGetLastError(); //throw an exception
	}
	else if (iResult == 0)
		throw -1; //socket closed
	else {
		timeoutCt = 0;

		char * next = remVar(recvBuf, time);
		next = remVar(remVar(next, x), y);
		remVar(remVar(next, heading), state);
	}
}
////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////
//--- Private Methods
template <typename T> char * NetworkIO::addVar(char buffer[], T var) {
	int decSize;

	if (var < 0)
		decSize = (int)log10((float)-var) + 3;
		//reserve enough space for all digits, neg sign, null char
	else if (var == 0)
		decSize = 2;
		//reserve enough space for 0, null char
	else
		decSize = (int)log10((float)var) + 2;
		//reserve enough space for all digits, null char

	char * var_s = new char[decSize];
	sprintf_s(var_s, decSize, "%d", var); //print value to string
	strcat_s(buffer, strlen(buffer)+decSize, var_s); //add string to buffer
	strcat_s(buffer, strlen(buffer)+2, " "); //spaces separate values
	delete [] var_s;
	return buffer;
}

template <typename T> char * NetworkIO::remVar(char *buffer, T & var) {
	
	char * bufEnd = strpbrk(buffer, " "); //end of value specified by space
	char * var_s = new char[bufEnd-buffer+1];
	strncpy_s(var_s, bufEnd-buffer+1, buffer, bufEnd-buffer);
	var = (T)atoi(var_s);
	delete [] var_s;
	return bufEnd + 1; //return pointer to location of the next value in buffer
}
////////////////////////////////////////////////////////////////////////////