#include "Gos.pch"
#include "eventid.h"

bool	AllowFail;	// TBD: remove
DWORD gEnableGosView=1;

extern HINSTANCE LibWinSock;
HANDLE GosViewCommandThreadHandle=0;
HANDLE GosViewLogThreadHandle=0;
HANDLE hLogStart=0;
SOCKET GosViewCommandServerSocket=0;
SOCKET GosViewLogServerSocket=0;
SOCKET GosViewCommandSocket=0;
SOCKET GosViewLogSocket=0;
bool KillGosViewThreads = false;

enum ELoggingStatus { LS_Off, LS_LogOnce, LS_Logging, LS_Sending } LogStatus;

void StartGosViewServerThread();
void StopGosViewServerThread();
DWORD WINAPI GosViewLogServerThread( LPVOID ThreadParam );
DWORD WINAPI GosViewCommandServerThread( LPVOID ThreadParam );
bool closeLogSocket;
int socketHeartBeatsMissed = 0;

// Winsock functions - for GosView usage only
extern int (WINAPI * _closesocket)( SOCKET s );
extern int (WINAPI * _recv)( SOCKET s, char *buf, int len, int flags );
extern int (WINAPI * _send)( SOCKET s, char *buf, int len, int flags );
extern SOCKET (WINAPI * _accept)( SOCKET s, struct sockaddr *addr, int *addrlen );
extern int (WINAPI * _listen)( SOCKET s, int backlog );
extern int (WINAPI * _bind)( SOCKET s, const struct sockaddr *name, int namelen );
extern u_long (WINAPI * _htonl)( u_long hostlong );
extern u_short (WINAPI * _htons)( u_short hostshort );
extern SOCKET (WINAPI * _socket)( int af, int type, int protocol );
extern int (WINAPI * _WSACleanup )(void);
extern int (WINAPI * _WSAStartup)( WORD wVersionRequested, LPWSADATA lpWSAData );
extern int (WINAPI * _WSAGetLastError)(void);
extern struct servent * (WINAPI * _getservbyname)( char *name, char *proto );

float ProcessorSpeed = 733.f;	// TBD: derive


void StartGosViewServerThreads()
{
	DWORD ID;
/*
#ifndef LAB_ONLY
    return;
#endif
    if( !LibWinSock || !gEnableGosView )
		return;	// without winsock, we can't run
*/

	// initialize winsock
	WORD wVersionRequired = MAKEWORD(1,1);
	WSADATA wsaData;

	if( _WSAStartup( wVersionRequired, &wsaData ) )
	{
		AllowFail = false;
		STOP(("Failed to do WSAStartup"));
	}

	hLogStart = CreateEvent( NULL, FALSE, FALSE, NULL );

	GosViewCommandThreadHandle=CreateThread(NULL, 0, GosViewCommandServerThread, 0, 0, &ID );
	GosViewLogThreadHandle=CreateThread(NULL, 0, GosViewLogServerThread, 0, 0, &ID );
}

void StopGosViewServerThreads()
{
	if( GosViewCommandThreadHandle )
	{
		KillGosViewThreads = true;
		if( GosViewLogServerSocket )
			_closesocket( GosViewLogServerSocket );
		if( GosViewCommandServerSocket )
			_closesocket( GosViewCommandServerSocket );
		if( GosViewLogSocket )
			_closesocket( GosViewLogSocket );
		if( GosViewCommandSocket )
			_closesocket( GosViewCommandSocket );
		if( GosViewCommandThreadHandle )
		{
			WaitForSingleObject( GosViewCommandThreadHandle, INFINITE );
			CloseHandle( GosViewCommandThreadHandle );
			GosViewCommandThreadHandle=0;
		}
		if( GosViewLogThreadHandle )
		{
			WaitForSingleObject( GosViewLogThreadHandle, INFINITE );
			CloseHandle( GosViewLogThreadHandle );
			GosViewLogThreadHandle=0;
		}
		if( hLogStart )
		{
			CloseHandle( hLogStart );
			hLogStart = 0;
		}
	}
	GosEventIdMgr::Cleanup();
	GosEventLog::Cleanup();
}

// Commands sent from the GUI app (debugger) to the Game
enum GosViewCommand
{
	GVCMD_STARTLOG = 100,
	GVCMD_STOPLOG = 101,
	GVCMD_GETLOG = 102,
	GVCMD_GETEVENTNAME = 103,
	GVCMD_STARTLOGONCE = 104,
	GVCMD_CLOSECONNECTIONS = 105,
	GVCMD_GETFREQ = 106,
	GVCMD_HEARTBEAT = 107,
};

enum GosViewResponse
{
	GVRESP_EVENTLOG = 200,
};


struct GosLogInfo
{
	DWORD	logLength;	// length of actual log data
	DWORD	magic;		// == GVRESP_EVENTLOG
	_int64	tLogStart;	// start time when we began collecting log (not necessarily in log)
	_int64	tLogEnd;		// stop time when we stopped collecting log (probably right after last entry)
};

struct CommandPacket
{
	unsigned long	extLength;	// length beyond the first 8 bytes
	GosViewCommand	command;
	union
	{
		unsigned long eventId;
		struct
		{
			char	dummy_space[100];
		};
	};
} commandPacket;

#define RESPONSE_HEADER_SIZE 4

bool receiveData( SOCKET s, char *pData, int length )
{
	int dataReceived;
	while( length>0 )
	{
		dataReceived = _recv( s, pData, length, 0 );
		if( !dataReceived )
			return false;
		if( dataReceived == SOCKET_ERROR )
		{
			int err = _WSAGetLastError();
			err = 0;

		}
		pData += dataReceived;
		length -= dataReceived;
	}
	return true;
}

bool receivePacket( SOCKET s, CommandPacket *pPacket )
{
	if( !receiveData( s, (char *)pPacket, 8 ) )
		return false;
	if( pPacket->extLength )
	{
		if( pPacket->extLength + 8 > sizeof(CommandPacket) )
		{
			STOP(("Error in packet received: length is too great"));
			return false;
		}
		if( !receiveData( s, ((char *)pPacket)+8, pPacket->extLength ) )
			return false;
	}
	return true;
}

bool sendData( SOCKET s, char *pData, int dataLength )
{
	int dataSent;

	while( dataLength > 0 )
	{
		dataSent = _send( s, pData, dataLength, 0 );
		if( dataSent == SOCKET_ERROR )
		{
			// int err = _WSAGetLastError();
			return false;
		}
		dataLength -= dataSent;
		pData += dataSent;
	}

	return true;
}

bool sendPacket( SOCKET s, char *pData, int dataLength )
{
	if( !sendData( s, (char *)&dataLength, 4 ) ||
		!sendData( s, pData, dataLength ) )
		return false;
	return true;
}


DWORD WINAPI GosViewLogServerThread( LPVOID ThreadParam )
{
	SOCKADDR_IN clientAddr;
	int clientAddrLen;
	int error;

	closeLogSocket = false;

	// create socket
	GosViewLogServerSocket = _socket( AF_INET, SOCK_STREAM, 0 );
	if( GosViewLogServerSocket == INVALID_SOCKET )
	{
		AllowFail = false;
		STOP(("Failed to create socket!"));
	}

	SOCKADDR_IN addr;

	addr.sin_family = AF_INET;
	//addr.sin_port = _htons(pservent->s_port);
	addr.sin_port = _htons(3001);
	addr.sin_addr.S_un.S_addr = _htonl(INADDR_ANY);

	// bind the socket to its address
	if( _bind( GosViewLogServerSocket, (LPSOCKADDR)&addr, sizeof(addr) ) == SOCKET_ERROR )
	{
		error = _WSAGetLastError();
		if( error == WSAEADDRINUSE )
		{
			// This happens when we run two GOS apps, - because the socket has already been assigned.
			// Just terminate this thread and let the other app keep it.
			return 0;
		}
		AllowFail = false;
		STOP(("Failed to bind socket !?"));

	}

	// open comms channel for GosView
	if( _listen( GosViewLogServerSocket, 3 ) == SOCKET_ERROR )
	{
		error = _WSAGetLastError();
		return 0;
		//AllowFail = false;
		//STOP(("listen( GosViewLogServerSocket,..) failed - should cope here!"));
	}


    while( !KillGosViewThreads )
	{
		bool socketError = false;

		clientAddrLen = sizeof(clientAddr);
		GosViewLogSocket = _accept( GosViewLogServerSocket, (LPSOCKADDR)&clientAddr, &clientAddrLen );
		if( GosViewLogSocket == INVALID_SOCKET )
		{
			error = _WSAGetLastError();
			return 0;
			//AllowFail = false;
			//STOP(("accept( GosViewServerSocket,...) failed"));
			continue;
		}
		// AllowFail = false;
		// STOP(("Succesfully connected to GosView!"));

		socketHeartBeatsMissed = 0;

		// now process requests from GosView
		while( !KillGosViewThreads && !socketError && !closeLogSocket )
		{
			GosLogInfo info;

			// This is a transmission-only thread
			LogStatus = LS_Off;
			
			while( LogStatus == LS_Off && !closeLogSocket )
			{
				if( ++socketHeartBeatsMissed > 6 )
				{
					closeLogSocket = true;
					// brutally close the other thread's socket to get its attention !
					_closesocket(GosViewCommandSocket);
					break;
				}
				WaitForSingleObject( hLogStart, 1000 );
			}
				

			if( closeLogSocket )
				break;


			LogStatus = LS_Logging;

			// for now, assume we are doing 1-second logs with 10 seconds between them

			info.magic = GVRESP_EVENTLOG;
			info.tLogStart = GetCPUCycles();
			GosEventLog::LogStart();
			Sleep( 50 );	// 1/2 a second
			GosEventLog::LogStop();
			info.tLogEnd = GetCPUCycles();
			info.logLength = GosEventLog::LogBytes(0) + GosEventLog::LogBytes(1);

			LogStatus = LS_Sending;
		
			if( !sendData( GosViewLogSocket, (char *)&info, sizeof(info) ) )
			{
				socketError = true;
				break;	// failed
			}

			if( GosEventLog::LogBytes(0) > 0 )
			{
				if( !sendData( GosViewLogSocket, GosEventLog::LogAddr(0), GosEventLog::LogBytes(0) ) )
				{
					socketError = true;
					break;
				}
			}

			if( GosEventLog::LogBytes(1) > 0 )
			{
				if( !sendData( GosViewLogSocket, GosEventLog::LogAddr(1), GosEventLog::LogBytes(1) ) )
				{
					socketError = true;
					break;
				}
			}

			LogStatus = LS_Off;

			socketHeartBeatsMissed = 0;	// since heartbeats stop while sending the log
		}

		// AllowFail = false;
		// STOP(("CLOSING Log Socket!"));

		closeLogSocket = false;

		_closesocket( GosViewLogSocket );
	}
	return 0;
}


DWORD WINAPI GosViewCommandServerThread( LPVOID ThreadParam )
{
	SOCKADDR_IN clientAddr;
	int clientAddrLen;
	int error;
	char *pEventName;

	// create socket
	GosViewCommandServerSocket = _socket( AF_INET, SOCK_STREAM, 0 );
	if( GosViewCommandServerSocket == INVALID_SOCKET )
	{
		error = _WSAGetLastError();
		AllowFail = false;
		STOP(("Failed to create socket!"));
	}

	SOCKADDR_IN addr;

	addr.sin_family = AF_INET;
	//addr.sin_port = _htons(pservent->s_port);
	addr.sin_port = _htons(3002);
	addr.sin_addr.S_un.S_addr = _htonl(INADDR_ANY);

	// bind the socket to its address
	if( _bind( GosViewCommandServerSocket, (LPSOCKADDR)&addr, sizeof(addr) ) == SOCKET_ERROR )
	{
		error = _WSAGetLastError();
		if( error == WSAEADDRINUSE )
		{
			// This happens when we run two GOS apps, - because the socket has already been assigned.
			// Just terminate this thread and let the other app keep it.
			return 0;
		}
		AllowFail = false;
		STOP(("Failed to bind socket !?"));

	}

	if( _listen( GosViewCommandServerSocket, 3 ) == SOCKET_ERROR )
	{
		error = _WSAGetLastError();
		AllowFail = false;
		STOP(("listen( GosViewCommandServerSocket,..) failed - should cope here!"));
	}

    while( !KillGosViewThreads )
	{
		bool socketError = false;
		// open comms channel for GosView
		clientAddrLen = sizeof(clientAddr);
		GosViewCommandSocket = _accept( GosViewCommandServerSocket, (LPSOCKADDR)&clientAddr, &clientAddrLen );
		if( GosViewCommandSocket == INVALID_SOCKET )
		{
			error = _WSAGetLastError();
			//AllowFail = false;
			//STOP(("accept( GosViewCommandServerSocket,...) failed"));
			continue;
		}
		// AllowFail = false;
		// STOP(("Succesfully connected to GosView!"));

		// now process requests from GosView

		while( !KillGosViewThreads && !socketError )
		{
			if( !receivePacket( GosViewCommandSocket, &commandPacket ) )
			{
				socketError = true;
				break;
			}

			switch( commandPacket.command )
			{
				/*
			case GVCMD_STARTLOG:
				GosEventLog::LogStart();
				// AllowFail = false;
				// STOP(("LOG STARTED!"));
				break;
			case GVCMD_STOPLOG:
				GosEventLog::LogStop();
				// AllowFail = false;
				// STOP(("LOG STOPPED!"));
				break;
				
			case GVCMD_GETLOG:
				{
					// send a series of packets <= 100 bytes followed by a null packet as an end marker
					int length;
					char *pData;
					int packetLength;

					length = GosEventLog::LogBytes(0);
					pData = GosEventLog::LogAddr(0);

					while ( length > 0 )
					{
						packetLength = (length>100)?100:length;
						sendPacket( GosViewSocket, pData, packetLength );
						length -= packetLength;
						pData += packetLength;
					}

					length = GosEventLog::LogBytes(1);
					pData = GosEventLog::LogAddr(1);

					while ( length > 0 )
					{
						packetLength = (length>100)?100:length;
						sendPacket( GosViewSocket, pData, packetLength );
						length -= packetLength;
						pData += packetLength;
					}

					// send an end marker

					sendPacket( GosViewSocket, pData, 0 );

				}
				break;
				*/
			case GVCMD_GETEVENTNAME:
				{
					socketHeartBeatsMissed = 0;
					pEventName = GosEventIdMgr::EventName( commandPacket.eventId );
					if( !sendPacket( GosViewCommandSocket, pEventName, strlen(pEventName)+1 ) )
						socketError = true;
				}
				break;
			case GVCMD_STARTLOGONCE:
				socketHeartBeatsMissed = 0;
				if( LogStatus != LS_Off )
					break;
				LogStatus = LS_LogOnce;
				SetEvent(hLogStart);
				break;
			case GVCMD_CLOSECONNECTIONS:
				closeLogSocket = true;
				SetEvent(hLogStart);
				socketError = true;
				break;
				
			case GVCMD_HEARTBEAT:
				socketHeartBeatsMissed = 0;
				break;

			case GVCMD_GETFREQ:
				{
					_int64 qpc;
					qpc = (_int64)((double)ProcessorSpeed * 1000000.0);
					if( !sendPacket( GosViewCommandSocket, (char *)&qpc, 8 ) )
						socketError = true;
				}
				break;

			}
		}

		// AllowFail = false;
		// STOP(("CLOSING Command Socket!"));

		_closesocket(GosViewCommandSocket);
	}
	return 0;
}
