/******
gserver.c
GameSpy C Engine SDK

Copyright 1999 GameSpy Industries, Inc

Suite E-204
2900 Bristol Street
Costa Mesa, CA 92626
(714)549-7689
Fax(714)549-0757

******

Updated 10-15-99 (BGW)
	Modified ServerParseKeyVals to actually parse and store empty
	values for keys (i.e. "\delete\\" adds key="delete" and value="")
Updated 6-17-99 (DDW)
	Added new tokenize function to handle empty values for keys

*******/
#include "nonport.h"
#include "goaceng.h"
#include "gserver.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>

static int KeyValHashKeyP(const void *elem, int numbuckets);
static int KeyValCompareKeyP(const void *entry1, const void *entry2);

static char *mytok(char *instr, char delim)
{
	char *result;
	static char *thestr;

	if (instr)
		thestr = instr;
	result=thestr;
	while (*thestr && *thestr != delim)
	{
		thestr++;
	}
	if (thestr == result)
		result = NULL;
	if (*thestr) //not the null term
		*thestr++ = '\0';
	return result;
}


static char *LookupKey(GServer server, const char *k)
{
	char **keyindex;
	char *cp;

	keyindex = (char **)TableLookup(server->keylist,&k);
	if (keyindex != NULL)
		return *keyindex;
	cp = strdup(k);
	TableEnter(server->keylist,&cp);
	return cp;
}

void ServerFree(void *elem)
{
	//free a server!
	GServer server = *(GServer *)elem;
	
	TableFree(server->keyvals);
	free(server);
}

static void ServerSetAddressFromString(GServer server, const char *address)
{
	char *cpos;
	cpos = strchr(address,':');
	if (cpos == NULL)
		return; //no : character
	*cpos = 0;
	server->ip = inet_addr(address);
	server->port = atoi(cpos+1);
}

GServer ServerNewData(char **fieldlist, int fieldcount, char *serverdata, GQueryType qtype, HashTable keylist)
{
	GServer server;
	const char *k, *v;
	int curfield = 0;
	GKeyValuePair kvpair;
	//first, create a server with an empty ip/port (we'll fill in later if we get those fields!)
	server = ServerNew(0, 0, qtype, keylist);
	//now fill in the data
	v = mytok(++serverdata,'\\'); //skip over starting backslash
	while (curfield < fieldcount)
	{
		k = fieldlist[curfield];
		if (v == NULL)
			v = "";
		if (strcmp(k, "ip")==0) //it's the ip:port string, parse it out
		{
			ServerSetAddressFromString(server, v);
		} else
		{
			kvpair.key = LookupKey(server, k);
			kvpair.value = LookupKey(server, v);
			TableEnter(server->keyvals, &kvpair);
		}
		curfield++;
		if (curfield < fieldcount)
			v = mytok(NULL,'\\');
	}
	return server;
}

GServer ServerNew(in_addr_t ip, unsigned short port, GQueryType qtype, HashTable keylist)
{
	GServer server;
	int nBuckets, nChains;

	server = (GServer)malloc(sizeof(struct GServerImplementation));
	server->ip = ip;
	server->port = port;
	server->ping = 9999;
	server->querytype = qtype;
	/* optimize the number of buckets / chains based on query type */
	switch (qtype)
	{
	case qt_basic:
		nBuckets = 4;
		nChains = 2;
		break;
	case qt_info:
		nBuckets = 6;
		nChains = 2;
		break;
	case qt_players:
	case qt_rules:
		nBuckets = 8;
		nChains = 2;
		break;
	case qt_info_rules:
	case qt_status:
	default:
		nBuckets = 8;
		nChains=  4;
		break;
	}
	server->keyvals = TableNew2(sizeof(GKeyValuePair),nBuckets,nChains, KeyValHashKeyP, KeyValCompareKeyP, NULL);
	server->keylist = keylist;
	return server;
}


static int CheckValidKey(const char *key)
{
	static const char *InvalidKeys[]={"queryid","final"};
	int i;
	for (i = 0; i < sizeof(InvalidKeys)/sizeof(InvalidKeys[0]); i++)
	{
		if (strcmp(key,InvalidKeys[i]) == 0)
			return 0;
	}
	return 1;
}

void ServerParseKeyVals(GServer server, char *keyvals)
{
	const char *k, *v;
	GKeyValuePair kvpair;

	k = mytok(++keyvals,'\\'); //skip over starting backslash
	while (k != NULL)
	{
		v = mytok(NULL,'\\');
		if (v == NULL)
			v = "";
		if (CheckValidKey(k))
		{
			kvpair.key = LookupKey(server, k);
			kvpair.value = LookupKey(server, v);
			TableEnter(server->keyvals, &kvpair);
		}
		k = mytok(NULL,'\\');
	}
}


/* ServerGetPing
----------------
Returns the ping for the specified server. */
int ServerGetPing(GServer server)
{
	return server->ping;
}

/* ServerGetAddress
-------------------
Returns the string, dotted IP address for the specified server */
char *ServerGetAddress(GServer server)
{
	return inet_ntoa(*(struct in_addr*)&server->ip);
}

/* ServerGetInetAddress
-------------------
Returns the IP address for the specified server */
in_addr_t ServerGetInetAddress(GServer server)
{
	return server->ip;
}

/* ServerGetPort
----------------
Returns the "query" port for the specified server. */
int ServerGetQueryPort(GServer server)
{
	return server->port;
}

static GKeyValuePair *ServerRuleLookup(GServer server, const char *key)
{
	GKeyValuePair kvp;
	char **keyindex;
	keyindex = (char **)TableLookup(server->keylist, &key);
	if (keyindex == NULL)
		return NULL; //otherwise, the keyindex->keyindex is valid, so use it to lookup
	kvp.key = *keyindex;
	return (GKeyValuePair *)TableLookup(server->keyvals, &kvp);
}

/* ServerGet[]Value
------------------
Returns the value for the specified key. */
const char *ServerGetStringValue(GServer server, const char *key, const char *sdefault)
{
	GKeyValuePair *kv;

	if (strcmp(key,"hostaddr") == 0) //ooh! they want the hostaddr!
		return ServerGetAddress(server);
	kv = ServerRuleLookup(server,key);
	if (!kv)
		return sdefault;
	return kv->value;
}

int ServerGetIntValue(GServer server, const char *key, int idefault)
{
	GKeyValuePair *kv;

	if (strcmp(key,"ping") == 0) //ooh! they want the ping!
		return ServerGetPing(server);
	kv = ServerRuleLookup(server,key);
	if (!kv)
		return idefault;
	return atoi(kv->value);
}

double ServerGetFloatValue(GServer server, const char *key, double fdefault)
{
	GKeyValuePair *kv;

	kv = ServerRuleLookup(server,key);
	if (!kv)
		return fdefault;
	return atof(kv->value);
}

const char *ServerGetPlayerStringValue(GServer server, int playernum, const char *key, const char *sdefault)
{
	char newkey[32];

	sprintf(newkey,"%s_%d",key,playernum);
	return ServerGetStringValue(server, newkey, sdefault);
}

int ServerGetPlayerIntValue(GServer server, int playernum, const char *key, int idefault)
{
	char newkey[32];
	
	sprintf(newkey,"%s_%d",key,playernum);
	return ServerGetIntValue(server, newkey, idefault);
}

double ServerGetPlayerFloatValue(GServer server, int playernum, const char *key, double fdefault)
{
	char newkey[32];

	sprintf(newkey,"%s_%d",key,playernum);
	return ServerGetFloatValue(server, newkey, fdefault);
}


/* ServerEnumKeys
-----------------
Enumerates the keys/values for a given server by calling KeyEnumFn with each
key/value. The user-defined instance data will be passed to the KeyFn callback */

static void KeyMapF(void *elem, void *clientData)
{
	GKeyValuePair *kv = (GKeyValuePair *)elem;
	GEnumData *ped = (GEnumData *)clientData;

	ped->EnumFn(kv->key, kv->value, ped->instance);
}


void ServerEnumKeys(GServer server, KeyEnumFn KeyFn, void *instance)
{
	GEnumData ed;

	ed.EnumFn = KeyFn;
	ed.instance = instance;
	ed.keylist = server->keylist;
	TableMap(server->keyvals, KeyMapF, &ed);
}


/***********
 * UTILITY FUNCTIONS
 **********/
#define MULTIPLIER -1664117991
static int StringHash(const char *s, int numbuckets)
{
	unsigned long hashcode = 0;
	while (*s != 0)
	{
		hashcode = hashcode * MULTIPLIER + tolower(*s);
		s++;
	}
	return (hashcode % numbuckets);
}

int GStringHash(const void *elem, int numbuckets)
{
	return StringHash(*(const char **)elem, numbuckets);
}

static int KeyValHashKeyP(const void *elem, int numbuckets)
{
	return StringHash(((GKeyValuePair *)elem)->key, numbuckets);
}


/* CaseInsensitiveCompare
 * ----------------------
 * Comparison function passed to qsort to sort an array of
 * strings in alphabetical order. It uses strcasecmp which is
 * identical to strcmp, except that it doesn't consider case of the
 * characters when comparing them, thus it sorts case-insensitively.
 */
int GCaseInsensitiveCompare(const void *entry1, const void *entry2)
{
	return strcasecmp(*(const char **)entry1,*(const char **)entry2);
}

/* keyval
 * Compares two gkeyvaluepair 
 */
static int KeyValCompareKeyP(const void *entry1, const void *entry2)
{
	return ((GKeyValuePair *)entry1)->key - ((GKeyValuePair *)entry2)->key;
}

void GStringFree(void *elem)
{
	free(*(char **)elem);
}

/* keyval
 * Compares two gkeyvaluepair  (case insensative)
 *
static int KeyValCompareKeyA(const void *entry1, const void *entry2)
{
	return CaseInsensitiveCompare(&((GKeyValuePair *)entry1)->key, 
								  &((GKeyValuePair *)entry2)->key);
}
*/
