/*
	net_main.c

	@description@

	Copyright (C) 1996-1997  Id Software, Inc.

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

	This program 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 General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#include "QF/cbuf.h"
#include "QF/cmd.h"
#include "QF/cvar.h"
#include "QF/msg.h"
#include "QF/qargs.h"
#include "QF/quakeio.h"
#include "QF/sizebuf.h"
#include "QF/sys.h"

#include "netmain.h"
#include "net_vcr.h"

#include "../nq/include/server.h"

int net_is_dedicated = 0;

qsocket_t  *net_activeSockets = NULL;
qsocket_t  *net_freeSockets = NULL;
int         net_numsockets = 0;

qboolean    tcpipAvailable = false;

int         net_hostport;
int         DEFAULTnet_hostport = 26000;

char        my_tcpip_address[NET_NAMELEN];

static qboolean listening = false;

qboolean    slistInProgress = false;
qboolean    slistSilent = false;
qboolean    slistLocal = true;
static double slistStartTime;
static int  slistLastShown;

static void Slist_Send (void *);
static void Slist_Poll (void *);
PollProcedure slistSendProcedure = { NULL, 0.0, Slist_Send };
PollProcedure slistPollProcedure = { NULL, 0.0, Slist_Poll };


static sizebuf_t _net_message_message;
static qmsg_t _net_message = { 0, 0, &_net_message_message };
qmsg_t     *net_message = &_net_message;
unsigned    net_activeconnections = 0;

int         messagesSent = 0;
int         messagesReceived = 0;
int         unreliableMessagesSent = 0;
int         unreliableMessagesReceived = 0;

float net_messagetimeout;
static cvar_t net_messagetimeout_cvar = {
	.name = "net_messagetimeout",
	.description =
		"None",
	.default_value = "300",
	.flags = CVAR_NONE,
	.value = { .type = &cexpr_float, .value = &net_messagetimeout },
};
char *hostname;
static cvar_t hostname_cvar = {
	.name = "hostname",
	.description =
		"None",
	.default_value = "UNNAMED",
	.flags = CVAR_NONE,
	.value = { .type = 0, .value = &hostname },
};

QFile      *vcrFile;
qboolean    recording = false;

// these two macros are to make the code more readable
#define sfunc	net_drivers[sock->driver]
#define dfunc	net_drivers[net_driverlevel]

int         net_driverlevel;

double      net_time;

static int         hostCacheCount = 0;
static hostcache_t hostcache[HOSTCACHESIZE];

static cbuf_t *net_cbuf;

double
SetNetTime (void)
{
	net_time = Sys_DoubleTime ();
	return net_time;
}


qsocket_t *
NET_NewQSocket (void)
{
	qsocket_t  *sock;

	if (net_freeSockets == NULL)
		return NULL;

	if (net_activeconnections >= svs.maxclients)
		return NULL;

	// get one from free list
	sock = net_freeSockets;
	net_freeSockets = sock->next;

	// add it to active list
	sock->next = net_activeSockets;
	net_activeSockets = sock;

	sock->disconnected = false;
	sock->connecttime = net_time;
	strcpy (sock->address, "UNSET ADDRESS");
	sock->driver = net_driverlevel;
	sock->socket = 0;
	sock->driverdata = NULL;
	sock->canSend = true;
	sock->sendNext = false;
	sock->lastMessageTime = net_time;
	sock->ackSequence = 0;
	sock->sendSequence = 0;
	sock->unreliableSendSequence = 0;
	sock->sendMessageLength = 0;
	sock->receiveSequence = 0;
	sock->unreliableReceiveSequence = 0;
	sock->receiveMessageLength = 0;

	return sock;
}


void
NET_FreeQSocket (qsocket_t *sock)
{
	qsocket_t  *s;

	// remove it from active list
	if (sock == net_activeSockets)
		net_activeSockets = net_activeSockets->next;
	else {
		for (s = net_activeSockets; s; s = s->next)
			if (s->next == sock) {
				s->next = sock->next;
				break;
			}
		if (!s)
			Sys_Error ("NET_FreeQSocket: not active");
	}

	// add it to free list
	sock->next = net_freeSockets;
	net_freeSockets = sock;
	sock->disconnected = true;
}


static void
NET_Listen_f (void)
{
	if (Cmd_Argc () != 2) {
		Sys_Printf ("\"listen\" is \"%u\"\n", listening ? 1 : 0);
		return;
	}

	listening = atoi (Cmd_Argv (1)) ? true : false;

	for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
		 net_driverlevel++) {
		if (net_drivers[net_driverlevel].initialized == false)
			continue;
		dfunc.Listen (listening);
	}
}


static void
MaxPlayers_f (void)
{
	unsigned    n;

	if (Cmd_Argc () != 2) {
		Sys_Printf ("\"maxplayers\" is \"%u\"\n", svs.maxclients);
		return;
	}

	if (sv.active) {
		Sys_Printf
			("maxplayers can not be changed while a server is running.\n");
		return;
	}

	n = atoi (Cmd_Argv (1));
	if (n < 1)
		n = 1;
	if (n > svs.maxclientslimit) {
		n = svs.maxclientslimit;
		Sys_Printf ("\"maxplayers\" set to \"%u\"\n", n);
	}

	if ((n == 1) && listening)
		Cbuf_AddText (net_cbuf, "listen 0\n");

	if ((n > 1) && (!listening))
		Cbuf_AddText (net_cbuf, "listen 1\n");

	svs.maxclients = n;
	if (n == 1)
		Cvar_Set ("deathmatch", "0");
	else
		Cvar_Set ("deathmatch", "1");
}


static void
NET_Port_f (void)
{
	int         n;

	if (Cmd_Argc () != 2) {
		Sys_Printf ("\"port\" is \"%u\"\n", net_hostport);
		return;
	}

	n = atoi (Cmd_Argv (1));
	if (n < 1 || n > 65534) {
		Sys_Printf ("Bad value, must be between 1 and 65534\n");
		return;
	}

	DEFAULTnet_hostport = n;
	net_hostport = n;

	if (listening) {
		// force a change to the new port
		Cbuf_AddText (net_cbuf, "listen 0\n");
		Cbuf_AddText (net_cbuf, "listen 1\n");
	}
}


static void
PrintSlistHeader (void)
{
	Sys_Printf ("Server          Map             Users\n");
	Sys_Printf ("--------------- --------------- -----\n");
	slistLastShown = 0;
}

void
NET_AddCachedHost (const char *name, const char *map, const char *cname,
				   int users, int maxusers, int driver, int ldriver,
				   const netadr_t *addr)
{
	if (hostCacheCount == HOSTCACHESIZE) {
		return;
	}
	for (int i = 0; i < hostCacheCount; i++) {
		// addr will be 0 for loopback, and there can be only one loopback
		// server.
		if (!addr || !memcmp (addr, &hostcache[i].addr, sizeof (netadr_t))) {
			return;
		}
	}

	const int   namesize = sizeof (hostcache[0].name) - 1;
	const int   mapsize = sizeof (hostcache[0].map) - 1;
	const int   cnamesize = sizeof (hostcache[0].cname) - 1;

	hostcache_t *host = &hostcache[hostCacheCount++];
	strncpy (host->name, name, namesize);
	strncpy (host->map, map, mapsize);
	strncpy (host->cname, cname, cnamesize);
	host->name[namesize] = 0;
	host->map[mapsize] = 0;
	host->cname[cnamesize] = 0;

	host->users = users;
	host->maxusers = maxusers;
	host->driver = driver;
	host->ldriver = ldriver;
	if (addr) {
		host->addr = *addr;
	} else {
		memset (&host->addr, 0, sizeof (host->addr));
	}

	// check for and resolve name conflicts
	for (int i = 0; i < hostCacheCount - 1; i++) {
		if (strcasecmp (host->name, hostcache[i].name) == 0) {
			int         len = strlen (host->name);
			if (len < namesize && host->name[len - 1] > '8') {
				host->name[len] = '0';
				host->name[len + 1] = 0;
			} else {
				host->name[len - 1]++;
			}
			i = -1;	// restart loop
		}
	}
}

static void
PrintSlist (void)
{
	int         n;

	for (n = slistLastShown; n < hostCacheCount; n++) {
		if (hostcache[n].maxusers)
			Sys_Printf ("%-15.15s %-15.15s %2u/%2u\n", hostcache[n].name,
						hostcache[n].map, hostcache[n].users,
						hostcache[n].maxusers);
		else
			Sys_Printf ("%-15.15s %-15.15s\n", hostcache[n].name,
						hostcache[n].map);
	}
	slistLastShown = n;
}


static void
PrintSlistTrailer (void)
{
	if (hostCacheCount)
		Sys_Printf ("== end list ==\n\n");
	else
		Sys_Printf ("No Quake servers found.\n\n");
}


static void
NET_Slist_f (void)
{
	if (slistInProgress)
		return;

	if (!slistSilent) {
		Sys_Printf ("Looking for Quake servers...\n");
		PrintSlistHeader ();
	}

	slistInProgress = true;
	slistStartTime = Sys_DoubleTime ();

	SchedulePollProcedure (&slistSendProcedure, 0.0);
	SchedulePollProcedure (&slistPollProcedure, 0.1);

	hostCacheCount = 0;
}


static void
Slist_Send (void *unused)
{
	for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
		 net_driverlevel++) {
		if (!slistLocal && net_driverlevel == 0)
			continue;
		if (net_drivers[net_driverlevel].initialized == false)
			continue;
		dfunc.SearchForHosts (true);
	}

	if ((Sys_DoubleTime () - slistStartTime) < 0.5)
		SchedulePollProcedure (&slistSendProcedure, 0.75);
}


static void
Slist_Poll (void *unused)
{
	for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
		 net_driverlevel++) {
		if (!slistLocal && net_driverlevel == 0)
			continue;
		if (net_drivers[net_driverlevel].initialized == false)
			continue;
		dfunc.SearchForHosts (false);
	}

	if (!slistSilent)
		PrintSlist ();

	if ((Sys_DoubleTime () - slistStartTime) < 1.5) {
		SchedulePollProcedure (&slistPollProcedure, 0.1);
		return;
	}

	if (!slistSilent)
		PrintSlistTrailer ();
	slistInProgress = false;
	slistSilent = false;
	slistLocal = true;
}

qsocket_t *
NET_Connect (const char *host)
{
	qsocket_t  *ret;
	int         n;
	int         numdrivers = net_numdrivers;

	SetNetTime ();

	if (host && *host == 0)
		host = NULL;

	if (host) {
		if (strcasecmp (host, "local") == 0) {
			numdrivers = 1;
			goto JustDoIt;
		}

		if (hostCacheCount) {
			for (n = 0; n < hostCacheCount; n++)
				if (strcasecmp (host, hostcache[n].name) == 0) {
					host = hostcache[n].cname;
					break;
				}
			if (n < hostCacheCount)
				goto JustDoIt;
		}
	}

	slistSilent = host ? true : false;
	NET_Slist_f ();

	while (slistInProgress)
		NET_Poll ();

	if (host == NULL) {
		if (hostCacheCount != 1)
			return NULL;
		host = hostcache[0].cname;
		Sys_Printf ("Connecting to...\n%s @ %s\n\n", hostcache[0].name, host);
	}

	if (hostCacheCount)
		for (n = 0; n < hostCacheCount; n++)
			if (strcasecmp (host, hostcache[n].name) == 0) {
				host = hostcache[n].cname;
				break;
			}

  JustDoIt:
	for (net_driverlevel = 0; net_driverlevel < numdrivers; net_driverlevel++) {
		if (net_drivers[net_driverlevel].initialized == false)
			continue;
		ret = dfunc.Connect (host);
		if (ret)
			return ret;
	}

	if (host) {
		Sys_Printf ("\n");
		PrintSlistHeader ();
		PrintSlist ();
		PrintSlistTrailer ();
	}

	return NULL;
}


struct {
	double      time;
	int         op;
	intptr_t    session;
} vcrConnect;

qsocket_t *
NET_CheckNewConnections (void)
{
	qsocket_t  *ret;

	SetNetTime ();

	for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
		 net_driverlevel++) {
		if (net_drivers[net_driverlevel].initialized == false)
			continue;
		if (net_driverlevel && listening == false)
			continue;
		ret = dfunc.CheckNewConnections ();
		if (ret) {
			if (recording) {
				vcrConnect.time = host_time;
				vcrConnect.op = VCR_OP_CONNECT;
				vcrConnect.session = (intptr_t) ret;
				Qwrite (vcrFile, &vcrConnect, sizeof (vcrConnect));
				Qwrite (vcrFile, ret->address, NET_NAMELEN);
			}
			return ret;
		}
	}

	if (recording) {
		vcrConnect.time = host_time;
		vcrConnect.op = VCR_OP_CONNECT;
		vcrConnect.session = 0;
		Qwrite (vcrFile, &vcrConnect, sizeof (vcrConnect));
	}

	return NULL;
}

void
NET_Close (qsocket_t *sock)
{
	if (!sock)
		return;

	if (sock->disconnected)
		return;

	SetNetTime ();

	// call the driver_Close function
	sfunc.Close (sock);

	Sys_MaskPrintf (SYS_net, "closing socket\n");
	NET_FreeQSocket (sock);
}


struct {
	double      time;
	int         op;
	intptr_t    session;
	int         ret;
	int         len;
} vcrGetMessage;


int
NET_GetMessage (qsocket_t *sock)
{
	int         ret;

	if (!sock)
		return -1;

	if (sock->disconnected) {
		Sys_Printf ("NET_GetMessage: disconnected socket\n");
		return -1;
	}

	SetNetTime ();

	ret = sfunc.QGetMessage (sock);

	// see if this connection has timed out
	if (ret == 0 && sock->driver) {
		if (net_time - sock->lastMessageTime > net_messagetimeout) {
			Sys_MaskPrintf (SYS_net, "socket timed out\n");
			NET_Close (sock);
			return -1;
		}
	}


	if (ret > 0) {
		if (sock->driver) {
			sock->lastMessageTime = net_time;
			if (ret == 1)
				messagesReceived++;
			else if (ret == 2)
				unreliableMessagesReceived++;
		}

		if (recording) {
			vcrGetMessage.time = host_time;
			vcrGetMessage.op = VCR_OP_GETMESSAGE;
			vcrGetMessage.session = (intptr_t) sock;
			vcrGetMessage.ret = ret;
			vcrGetMessage.len = _net_message_message.cursize;
			Qwrite (vcrFile, &vcrGetMessage, 24);
			Qwrite (vcrFile, _net_message_message.data,
						   _net_message_message.cursize);
		}
	} else {
		if (recording) {
			vcrGetMessage.time = host_time;
			vcrGetMessage.op = VCR_OP_GETMESSAGE;
			vcrGetMessage.session = (intptr_t) sock;
			vcrGetMessage.ret = ret;
			Qwrite (vcrFile, &vcrGetMessage, 20);
		}
	}

	return ret;
}


struct {
	double      time;
	int         op;
	intptr_t    session;
	int         r;
} vcrSendMessage;

int
NET_SendMessage (qsocket_t *sock, sizebuf_t *data)
{
	int         r;

	if (!sock)
		return -1;

	if (sock->disconnected) {
		Sys_Printf ("NET_SendMessage: disconnected socket\n");
		return -1;
	}

	SetNetTime ();
	r = sfunc.QSendMessage (sock, data);
	if (r == 1 && sock->driver)
		messagesSent++;

	if (recording) {
		vcrSendMessage.time = host_time;
		vcrSendMessage.op = VCR_OP_SENDMESSAGE;
		vcrSendMessage.session = (intptr_t) sock;
		vcrSendMessage.r = r;
		Qwrite (vcrFile, &vcrSendMessage, 20);
	}

	return r;
}


int
NET_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data)
{
	int         r;

	if (!sock)
		return -1;

	if (sock->disconnected) {
		Sys_Printf ("NET_SendMessage: disconnected socket\n");
		return -1;
	}

	SetNetTime ();
	r = sfunc.SendUnreliableMessage (sock, data);
	if (r == 1 && sock->driver)
		unreliableMessagesSent++;

	if (recording) {
		vcrSendMessage.time = host_time;
		vcrSendMessage.op = VCR_OP_SENDMESSAGE;
		vcrSendMessage.session = (intptr_t) sock;
		vcrSendMessage.r = r;
		Qwrite (vcrFile, &vcrSendMessage, 20);
	}

	return r;
}


qboolean
NET_CanSendMessage (qsocket_t *sock)
{
	int         r;

	if (!sock)
		return false;

	if (sock->disconnected)
		return false;

	SetNetTime ();

	r = sfunc.CanSendMessage (sock);

	if (recording) {
		vcrSendMessage.time = host_time;
		vcrSendMessage.op = VCR_OP_CANSENDMESSAGE;
		vcrSendMessage.session = (intptr_t) sock;
		vcrSendMessage.r = r;
		Qwrite (vcrFile, &vcrSendMessage, 20);
	}

	return r;
}


int
NET_SendToAll (sizebuf_t *data, double blocktime)
{
	double      start;
	unsigned    i;
	int         count = 0;
	qboolean    state1[MAX_SCOREBOARD];	/* can we send */
	qboolean    state2[MAX_SCOREBOARD];	/* did we send */

	for (i = 0, host_client = svs.clients; i < svs.maxclients;
		 i++, host_client++) {
		if (host_client->netconnection && host_client->active) {
			if (host_client->netconnection->driver == 0) {
				NET_SendMessage (host_client->netconnection, data);
				state1[i] = true;
				state2[i] = true;
				continue;
			}
			count++;
			state1[i] = false;
			state2[i] = false;
		} else {
			state1[i] = true;
			state2[i] = true;
		}
	}

	start = Sys_DoubleTime ();
	while (count) {
		count = 0;
		for (i = 0, host_client = svs.clients; i < svs.maxclients;
			 i++, host_client++) {
			if (!state1[i]) {
				if (NET_CanSendMessage (host_client->netconnection)) {
					state1[i] = true;
					NET_SendMessage (host_client->netconnection, data);
				} else {
					NET_GetMessage (host_client->netconnection);
				}
				count++;
				continue;
			}

			if (!state2[i]) {
				if (NET_CanSendMessage (host_client->netconnection)) {
					state2[i] = true;
				} else {
					NET_GetMessage (host_client->netconnection);
				}
				count++;
				continue;
			}
		}
		if ((Sys_DoubleTime () - start) > blocktime)
			break;
	}
	return count;
}


//=============================================================================

static void
NET_shutdown (void *data)
{
	qsocket_t  *sock;

	SetNetTime ();

	for (sock = net_activeSockets; sock; sock = sock->next)
		NET_Close (sock);

//
// shutdown the drivers
//
	for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
		 net_driverlevel++) {
		if (net_drivers[net_driverlevel].initialized == true) {
			net_drivers[net_driverlevel].Shutdown ();
			net_drivers[net_driverlevel].initialized = false;
		}
	}

	if (vcrFile) {
		Sys_Printf ("Closing vcrfile.\n");
		Qclose (vcrFile);
	}
}

void
NET_Init (cbuf_t *cbuf)
{
	int         i;
	int         controlSocket;
	qsocket_t  *s;

	net_cbuf = cbuf;

	Sys_RegisterShutdown (NET_shutdown, 0);

	if (COM_CheckParm ("-playback")) {
		net_numdrivers = 1;
		net_drivers[0].Init = VCR_Init;
	}

	if (COM_CheckParm ("-record"))
		recording = true;

	i = COM_CheckParm ("-port");
	if (!i)
		i = COM_CheckParm ("-udpport");
	if (!i)
		i = COM_CheckParm ("-ipxport");

	if (i) {
		if (i < com_argc - 1)
			DEFAULTnet_hostport = atoi (com_argv[i + 1]);
		else
			Sys_Error ("NET_Init: you must specify a number after -port");
	}
	net_hostport = DEFAULTnet_hostport;

	if (COM_CheckParm ("-listen") || net_is_dedicated)
		listening = true;
	net_numsockets = svs.maxclientslimit;
	if (!net_is_dedicated)
		net_numsockets++;

	SetNetTime ();

	for (i = 0; i < net_numsockets; i++) {
		s = (qsocket_t *) Hunk_AllocName (0, sizeof (qsocket_t), "qsocket");
		s->next = net_freeSockets;
		net_freeSockets = s;
		s->disconnected = true;
	}

	// allocate space for network message buffer
	SZ_Alloc (&_net_message_message, NET_MAXMESSAGE);

	Cvar_Register (&net_messagetimeout_cvar, 0, 0);
	Cvar_Register (&hostname_cvar, 0, 0);

	Cmd_AddCommand ("slist", NET_Slist_f, "No Description");
	Cmd_AddCommand ("listen", NET_Listen_f, "No Description");
	Cmd_AddCommand ("maxplayers", MaxPlayers_f, "No Description");
	Cmd_AddCommand ("port", NET_Port_f, "No Description");

	// initialize all the drivers
	for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
		 net_driverlevel++) {
		controlSocket = net_drivers[net_driverlevel].Init ();
		if (controlSocket == -1)
			continue;
		net_drivers[net_driverlevel].initialized = true;
		net_drivers[net_driverlevel].controlSock = controlSocket;
		if (listening)
			net_drivers[net_driverlevel].Listen (true);
	}

	if (*my_tcpip_address)
		Sys_MaskPrintf (SYS_net, "TCP/IP address %s\n", my_tcpip_address);
}


static PollProcedure *pollProcedureList = NULL;

void
NET_Poll (void)
{
	PollProcedure *pp;

	SetNetTime ();

	for (pp = pollProcedureList; pp; pp = pp->next) {
		if (pp->nextTime > net_time)
			break;
		pollProcedureList = pp->next;
		pp->procedure (pp->arg);
	}
}


void
SchedulePollProcedure (PollProcedure *proc, double timeOffset)
{
	PollProcedure *pp, *prev;

	proc->nextTime = Sys_DoubleTime () + timeOffset;
	for (pp = pollProcedureList, prev = NULL; pp; pp = pp->next) {
		if (pp->nextTime >= proc->nextTime)
			break;
		prev = pp;
	}

	if (prev == NULL) {
		proc->next = pollProcedureList;
		pollProcedureList = proc;
		return;
	}

	proc->next = pp;
	prev->next = proc;
}
