/*
        Copyright (C) 1998, 1999 by Serguei 'Snaar' Narojnyi
        Copyright (C) 1998, 1999 by Jorrit Tyberghein
        Written by Serguei 'Snaar' Narojnyi

        This library is free software; you can redistribute it and/or
        modify it under the terms of the GNU Library General Public
        License as published by the Free Software Foundation; either
        version 2 of the License, or (at your option) any later version.

        This library is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
        Library General Public License for more details.

        You should have received a copy of the GNU Library General Public
        License along with this library; if not, write to the Free
        Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <stdarg.h>

#include "sysdef.h"
#include "cscom/com.h"
#include "csnetman/simple/netsman.h"
#include "csnetman/simple/mansdefs.h"
#include "isystem.h"

IMPLEMENT_UNKNOWN_NODELETE (csNetworkManagerSimple)

BEGIN_INTERFACE_TABLE (csNetworkManagerSimple)
  IMPLEMENTS_INTERFACE (INetworkManager)
END_INTERFACE_TABLE ()

csNetworkManagerSimple::csNetworkManagerSimple(ISystem* piSystem)
{
	m_piSystem = piSystem;
	m_piSystem->GetSubSystemPtr((void **)(&piNetDrv), NetDrv_ID);

	dwLastError = 0;

	dwListeningSocketID = 0;
	dwClientSocketID = 0;

//	bListeningSocketSpawned = false;
//	bClientSocketSpawned = false;

	bNewConnections = true;

	bListening = false;
	bConnectedToServer = false;

	for(short i=0; i<CS_NET_MAN_MAX_SOCKETS; i++)
	{
		dwServerSocketID[i] = 0;
//		bServerSocketSpawned[i] = false;
		bServerSocketConnected[i] = false;
		bJustConnected[i] = false;
	}
}

csNetworkManagerSimple::~csNetworkManagerSimple()
{
	piNetDrv->Release();
}

void csNetworkManagerSimple::SysPrintf(int mode, char* szMsg, ...)
{
	char buf[1024];
	va_list arg;

	va_start(arg, szMsg);
	vsprintf(buf, szMsg, arg);
	va_end(arg);

	m_piSystem->Print(mode, buf);
}

STDMETHODIMP csNetworkManagerSimple::Open()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::Close()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::DoFrame()
{
	if(bListening)
	{
		CheckForIncomingConnections();

		if(bNewConnections) ProceedNewConnections();

		ParseIncomingFromClients();
	}

	if(bConnectedToServer) ParseIncomingFromServer();
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::CreateGame(unsigned short iNumOfPlayers)
{
	if (bListening)
	{
		dwLastError = CS_NET_MAN_ERR_ALREADY_LISTENING;
		return S_FALSE;
	}
	if (iNumOfPlayers == (unsigned short)-1)
          iNumOfPlayers = CS_NET_MAN_MAX_SOCKETS;
	iMaxPlayers = iNumOfPlayers;
	iCurPlayers = 0;

	CS_NET_LISTENPARAMS csNetListenParams;
	csNetListenParams.port = CS_NET_MAN_LISTEN_PORT;

	if(piNetDrv->Spawn(&dwListeningSocketID, CS_NET_CONNORIENTED) == S_FALSE)
	{
		dwLastError = piNetDrv->GetLastError();
		return S_FALSE;
	}

//	bListeningSocketSpawned = true;

	if(piNetDrv->SetListenState(dwListeningSocketID, &csNetListenParams) == S_FALSE)
	{
		piNetDrv->Kill(dwListeningSocketID);
		dwLastError = piNetDrv->GetLastError();
		return S_FALSE;
	}

	bListening = true;

	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::KillGame()
{
	if(!bListening) return S_OK;

	if(piNetDrv->Kill(dwListeningSocketID) == S_FALSE)
	{
		dwLastError = piNetDrv->GetLastError();
		return S_FALSE;
	}

	bListening = false;
//	bListeningSocketSpawned = false;

	DWORD retval = S_OK;

	for(int i=0; i<iMaxPlayers; i++)
	{
		if(piNetDrv->Kill(dwServerSocketID[i]) == S_FALSE)
		{
			dwLastError = piNetDrv->GetLastError();
			retval = S_FALSE;
		}
		else
		{
//			bServerSocketSpawned[i] = false;
			bServerSocketConnected[i] = false;
		}
	}

	return retval;
}

STDMETHODIMP csNetworkManagerSimple::JoinGame()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::LeaveGame()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::EnumerateGames()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::GetGameData()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::SetGameData()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::CreatePlayer()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::KillPlayer()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::GetPlayerData()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::SetPlayerData()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::AddPlayerToGroup()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::RemovePlayerFromGroup()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::CreateGroup()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::KillGroup()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::GetGroupData()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::SetGroupData()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::Send()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::Receive()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::GetMessageCount()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::PeekMessage()
{
	return S_OK;
}

STDMETHODIMP csNetworkManagerSimple::GetLastError()
{
	DWORD retval = dwLastError;
	dwLastError = 0;
	return retval;
}

STDMETHODIMP csNetworkManagerSimple::GetCaps()
{
	return S_OK;
}

DWORD csNetworkManagerSimple::SendMsg(DWORD dwSocketID, MSGHDR *lpmsghdr, char *buf)
{
	char msg[MAX_MSG_SIZE];
	unsigned short sizeofhdr = sizeof(*lpmsghdr);
	unsigned short bytestosend = sizeofhdr;

	lpmsghdr->crc = 0;

	memcpy(msg, lpmsghdr, sizeofhdr);

	if(lpmsghdr->msglength != 0)
	{
		memcpy(msg + sizeofhdr, buf, lpmsghdr->msglength);
		bytestosend+=lpmsghdr->msglength;
	}

	if(bytestosend > MAX_MSG_SIZE) bytestosend = MAX_MSG_SIZE;

	unsigned short tempcrc = CalculateCRC(msg, bytestosend);

	lpmsghdr->crc = tempcrc;
	memcpy(msg, lpmsghdr, sizeofhdr);

	if(piNetDrv->Send(dwSocketID, bytestosend, msg) == S_FALSE)
	{
		dwLastError = piNetDrv->GetLastError();
		return S_FALSE;
	}

	return S_OK;
}

DWORD csNetworkManagerSimple::RecvMsg(DWORD dwSocketID, MSGHDR *lpmsghdr, char *buf)
{
	char msg[MAX_MSG_SIZE];
	MSGHDR temphdr;

	unsigned long bytestorecv;
	unsigned short sizeofhdr = sizeof(*lpmsghdr);

	if(piNetDrv->Receive(dwSocketID, &bytestorecv, msg) == S_FALSE)
	{
		dwLastError = piNetDrv->GetLastError();
		return S_FALSE;
	}

	if(bytestorecv < sizeofhdr)
	{
		return 0xFFFFFFFF;
	}

	memcpy(lpmsghdr, msg, sizeofhdr);

	if(lpmsghdr->msglength != (bytestorecv - sizeofhdr))
	{
		return 0xFFFFFFFF;
	}

	memcpy(&temphdr, lpmsghdr, sizeofhdr);
	temphdr.crc = 0;
	memcpy(msg, &temphdr, sizeofhdr);

	unsigned short tempcrc = CalculateCRC(msg, bytestorecv);

	if(tempcrc != lpmsghdr->crc)
	{
		return 0xFFFFFFFF;
	}

	memcpy(buf, msg+sizeofhdr, bytestorecv-sizeofhdr);

	return S_OK;
}

DWORD csNetworkManagerSimple::CheckForIncomingConnections()
{
	DWORD dwTempSocket;
	CS_NET_ADDRESS csNetAddress;

	if(piNetDrv->Accept(dwListeningSocketID, &dwTempSocket, &csNetAddress) == S_FALSE)
	{
		dwLastError = piNetDrv->GetLastError();
		return S_FALSE;
	}

	if(iCurPlayers>=iMaxPlayers)
	{
		char buf[]="Too many players";
		MSGHDR msghdr;
		msghdr.type = MSG_SRV_CONNNACK;
		msghdr.msglength = sizeof(buf);
		SendMsg(dwTempSocket, &msghdr, buf);
		piNetDrv->Kill(dwTempSocket);
	}
	else
	{
		unsigned short i;
		for (i = 0; (i<iMaxPlayers) && (bServerSocketConnected[i]); i++)
			;
//		MSGHDR msghdr;
//		msghdr.type = MSG_SRV_CONNACK;
//		msghdr.msglength = 0;
//		SendMsg(dwTempSocket, &msghdr, NULL);
		dwServerSocketID[i] = dwTempSocket;

		m_piSystem->GetTime(ConnectionTime[i]);

		bJustConnected[i] = true;
		bServerSocketConnected[i] = true;
		bNewConnections = true;
	}

	CheckForIncomingConnections();

	return S_OK;
}

DWORD csNetworkManagerSimple::ProceedNewConnections()
{
	bNewConnections = false;

	for(unsigned short i = 0; i<iMaxPlayers; i++)
	{
		if(bJustConnected[i] = true)
		{
			time_t CurrentTime;
			m_piSystem->GetTime(CurrentTime);
			if((CurrentTime - ConnectionTime[i]) > 10000)
			{
				char buf[]="Connection timed out";
				MSGHDR msghdr;
				msghdr.type = MSG_SRV_CONNNACK;
				msghdr.msglength = sizeof(buf);
				SendMsg(dwServerSocketID[i], &msghdr, buf);
				piNetDrv->Kill(dwServerSocketID[i]);
				bJustConnected[i] = false;
				bServerSocketConnected[i] = false;
			}
			else
			{
				MSGHDR msghdr;
				msghdr.type = MSG_CLI_CONNREQ;
				char buf[MAX_MSG_SIZE];
				long retval = RecvMsg(dwServerSocketID[i], &msghdr, buf);
				if((retval == -1) || (msghdr.type != MSG_CLI_CONNREQ))
				{
					char tbuf[]="Trash received";
					MSGHDR tmsghdr;
					tmsghdr.type = MSG_SRV_CONNNACK;
					tmsghdr.msglength = sizeof(tbuf);
					SendMsg(dwServerSocketID[i], &tmsghdr, tbuf);
					piNetDrv->Kill(dwServerSocketID[i]);
					bJustConnected[i] = false;
					bServerSocketConnected[i] = false;
				}
				else if(retval == S_FALSE)
				{
					bNewConnections = true;
				}
				else
				{
					char tbuf[]="Connection accepted";
					MSGHDR tmsghdr;
					tmsghdr.type = MSG_SRV_CONNACK;
					tmsghdr.msglength = sizeof(tbuf);
					SendMsg(dwServerSocketID[i], &tmsghdr, tbuf);
					piNetDrv->Kill(dwServerSocketID[i]);
					bJustConnected[i] = false;
				}
			}
		}
	}
	return S_OK;
}

DWORD csNetworkManagerSimple::ParseIncomingFromClients()
{
	char buf[512];
	MSGHDR msghdr;

	for(unsigned short i = 0; i<iMaxPlayers; i++)
	{
		if(!bJustConnected[i] && bServerSocketConnected[i])
		{
			RecvMsg(dwServerSocketID[i], &msghdr, buf);
			if(msghdr.type == MSG_CLI_PLAIN)
			{
				ParsePlainMsg(buf, msghdr.msglength, true, i);
			}
		}
	}

	return S_OK;
}

DWORD csNetworkManagerSimple::ParseIncomingFromServer()
{
	return S_OK;
}

DWORD csNetworkManagerSimple::ParsePlainMsg(char *msg, unsigned short msgsize, bool fromclient, unsigned short src)
{
	PLAINMSGHDR pmh;
	char buf[512];
	unsigned short pmhsize = sizeof(PLAINMSGHDR);
	unsigned short bufsize = msgsize-pmhsize;

	if(msgsize <= pmhsize) return S_FALSE; // "<=" should be here because plain msg without msg is pointless

	memcpy(&pmh, msg, pmhsize);
	memcpy(buf, msg+pmhsize, bufsize);

	if(fromclient)
	{
		if (pmh.dest == BROADCAST)
		{
			for(unsigned short i = 0; i<iMaxPlayers; i++)
			{
				if((i != src) && (bServerSocketConnected[i]))
				{
					char temp[512];
					MSGHDR tempmh;
					PLAINMSGHDR temppmh;

					tempmh.type = MSG_SRV_PLAIN;
					tempmh.msglength = msgsize;

					temppmh.dest = i;
					temppmh.src = src;

					memcpy(temp, &tempmh, pmhsize);
					memcpy(temp+pmhsize, buf, bufsize);

					SendMsg(dwServerSocketID[i], &tempmh, temp);
				}
			}
		}
		else if(pmh.dest == SERVER)
		{
			strcat(buf, "\0");
			m_piSystem->Print(MSG_INITIALIZATION, buf);
		}
		else
		{
			if(bServerSocketConnected[pmh.dest])
			{
				char temp[512];
				MSGHDR tempmh;
				PLAINMSGHDR temppmh;

				tempmh.type = MSG_SRV_PLAIN;
				tempmh.msglength = msgsize;

				temppmh.dest = pmh.dest;
				temppmh.src = src;

				memcpy(temp, &tempmh, pmhsize);
				memcpy(temp+pmhsize, buf, bufsize);

				SendMsg(dwServerSocketID[temppmh.dest], &tempmh, temp);
			}
		}
	}
	else
	{
	}

	return S_OK;
}

unsigned short csNetworkManagerSimple::CalculateCRC(char *buf, int n)
{
	unsigned short crc_table[] = {
		0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
		0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
		0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
		0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
		0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
		0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
		0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
		0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
		0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
		0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
		0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
		0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
		0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
		0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
		0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
		0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
		0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
		0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
		0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
		0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
		0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
		0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
		0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
		0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
		0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
		0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
		0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
		0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
		0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
		0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
		0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
		0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
	};

	unsigned short crc = 0;

	while (--n >= 0) crc = ((crc >> 8) & 0xff) ^ crc_table[(crc ^ *buf++) & 0xff];

	return crc;
}

DWORD csNetworkManagerSimple::AddMessageToQueue(PLAINMSGHDR *hdr, char *body)
{
	unsigned short t1 = sizeof(body);
	unsigned short t2 = sizeof(&(messagequeue[0].body));
	unsigned short t;

	if(t1 >= t2) t = t2;
	else t = t1;

	if(!bNewMessages)
	{
		iCurMessage = 0;
		iLastMessage = 0;

		memcpy(&(messagequeue[0].hdr), hdr, sizeof(PLAINMSGHDR));
		memcpy(&(messagequeue[0].body), body, t);

		bNewMessages = true;
	}
	else
	{
		iLastMessage++;

		if(iLastMessage >= CS_NET_MAN_MAX_QUEUE) iLastMessage = 0;
		if(iLastMessage == iCurMessage) return 0xFFFFFFFF;

		memcpy(&(messagequeue[iLastMessage].hdr), hdr, sizeof(PLAINMSGHDR));
		memcpy(&(messagequeue[iLastMessage].body), body, t);
	}
	return S_OK;
}

DWORD csNetworkManagerSimple::ReadMessage(PLAINMSGHDR *hdr, char *body)
{
	if(bNewMessages)
	{
		unsigned short t1 = sizeof(body);
		unsigned short t2 = sizeof(&(messagequeue[0].body));
		unsigned short t;

		if(t1 >= t2) t = t2;
		else t = t1;

		memcpy(hdr, &(messagequeue[iCurMessage].hdr), sizeof(PLAINMSGHDR));
		memcpy(body, &(messagequeue[iCurMessage].body), t);

		iCurMessage++;
		if(iCurMessage >= CS_NET_MAN_MAX_QUEUE) iCurMessage = 0;
		if(iCurMessage == iLastMessage) bNewMessages = false;

		return S_OK;
	}
	else
	{
		return S_FALSE;
	}
}