
#include <stdio.h>
#include <Winsock.h>

int socketOpen(SOCKET *s,unsigned short port)
{
	SOCKET sock;
	struct sockaddr addr;
	int rc;
	unsigned long i = 1;

	sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

	((struct sockaddr_in *)&addr)->sin_family = AF_INET;
	((struct sockaddr_in *)&addr)->sin_addr.s_addr = INADDR_ANY;
	((struct sockaddr_in *)&addr)->sin_port = htons(port);

	// set non-blocking
	rc = ioctlsocket(sock, FIONBIO, &i);

	if (bind(sock, &addr, (int)sizeof(struct sockaddr)) == SOCKET_ERROR)
	{
		return 1;
	}

	*s = sock;

	return 0;
}

int socketRead(SOCKET s,char *buffer,int nbytes)
{
	int len;
	int count;
	struct sockaddr from;
	
	len = sizeof(struct sockaddr_in);

	count = recvfrom(s, buffer, nbytes, 0, &from, &len);

	return count;
}

int socketWrite(SOCKET s,char *buffer,int nbytes,struct sockaddr *address)
{
	int count;

	count = sendto(s, buffer, nbytes, 0, address, (int)sizeof(struct sockaddr_in));

	return count;
}

int stringtoaddr(char *string,struct sockaddr *address)
{
	unsigned long a1, a2, a3, a4;
	unsigned long ipaddress;
	unsigned long port = 0;
	int ret;

	ret = sscanf(string, "%lu.%lu.%lu.%lu:%lu", &a1, &a2, &a3, &a4, &port);

	if(a1 > 255 || a2 > 255 || a3 > 255 || a4 > 255 || port > 65535 || ret < 4)
	{
		/* bad address */
		((struct sockaddr_in *)address)->sin_family = AF_INET;
		((struct sockaddr_in *)address)->sin_addr.s_addr = INADDR_NONE;
		((struct sockaddr_in *)address)->sin_port = 0;

		return 1;
	}

	ipaddress = (a1 << 24) | (a2 << 16) | (a3 << 8) | a4;
	((struct sockaddr_in *)address)->sin_family = AF_INET;
	((struct sockaddr_in *)address)->sin_addr.s_addr = htonl(ipaddress);
	((struct sockaddr_in *)address)->sin_port = htons((unsigned short)port);

	return 0;
}

typedef struct
{
	char *m_pcData;
	int	m_iMaxSize;
	int	m_iCurSize;
	int m_iReadCount;
} sizebuf_t;

void MSG_WriteCreate(sizebuf_t *buf, char *_acBuffer, int _iLength)
{
	buf->m_pcData = _acBuffer;
	buf->m_iCurSize = 0;
	buf->m_iMaxSize = _iLength;
	buf->m_iReadCount = 0;
}

void *SZ_GetSpace (sizebuf_t *buf, int length)
{
	void *data;

	if (buf->m_iCurSize + length > buf->m_iMaxSize)
	{
		return NULL;
	}

	data = buf->m_pcData + buf->m_iCurSize;
	buf->m_iCurSize += length;

	return data;
}

int MSG_Write(sizebuf_t *sb, void *data, int length)
{
	char *buf;

	if ( (buf = SZ_GetSpace(sb,length)) == NULL)
		return 1;

	memcpy (buf,data,length);

	return 0;
}

int MSG_WriteStringLine (sizebuf_t *sb, char *s)
{
	char *buf;

	if (!s)
		return MSG_Write(sb, "\n", 1);
	else
	{
		if ( (buf = SZ_GetSpace(sb,strlen(s))) == NULL)
			return 1;

		memcpy(buf,s,strlen(s));

		MSG_Write(sb, "\n", 1);
		return 0;
	}
}

void MSG_ReadCreate(sizebuf_t *buf, char *_acBuffer, int _iLength)
{
	buf->m_pcData = _acBuffer;
	buf->m_iCurSize = _iLength;
	buf->m_iMaxSize = _iLength;
	buf->m_iReadCount = 0;
}

// returns -1 if no more characters are available
int MSG_ReadChar(sizebuf_t *msg_read)
{
	int	c;

	if (msg_read->m_iReadCount+1 > msg_read->m_iCurSize)
		c = -1;
	else
		c = (signed char)msg_read->m_pcData[msg_read->m_iReadCount];
	msg_read->m_iReadCount++;

	return c;
}

void MSG_ReadStringLine(sizebuf_t *msg_read, char *_szString)
{
	int		l,c;

	l = 0;
	do
	{
		c = MSG_ReadChar (msg_read);
		if (c == -1 || c == '\n' || c == 0)
			break;
		_szString[l] = c;
		l++;
	} while (1);

	_szString[l] = 0;
}

int g_iQuit;
HANDLE g_hThread;

int g_iDataBufferSize;
int g_iDataBufferMaxSize;
char *g_pacDataBuffer;

DWORD WINAPI InputProc()
{
	char szInput[100];
	char buffer[2000];
	sizebuf_t sMsg;

	do
	{
		fgets(szInput, 99, stdin);

		MSG_WriteCreate(&sMsg,buffer,2000);
		if (szInput[0] == '>')
		{
			MSG_WriteStringLine(&sMsg,&szInput[1]);
			printf("CMD: %s",&szInput[1]);
		}
		else
		{
			MSG_WriteStringLine(&sMsg,"MSG");
			MSG_WriteStringLine(&sMsg,szInput);
			printf("MSG: %s",szInput);
		}
		// TODO: handle a "quit" message

		// add to the buffer
		while ((g_iDataBufferSize + 4 + sMsg.m_iCurSize) > g_iDataBufferMaxSize)
		{
			g_pacDataBuffer = realloc(g_pacDataBuffer,g_iDataBufferMaxSize + 1000);
			g_iDataBufferMaxSize += 1000;
		}

		*(int*)&g_pacDataBuffer[g_iDataBufferSize] = sMsg.m_iCurSize;
		g_iDataBufferSize += 4;
		memcpy(&g_pacDataBuffer[g_iDataBufferSize],sMsg.m_pcData,sMsg.m_iCurSize);
		g_iDataBufferSize += sMsg.m_iCurSize;
	} while (!g_iQuit);

	return 0;
}

int main(int argc,char *argv[])
{
	WSADATA libmibWSAdata;
	SOCKET s;
	struct sockaddr address;
	char szPassword[100];
	int iTimer;
	int iState;				/* 0: disconnected; 1: connecting; 2: connected */
	int iConnectionTime;
	DWORD dwPrev,dwCur;
	int iTime;
	char buffer[2000];
	int count;
	sizebuf_t sMsg;
	char szString[100];

	int iKeepAliveTime;
	char szID[11];
	int iID;
	int iMessageID;
	int iMessageSize;
	int iMessageTime;
	char out_buffer[2000];
	sizebuf_t sOutMsg;

	/* first try Winsock 2.0 */
	if(WSAStartup(MAKEWORD(2, 0),&libmibWSAdata) != 0)
	{
		/* Winsock 2.0 failed, so now try 1.1 */
		if(WSAStartup(MAKEWORD(1, 1),&libmibWSAdata) != 0)
		{
			return 1;
		}
	}

	if (socketOpen(&s,20000) != 0)
	{
		return 1;
	}

	stringtoaddr("127.0.0.1:54430",&address);
	strcpy(szPassword,"test");

	iTimer = 0;

	iState = 1;
	iConnectionTime = iTimer - 2000;

	timeBeginPeriod(1);

	dwPrev = timeGetTime();

	// connection
	while (iState == 1)
	{
		do
		{
			dwCur = timeGetTime();
			iTime = dwCur - dwPrev;
		} while (iTime < 5);
		dwPrev = dwCur;

		iTimer += iTime;

		count = socketRead(s,buffer,2000);
		if (count > 0)
		{
			MSG_ReadCreate(&sMsg,buffer,count);
			MSG_ReadStringLine(&sMsg,szString);

			if (!stricmp(szString,"OK"))
			{
				if (iState == 1)
				{
					iState = 2;
				}

				printf("OK\n");
			}
			else if (!stricmp(szString,"WRONGPASSWORD"))
			{
				iState = 0;

				printf("WRONGPASSWORD\n");
			}
			else if (!stricmp(szString,"FULL"))
			{
				iState = 0;

				printf("FULL\n");
			}
		}

		if (iState == 1)
		{
			if ((iTimer - iConnectionTime) > 2000)
			{
				MSG_WriteCreate(&sMsg,buffer,2000);
				MSG_WriteStringLine(&sMsg,"CONNECT");
				MSG_WriteStringLine(&sMsg,szPassword);

				socketWrite(s,sMsg.m_pcData,sMsg.m_iCurSize,&address);

				printf("CONNECT\n");

				iConnectionTime = iTimer;
			}
		}

		Sleep(5);
	}

	if (iState == 2)
	{
		// init
		iKeepAliveTime = iTimer;
		iMessageID = 1;
		iMessageSize = 0;

		// create thread for non-blocking input
		g_hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)InputProc,NULL,0,NULL);
	}

	// main loop
	do
	{
		if (iState == 2)
		{
			do
			{
				dwCur = timeGetTime();
				iTime = dwCur - dwPrev;
			} while (iTime < 5);
			dwPrev = dwCur;

			iTimer += iTime;

			// check for ACKs
			count = socketRead(s,buffer,2000);
			if (count > 0)
			{
				MSG_ReadCreate(&sMsg,buffer,count);
				MSG_ReadStringLine(&sMsg,szString);

				if (!stricmp(szString,"ACK"))
				{
					MSG_ReadStringLine(&sMsg,szID);
					iID = atol(szID);
					if (iID == iMessageID)
					{
						iMessageSize = 0;
						iMessageID++;
					}
				}
			}

			if (iMessageSize == 0)		// no message in line, try to build a new one
			{
				if (g_iDataBufferSize)	// there is data in the buffer
				{
					// build a new packet
					MSG_WriteCreate(&sOutMsg,out_buffer,2000);
					MSG_WriteStringLine(&sOutMsg,"CMD");
					sprintf(szID,"%d",iMessageID);
					MSG_WriteStringLine(&sOutMsg,szID);

					while (g_iDataBufferSize)
					{
						int iDataSize;

						iDataSize = *(int*)g_pacDataBuffer;
						if ((sOutMsg.m_iCurSize + iDataSize) > (2000-1))
						{
							break;
						}

						MSG_Write(&sOutMsg,&g_pacDataBuffer[4],iDataSize);

						// remove from buffer
						g_iDataBufferSize -= (4 + iDataSize);
						memmove(g_pacDataBuffer,g_pacDataBuffer + 4 + iDataSize,g_iDataBufferSize);
					}

					MSG_WriteStringLine(&sOutMsg,"");

					iMessageSize = sOutMsg.m_iCurSize;
					iMessageTime = iTimer - 2000;
				}
			}

			if (iMessageSize > 0)
			{
				// check for resend
				if ((iTimer - iMessageTime) >= 2000)
				{
					socketWrite(s,sOutMsg.m_pcData,sOutMsg.m_iCurSize,&address);

					iMessageTime = iTimer;
				}
			}

			if ((iTimer - iKeepAliveTime) > 15000)
			{
				MSG_WriteCreate(&sMsg,buffer,2000);
				MSG_WriteStringLine(&sMsg,"KEEPALIVE");

				socketWrite(s,sMsg.m_pcData,sMsg.m_iCurSize,&address);

				printf("KEEPALIVE\n");

				iKeepAliveTime = iTimer;
			}

			// TODO: handle the "ALIVE" messages from the server, to check if the connection has been lost
		}
	} while (!g_iQuit);

	if (g_hThread)
	{
		CloseHandle(g_hThread);
		g_hThread = NULL;
	}

	timeEndPeriod(1);

	closesocket(s);

	WSACleanup();

	return 0;
}