/*
	master.c

	Quakeworld master server

	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
		
*/
static const char rcsid[] =
	"$Id$";

		/* afternoon hack (james was here.)
		   FIXME: I coded this outside of the QF tree so it has non-quake
		   stuff like malloc and things... ie, it's POSIX, not quake. */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_UNISTD_H
# include "unistd.h"
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_WINSOCK_H
# include <winsock.h>
#endif

#include <sys/types.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "protocol.h"

#if 0
#define S2C_CHALLENGE 'c'
#define S2M_HEARTBEAT 'a'
#define S2M_SHUTDOWN 'C'
#endif

#define MASTER_TIMEOUT 300
#define SLIST_MULTIPLE 1

typedef struct {
	struct		sockaddr_in addr;
	time_t		updated;
	qboolean	notimeout;
} server_t;

int 
QW_AddHeartbeat (server_t **servers_p, int slen,
				 struct sockaddr_in *addr, char *buf)
{
	server_t *servers = *servers_p;
	int freeslot = -1;
	int i;
	int sequence, players;
	char *c;

	sequence = atoi (buf + 2);
	c = strchr (buf + 2, '\n');
	players = c ? atoi (c + 1) : 0;

	for (i = 0; i < slen; i++) {
		if (servers[i].updated
			&& servers[i].addr.sin_addr.s_addr == addr->sin_addr.s_addr
			&& servers[i].addr.sin_port == addr->sin_port) {
			time (&servers[i].updated);
#if 1
			printf ("Refreshed %s:%d (seq %d, players %d)\n",
					inet_ntoa (servers[i].addr.sin_addr),
					ntohs (servers[i].addr.sin_port),
					sequence, players);
#endif
			return slen;
		} else if (freeslot == -1 && servers[i].updated == 0) {
			freeslot = i;
		}
	}
	if (freeslot == -1) {
		server_t *newservers;
		newservers = realloc (servers, sizeof (server_t)
									   * (slen + SLIST_MULTIPLE));
		if (!newservers) {
			printf ("realloc failed\n");
			return slen; // boo.
		}

		for (i = slen; i < slen + SLIST_MULTIPLE; i++)
			newservers[i].updated = 0;

		freeslot = slen;
		slen += SLIST_MULTIPLE;
		*servers_p = newservers;
	}
	servers[freeslot].addr = *addr;
	servers[freeslot].notimeout = false;
#if 1
	printf ("Added %s:%d (seq %d, players %d)\n",
			inet_ntoa (servers[freeslot].addr.sin_addr),
			ntohs (servers[freeslot].addr.sin_port),
			sequence, players);
#endif
	time (&(servers[freeslot].updated));
	return slen;
}

void 
QW_TimeoutHearts (server_t *servers, int slen)
{
	time_t t;
	int i;
	time (&t);
	for (i = 0; i < slen; i++) {
		if (servers[i].updated != 0
			&& servers[i].updated < t - MASTER_TIMEOUT
			&& !servers[i].notimeout) {
			servers[i].updated = 0;
#if 1
			printf ("Removed %s:%d (timeout)\n",
				   inet_ntoa (servers[i].addr.sin_addr),
				   ntohs (servers[i].addr.sin_port));
#endif
		}
	}
}

void
QW_HeartShutdown (struct sockaddr_in *addr, server_t *servers,
				  int slen)
{
	int i;
	for (i = 0; i < slen; i++) {
		if (servers[i].addr.sin_addr.s_addr == addr->sin_addr.s_addr
			&& servers[i].addr.sin_port == addr->sin_port) {
			servers[i].updated = 0;
#if 1
			printf ("Removed %s:%d\n",
				   inet_ntoa (servers[i].addr.sin_addr),
				   ntohs (servers[i].addr.sin_port));
#endif
			return;
		}
	}
}

void
QW_SendHearts (int sock, struct sockaddr_in *addr, server_t *servers,
			   int serverlen) 
{
	unsigned char *out;
	int count, cpos;
	int i;

	count = 0;

	for (i = 0; i < serverlen; i++)
		if (servers[i].updated != 0)
			count++;

	out = malloc ((count + 1) * 6);
	if (!out) {
		printf ("malloc failed\n");
		return;
	}
	cpos = 1;
	out[0] = out[1] = out[2] = out[3] = 0xff;
	out[4] = 0x64;
	out[5] = 0x0a;

	for (i = 0; i < serverlen; i++) {
		if (servers[i].updated != 0) {
			unsigned char *p = out + (cpos * 6);
			p[0] = ((unsigned char *) &servers[i].addr.sin_addr.s_addr)[0];
			p[1] = ((unsigned char *) &servers[i].addr.sin_addr.s_addr)[1];
			p[2] = ((unsigned char *) &servers[i].addr.sin_addr.s_addr)[2];
			p[3] = ((unsigned char *) &servers[i].addr.sin_addr.s_addr)[3];
			p[4] = (unsigned char) (ntohs(servers[i].addr.sin_port) >> 8);
			p[5] = (unsigned char) (ntohs(servers[i].addr.sin_port) & 0xFF);
			++cpos;
		}
	}
	sendto (sock, out, (count + 1) * 6, 0, (struct sockaddr *) addr,
			sizeof (struct sockaddr_in));
	free (out);
}

void
QW_Pong (int sock, struct sockaddr_in *addr)
{
	char data[6] = {0xFF, 0xFF, 0xFF, 0xFF, A2A_ACK, 0};
	printf ("Ping\n");
	sendto (sock, data, sizeof (data), 0,
			(struct sockaddr *) addr, sizeof (struct sockaddr_in));
}
      

void 
QW_Master (struct sockaddr_in *addr)
{
	int sock;
	server_t *servers;
	int serverlen = SLIST_MULTIPLE;
	int i;
#ifdef _WIN32
	WSADATA winsockdata;
#endif

	servers = malloc (sizeof (server_t) * serverlen);
	if (!servers) {
		printf ("initial malloc failed\n");
		return;
	}

	for (i = 0; i < serverlen; i++)
		servers[i].updated = 0;

#ifdef _WIN32
	i = WSAStartup (MAKEWORD (1, 1), &winsockdata);
	if (i) {
		printf ("Winsock initialization failed.\n");
		return;
	}
#endif

	sock = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock < 0) {
		printf ("socket failed\n");
		return;
	}

	if (bind (sock, (struct sockaddr *) addr, sizeof (struct sockaddr_in))) {
		printf ("bind failed\n");
		return;
	}
   
	listen (sock, 4); // probably don't need this.
   
	while (1) {
		char buf[31];
		struct sockaddr_in recvaddr;
		int recvsize = sizeof (struct sockaddr_in);
		int size;
		buf[30] = '\0'; // a sentinal for string ops

		size = recvfrom (sock, buf, sizeof (buf) - 1, 0,
						 (struct sockaddr *) &recvaddr, &recvsize);
		if (size == -1) {
			printf ("recvfrom failed\n");
			continue;
		}
#if 0
		printf ("Got %d bytes: 0x%x from %s\n", size, buf[0],
				inet_ntoa (recvaddr.sin_addr));
#endif

#if 0
		printf ("Message Contents: '");
		{ // so that 'j' isn't unused when commented out
			int j;
			for (j = 0; j < size; j++)
				if (isprint (buf[j]))
					printf ("%c", buf[j]);
				else
					switch (buf[j]) {
						case '\n': printf ("\\n"); break;
						case '\0': printf ("\\0"); break;
						default:   printf ("(%02x)", buf[j]);
					}
		}
		printf ("'\n");
#endif

		QW_TimeoutHearts (servers, serverlen);
		switch (buf[0]) {
			case S2C_CHALLENGE:
				QW_SendHearts (sock, &recvaddr, servers, serverlen);
				break;
			case S2M_HEARTBEAT:
				serverlen = QW_AddHeartbeat (&servers, serverlen,
											 &recvaddr, buf);
				break;
			case S2M_SHUTDOWN:
				QW_HeartShutdown (&recvaddr, servers, serverlen);
				break;
			case A2A_PING:
				QW_Pong (sock, &recvaddr);
				break;
			default:
				printf ("Unknown 0x%x\n", buf[0]);
				break;
		}
	}
	free (servers);
}

int
main (int argc, char **argv)
{
	struct sockaddr_in addr;
	short port = htons (27000);
#ifndef WIN32 //FIXME
	int c;

	while ((c = getopt (argc, argv, "p:")) != -1) {
		if (c == 'p') {
			port = htons (atoi (optarg));
		}
	}
#endif
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = port;
	QW_Master (&addr);
	return 0;
}