#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "i_net.h"

#if defined _MSC_VER
#define __WIN32__
#endif

#ifndef __WIN32__

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <time.h>
#include <curses.h>

#define Sleep(x)	usleep (x * 1000)

#else

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <conio.h>

#endif

#define MIN(a, b) ((a)<(b)?(a):(b))

// The master server code

#define mst_server 100
#define mst_end    101

#define MAX_SERVERS	64
#define MAX_SERVERS_FROM_ONE_IP	4
#define MAX_SERVERS_AGE	60*4 // 1 minute
#define MAX_REMOTE_SERVERS_AGE 600*4 // 10 minutes

#define ORIGINAL_MASTER1 "csdoom.dhs.org"
#define ORIGINAL_MASTER2 "65.192.90.195"
#define SERVERLIST_OUTPUT_FILE "serverlist.js"

int iOriginalMasterAlive = 0;

int       msg;
netadr_t  masteraddr; // address of the master server
char      masterip[] = "localhost";

buf_t   message;
byte    message_buffer[MAX_UDP_PACKET];

unsigned int iTrafficWarden = 0;

typedef struct server
{
	bool bExist;
	bool bRegisteredOnRemoteMaster;
	netadr_t addr;
	int iAge;
}Server, *pServer;

Server Servers[MAX_SERVERS];

void vClearServers()
{
	memset(&Servers, 0, sizeof(Servers));
}

int iCountServers(void)
{
	int index, x = 0;
	// Check existing servers for matching address
	for(index = 0; index < MAX_SERVERS; index++)
	{
		if(Servers[index].bExist)
		{
			x++;
		}
	}
	return x;
}

int iOutputServerList(void)
{
	FILE *fp = fopen(SERVERLIST_OUTPUT_FILE, "wb");
	
	if(!fp)
		return false;

	int index, x = 0;

	int iservers = iCountServers();
	//fprintf(fp, "<html><header><meta http-equiv=\"Refresh\" content=\"120; url=\'http://www.voxelsoft.com/csdoom/bin/serverlist.htm\'\"></header><body bgcolor=black text=white><center>\r\n");
	fprintf(fp, "iservers = %d; var N = new Array(%d);\r\n", iservers, iservers);

	// Check existing servers for matching address
	for(index = 0; index < MAX_SERVERS; index++)
	{
		if(Servers[index].bExist)
		{
			//Servers[index].
			x++;
			fprintf(fp, "N[%d]=\"%s\";\r\n", x-1, NET_AdrToString(Servers[index].addr));
		}
	}

	if(iOriginalMasterAlive)
		fprintf(fp, "original=1;\r\n");
	else
		fprintf(fp, "original=0;\r\n");


	time_t t;
	time(&t);

	//fprintf(fp, "lastupdate=\"%s;\"\r\n", ctime(&t));

	//fprintf(fp, "</center></body></html>\r\n");
	fclose(fp);

	return x;
}

bool bAddServer(netadr_t addr, bool bRemote)
{
	int index;
	int servers_on_this_ip = 0;

	// Check existing servers for matching address
	for(index = 0; index < MAX_SERVERS; index++)
	{
		if(Servers[index].bExist)
		{
			if(memcmp(&Servers[index].addr.ip, &addr.ip, 4) == 0)
			{
				servers_on_this_ip++;

				if(Servers[index].addr.port == addr.port)
				{
					// Same server, just update its time
					Servers[index].iAge = 0;
					return true;
				}
			}
		}
	}

	if(servers_on_this_ip > MAX_SERVERS_FROM_ONE_IP)
	{
		printf ("Failed to add server: %s as %d already from this ip\n", NET_AdrToString(Servers[index].addr), servers_on_this_ip);
		return false;
	}

	// Check free server slots
	for(index = 0; index < MAX_SERVERS; index++)
	{
		if(!Servers[index].bExist)
		{
			// Copy the new server entry
			Servers[index].bExist = true;
			memcpy(&Servers[index].addr, &addr, sizeof(addr));
			// Update time
			Servers[index].iAge = 0;
			Servers[index].bRegisteredOnRemoteMaster = bRemote;

			if(Servers[index].bRegisteredOnRemoteMaster)
				printf ("Added new remote server: %s, %d total\n", NET_AdrToString(Servers[index].addr), iCountServers());
			else
			{
				printf ("Added new server: %s, %d total\n", NET_AdrToString(Servers[index].addr), iCountServers());
				FILE *fp = fopen("master_log.txt", "a");
				fprintf(fp, "Server registered: %s, %d total\r\n", NET_AdrToString(Servers[index].addr), iCountServers());
				fclose(fp);
				iOutputServerList();
			}
			return true;
		}
	}

	printf ("Failed to add server: %s, no slots left\n", addr);
	return false;
}

void vAgeServers(void)
{
	int index;

	// Check existing servers for matching address
	for(index = 0; index < MAX_SERVERS; index++)
	{
		if(Servers[index].bExist)
		{
			Servers[index].iAge ++;
			if(Servers[index].bRegisteredOnRemoteMaster)
			{
				if(Servers[index].iAge > MAX_REMOTE_SERVERS_AGE)
				{
					printf ("Server timed out: %s, %d total\n", NET_AdrToString(Servers[index].addr), iCountServers());
					memset(&Servers[index], 0, sizeof(Servers[index]));
				}
			}
			else
			{
				if(Servers[index].iAge > MAX_SERVERS_AGE)
				{
					printf ("Remote server timed out: %s, %d total\n", NET_AdrToString(Servers[index].addr), iCountServers());
					memset(&Servers[index], 0, sizeof(Servers[index]));
				}
			}
		}
	}

	iOutputServerList();
}

void vWriteServerData(void)
{
	int index;

	// Check existing servers for matching address
	for(index = 0; index < MAX_SERVERS; index++)
	{
		if(Servers[index].bExist)
		{
			MSG_WriteByte(&message, mst_server);
			MSG_WriteString(&message, NET_AdrToString(Servers[index].addr));
		}
	}
}

int main()
{
	int LastMasterUpdate = MAX_REMOTE_SERVERS_AGE;
	localport = MASTERPORT;
	int pid = fork();
	if(pid)
		return 0;
	InitNetCommon();

    message.data = message_buffer;
	message.maxsize = sizeof(message_buffer);

	printf ("Master Server Started... close to exit, no console support.\n");

	vClearServers();

	while(true)
	{
		SZ_Clear(&message);

		if(NET_GetPacket())
		{
			int challenge = MSG_ReadLong();
		
			switch(challenge)
			{
			case SERVER_CHALLENGE:
				// Server making us aware of its existance
				printf ("Server ping, IP = %s\n", NET_AdrToString(net_from));
				bAddServer(net_from, 0);
			    break;
			case LAUNCHER_CHALLENGE:
				if(MSG_ReadByte() == mst_server)
				{
					netadr_t addr, addr2;
					//NET_StringToAdr(ORIGINAL_MASTER1, &addr);
					NET_StringToAdr(ORIGINAL_MASTER2, &addr2);
					
					//I_SetPort(addr, MASTERPORT);
					I_SetPort(addr2, MASTERPORT);

					//if(strcmp(NET_AdrToString(net_from), NET_AdrToString(addr2)) == 0)
					//{
						// Old server ok, but its DNS is down
					//}
					if(strcmp(NET_AdrToString(net_from), NET_AdrToString(addr2)) != 0)
					{
						printf ("Unauthorised server list from %s\n", NET_AdrToString(net_from));
						break;
					}

					printf ("Queried original master, IP = %s\n", NET_AdrToString(net_from));

					iOriginalMasterAlive = 2; // give it a chance to respond

					while(1)
					{
						NET_StringToAdr(MSG_ReadString(), &net_from);
						bAddServer(net_from, 1);
						if(MSG_ReadByte() != mst_server)
							break;
					}
				}
				else
				{
					// Client requesting server list
					if(strncmp(NET_AdrToString(net_from), "127.0.0.1", 9) == 0)
						break;

					if(strncmp(NET_AdrToString(net_from), "65.77.211.70", 12) == 0)
						break;
					
					if(strncmp(NET_AdrToString(net_from), "65.192.90.195", 13) == 0)
						break;

					if(iTrafficWarden > 64000)
					{
						// After sending 64k of data at 64kBps, we simply start randomly dropping packets to avoid DoS
						if(rand()%4 != 3)
						{
							printf("!");
							break;
						}
					}

					if(iTrafficWarden > 1000000)
					{
						// After 1Meg, drop everything
						printf("!");
						break;
					}

					printf("Client requesting server list, IP = %s\n", NET_AdrToString(net_from));

					SZ_Clear(&message);
					MSG_WriteLong(&message, LAUNCHER_CHALLENGE);
				
					vWriteServerData();

					MSG_WriteByte(&message, mst_end);
	
					iTrafficWarden += message.cursize;
					
					NET_SendPacket(message.cursize, message.data, net_from);
				}
			    break;
			default:
			    break;
			}
		}
		else
		{
			// Sleep for one second
			iTrafficWarden -= MIN(iTrafficWarden, 16000); // Limit all outgoing traffic to 64k stream to reduce chance of an induced DoS attacks
			Sleep(250);
			vAgeServers();
			LastMasterUpdate++;
			if(LastMasterUpdate >= (MAX_REMOTE_SERVERS_AGE/2))
			{
				printf("Contacting master\n");
				LastMasterUpdate = 0;
	
				//NET_StringToAdr(ORIGINAL_MASTER1, &net_from);
				//I_SetPort(net_from, MASTERPORT);
				//SZ_Clear(&message);
				//MSG_WriteLong(&message, LAUNCHER_CHALLENGE);
				//NET_SendPacket(message.cursize, message.data, net_from);

				NET_StringToAdr(ORIGINAL_MASTER2, &net_from);
				I_SetPort(net_from, MASTERPORT);
				SZ_Clear(&message);
				MSG_WriteLong(&message, LAUNCHER_CHALLENGE);
				NET_SendPacket(message.cursize, message.data, net_from);
				
				if(iOriginalMasterAlive > 0)
					iOriginalMasterAlive--;
			}
		}
	}

	CloseNetwork();
	
	return 0;
}
