/*
gpiProfile.c
GameSpy Presence SDK 
Dan "Mr. Pants" Schoenblum

Copyright 1999-2007 GameSpy Industries, Inc

devsupport@gamespy.com

***********************************************************************
Please see the GameSpy Presence SDK documentation for more information
**********************************************************************/

//INCLUDES
//////////
#include "gpi.h"

//DEFINES
/////////
#define GPI_PROFILE_GROW_SIZE        16
#define GPI_PROFILE_CACHE_VERSION    2

// GLOBALS
//////////
static char GPIInfoCacheFilename[FILENAME_MAX + 1] = "gp.info";

//FUNCTIONS
///////////
static int
gpiProfilesTableHash(
  const void *arg,
  int numBuckets
)
{
	const GPIProfile * profile = (const GPIProfile *)arg;
	return (profile->profileId % numBuckets);
}

static int
gpiProfilesTableCompare(
  const void * arg1,
  const void * arg2
)
{
  const GPIProfile * profile1 = (const GPIProfile *)arg1;
  const GPIProfile * profile2 = (const GPIProfile *)arg2;
	return (profile1->profileId - profile2->profileId);
}

static void
gpiProfilesTableFree(
  void *arg
)
{
	GPIProfile * profile = (GPIProfile *)arg;
	if(profile->buddyStatus)
	{
		freeclear(profile->buddyStatus->statusString);
		freeclear(profile->buddyStatus->locationString);
		freeclear(profile->buddyStatus);
	}
	if (profile->buddyStatusInfo)
	{
		freeclear(profile->buddyStatusInfo->richStatus);
		freeclear(profile->buddyStatusInfo->gameType);
		freeclear(profile->buddyStatusInfo->gameVariant);
		freeclear(profile->buddyStatusInfo->gameMapName);
		if (profile->buddyStatusInfo->extendedInfoKeys)
		{
			ArrayFree(profile->buddyStatusInfo->extendedInfoKeys);
			profile->buddyStatusInfo->extendedInfoKeys = NULL;
		}
		freeclear(profile->buddyStatusInfo);
	}
	gpiFreeInfoCache(profile);
	freeclear(profile->authSig);
	freeclear(profile->peerSig);
}

GPIBool
gpiInitProfiles(
  GPConnection * connection
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;

	iconnection->profileList.numBuddies = 0;
    iconnection->profileList.numBlocked = 0;
	iconnection->profileList.num = 0;
	iconnection->profileList.profileTable = TableNew(
		sizeof(GPIProfile),
		32,
		gpiProfilesTableHash,
		gpiProfilesTableCompare,
		gpiProfilesTableFree);
	if(!iconnection->profileList.profileTable)
		return GPIFalse;

	return GPITrue;
}

#ifndef NOFILE

static GPResult
gpiOpenDiskProfiles(
  GPConnection * connection,
  GPIBool write,
  GPIBool * failed
)
{
	FILE * fp = NULL;
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// Open the file.
	/////////////////
	if(write)
		fp = fopen(GPIInfoCacheFilename, "wt");
	else
		fp = fopen(GPIInfoCacheFilename, "rt");
	if(fp == NULL)
	{
		*failed = GPITrue;
		return GP_NO_ERROR;
	}

	// Excellent.
	/////////////
	iconnection->diskCache = fp;
	*failed = GPIFalse;

	return GP_NO_ERROR;
	
	GSI_UNUSED(write);
}

static void
gpiCloseDiskProfiles(
  GPConnection * connection
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// Close the file.
	//////////////////
	fclose(iconnection->diskCache);
	iconnection->diskCache = NULL;

	return;
}

static GPResult
gpiReadDiskKeyValue(
  GPConnection * connection,
  GPIBool * failed,
  char key[512],
  char value[512]
)
{
	int c;
	FILE * fp;
	GPIConnection * iconnection = (GPIConnection*)*connection;
	int i;

	// Grab the file pointer.
	/////////////////////////
	fp = iconnection->diskCache;

	// Read the key.
	////////////////
	i = 0;
	do
	{
		if(i == 512)
		{
			*failed = GPITrue;
			return GP_NO_ERROR;
		}
		c = fgetc(fp);
		if((c == EOF) || (c == '\n'))
		{
			*failed = GPITrue;
			return GP_NO_ERROR;
		}
		key[i++] = (char)c;
	}
	while(c != '=');
	key[--i] = '\0';

	// Check for no key.
	////////////////////
	if(i == 0)
	{
		*failed = GPITrue;
		return GP_NO_ERROR;
	}

	// Read the value.
	//////////////////
	i = 0;
	do
	{
		if(i == 512)
		{
			*failed = GPITrue;
			return GP_NO_ERROR;
		}
		c = fgetc(fp);
		if(c == EOF)
		{
			c = '\n';
		}
		value[i++] = (char)c;
	}
	while(c != '\n');
	value[--i] = '\0';

	// Done.
	////////
	*failed = GPIFalse;
	return GP_NO_ERROR;
	
	GSI_UNUSED(value);
	GSI_UNUSED(key);
	GSI_UNUSED(connection);
}

static GPResult
gpiReadDiskProfile(
  GPConnection * connection,
  GPIBool * failedOut
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	FILE * fp;
	GPIProfile profile;
	int c;
	int rcode;
	GPIBool failed;
	char key[256];
	char value[256];
	GPIInfoCache infoCache;
	GPIBool valid = GPIFalse;
	GPIProfile * pProfile;
	char nick[GP_NICK_LEN];
	char uniquenick[GP_UNIQUENICK_LEN];
	char email[GP_EMAIL_LEN];
	char firstname[GP_FIRSTNAME_LEN];
	char lastname[GP_LASTNAME_LEN];
	char homepage[GP_HOMEPAGE_LEN];
	char aimname[GP_AIMNAME_LEN];

	// Grab the file pointer.
	/////////////////////////
	fp = iconnection->diskCache;

	// Clear the temp profile.
	//////////////////////////
	memset(&profile, 0, sizeof(GPIProfile));

	// Clear the temp cache.
	////////////////////////
	memset(&infoCache, 0, sizeof(GPIInfoCache));
	infoCache.nick = nick;
	infoCache.uniquenick = uniquenick;
	infoCache.email = email;
	infoCache.firstname = firstname;
	infoCache.lastname = lastname;
	infoCache.homepage = homepage;
	infoCache.aimname = aimname;
	nick[0] = '\0';
	uniquenick[0] = '\0';
	email[0] = '\0';
	firstname[0] = '\0';
	lastname[0] = '\0';
	homepage[0] = '\0';
	aimname[0] = '\0';

	// Read until we hit a [.
	/////////////////////////
	do
	{
		c = fgetc(fp);
		if(c == EOF)
		{
			*failedOut = GPITrue;
			return GP_NO_ERROR;
		}
	}
	while(c != '[');

	// Grab the profileid.
	//////////////////////
	rcode = fscanf(fp, "%d]\n", &profile.profileId);
	if(rcode != 1)
	{
		*failedOut = GPITrue;
		return GP_NO_ERROR;
	}

	gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_Comment,
		"Reading profile %d from disk cache:\n", profile.profileId);

	// Read key/value pairs.
	////////////////////////
	do
	{
		CHECK_RESULT(gpiReadDiskKeyValue(connection, &failed, key, value));
		if(!failed)
		{
			gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_Comment,
				"%d: %s=%s\n", profile.profileId, key, value);

			// Set the data based on the key.
			/////////////////////////////////
			if(strcmp(key, "userid") == 0)
			{
				profile.userId = atoi(value);
			}
			else if(strcmp(key, "nick") == 0)
			{
				strzcpy(infoCache.nick, value, GP_NICK_LEN);
			}
			else if(strcmp(key, "uniquenick") == 0)
			{
				strzcpy(infoCache.uniquenick, value, GP_UNIQUENICK_LEN);
			}
			else if(strcmp(key, "email") == 0)
			{
				strzcpy(infoCache.email, value, GP_EMAIL_LEN);
			}
			else if(strcmp(key, "firstname") == 0)
			{
				strzcpy(infoCache.firstname, value, GP_FIRSTNAME_LEN);
			}
			else if(strcmp(key, "lastname") == 0)
			{
				strzcpy(infoCache.lastname, value, GP_LASTNAME_LEN);
			}
			else if(strcmp(key, "homepage") == 0)
			{
				strzcpy(infoCache.homepage, value, GP_HOMEPAGE_LEN);
			}
			else if(strcmp(key, "icquin") == 0)
			{
				infoCache.icquin = atoi(value);
			}
			else if(strcmp(key, "zipcode") == 0)
			{
				strzcpy(infoCache.zipcode, value, GP_ZIPCODE_LEN);
			}
			else if(strcmp(key, "countrycode") == 0)
			{
				strzcpy(infoCache.countrycode, value, GP_COUNTRYCODE_LEN);
			}
			else if(strcmp(key, "birthday") == 0)
			{
				infoCache.birthday = atoi(value);
			}
			else if(strcmp(key, "birthmonth") == 0)
			{
				infoCache.birthmonth = atoi(value);
			}
			else if(strcmp(key, "birthyear") == 0)
			{
				infoCache.birthyear = atoi(value);
			}
			else if(strcmp(key, "sex") == 0)
			{
				if(toupper(value[0]) == 'M')
					infoCache.sex = GP_MALE;
				else if(toupper(value[1] == 'F'))
					infoCache.sex = GP_FEMALE;
				else
					infoCache.sex = GP_PAT;
			}
			else if(strcmp(key, "publicmask") == 0)
			{
				infoCache.publicmask = atoi(value);
			}
			else if(strcmp(key, "aimname") == 0)
			{
				strzcpy(infoCache.aimname, value, GP_AIMNAME_LEN);
			}
			else if(strcmp(key, "pic") == 0)
			{
				infoCache.pic = atoi(value);
			}
			else if(strcmp(key, "occupationid") == 0)
			{
				infoCache.occupationid = atoi(value);
			}
			else if(strcmp(key, "industryid") == 0)
			{
				infoCache.industryid = atoi(value);
			}
			else if(strcmp(key, "incomeid") == 0)
			{
				infoCache.incomeid = atoi(value);
			}
			else if(strcmp(key, "marriedid") == 0)
			{
				infoCache.marriedid = atoi(value);
			}
			else if(strcmp(key, "childcount") == 0)
			{
				infoCache.childcount = atoi(value);
			}
			else if(strcmp(key, "interests1") == 0)
			{
				infoCache.interests1 = atoi(value);
			}
			else if(strcmp(key, "ownership1") == 0)
			{
				infoCache.ownership1 = atoi(value);
			}
			else if(strcmp(key, "conntypeid") == 0)
			{
				infoCache.conntypeid = atoi(value);
			}
			else if(strcmp(key, "valid") == 0)
			{
				valid = (GPIBool)atoi(value);
			}
			else
			{
				gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_HotError,
					"Unrecognized profile key: %s=%s\n", key, value);
			}
		}
	}
	while(!failed);

	// Create a new profile.
	////////////////////////
	pProfile = gpiProfileListAdd(connection, profile.profileId);
	if(pProfile)
	{
		// Copy the profile we've set up into the list.
		///////////////////////////////////////////////
		*pProfile = profile;

		// Copy the info if valid.
		//////////////////////////
		if(valid)
			gpiSetInfoCache(connection, pProfile, &infoCache);
	}
	*failedOut = GPIFalse;
	return GP_NO_ERROR;
	
	GSI_UNUSED(connection);
}

static GPResult
gpiReadVersion(
  const GPConnection * connection,
  int * version
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	FILE * fp;

	// Grab the file pointer.
	/////////////////////////
	fp = iconnection->diskCache;

	// Read the version.
	////////////////////
	if(fscanf(fp, "%d\n", version) != 1)
		*version = 0;

	return GP_NO_ERROR;
	
	GSI_UNUSED(connection);
	GSI_UNUSED(version);
}

static void
gpiWriteVersion(
  GPConnection * connection,
  int version
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	FILE * fp;

	// Grab the file pointer.
	/////////////////////////
	fp = iconnection->diskCache;

	// Write the version.
	/////////////////////
	fprintf(fp, "%d\n", version);

	GSI_UNUSED(connection);
	GSI_UNUSED(version);
}

GPResult
gpiLoadDiskProfiles(
  GPConnection * connection
)
{
	GPIBool failed;
	int count;
	int version = 0;

	// Open the disk cache.
	///////////////////////
	CHECK_RESULT(gpiOpenDiskProfiles(connection, GPIFalse, &failed));
	if(failed)
	{
		gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_HotError,
			"Failed to open the disk cache file.\n");
		return GP_NO_ERROR;
	}

	// Check the version.
	//////////////////////
	CHECK_RESULT(gpiReadVersion(connection, &version));
	if(version == GPI_PROFILE_CACHE_VERSION)
	{
		// Read profiles.
		/////////////////
		count = 0;
		do
		{
			CHECK_RESULT(gpiReadDiskProfile(connection, &failed));
			if(!failed)
				count++;
		}
		while(!failed);
		gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_Comment,
			"Loaded %d profiles from disk cache.\n", count);
	}

	// Close the cache.
	///////////////////
	gpiCloseDiskProfiles(connection);

	return GP_NO_ERROR;
}

static GPIBool
gpiSaveDiskProfile(
  GPConnection * connection,
  GPIProfile * profile,
  void * data
)
{
	FILE * fp;
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// Grab the file pointer.
	/////////////////////////
	fp = iconnection->diskCache;

	// Write the profile id.
	////////////////////////
	fprintf(fp, "[%d]\n", profile->profileId);

	// Write the userid if not 0.
	/////////////////////////////
	if(profile->userId != 0)
		fprintf(fp, "userid=%d\n", profile->userId);

	// Is the cache valid?
	//////////////////////
	if(profile->cache)
	{
		fprintf(fp, "valid=1\n");

		fprintf(fp, "nick=%s\n", profile->cache->nick);
		fprintf(fp, "uniquenick=%s\n", profile->cache->uniquenick);
		fprintf(fp, "email=%s\n", profile->cache->email);
		fprintf(fp, "firstname=%s\n", profile->cache->firstname);
		fprintf(fp, "lastname=%s\n", profile->cache->lastname);
		fprintf(fp, "homepage=%s\n", profile->cache->homepage);
		fprintf(fp, "icquin=%d\n", profile->cache->icquin);
		fprintf(fp, "zipcode=%s\n", profile->cache->zipcode);
		fprintf(fp, "countrycode=%s\n", profile->cache->countrycode);
		fprintf(fp, "birthday=%d\n", profile->cache->birthday);
		fprintf(fp, "birthmonth=%d\n", profile->cache->birthmonth);
		fprintf(fp, "birthyear=%d\n", profile->cache->birthyear);
		if(profile->cache->sex == GP_MALE)
			fprintf(fp, "sex=Male\n");
		if(profile->cache->sex == GP_FEMALE)
			fprintf(fp, "sex=Female\n");
		if(profile->cache->sex == GP_PAT)
			fprintf(fp, "sex=Pat\n");
		fprintf(fp, "publicmask=%d\n", profile->cache->publicmask);
		fprintf(fp, "aimname=%s\n", profile->cache->aimname);
		fprintf(fp, "pic=%d\n", profile->cache->pic);
		fprintf(fp, "occupationid=%d\n", profile->cache->occupationid);
		fprintf(fp, "industryid=%d\n", profile->cache->industryid);
		fprintf(fp, "incomeid=%d\n", profile->cache->incomeid);
		fprintf(fp, "marriedid=%d\n", profile->cache->marriedid);
		fprintf(fp, "childcount=%d\n", profile->cache->childcount);
		fprintf(fp, "interests1=%d\n", profile->cache->interests1);
		fprintf(fp, "ownership1=%d\n", profile->cache->ownership1);
		fprintf(fp, "conntypeid=%d\n", profile->cache->conntypeid);
	}

	// End this profile.
	////////////////////
	fprintf(fp, "\n");

	GSI_UNUSED(data);
	GSI_UNUSED(connection);
	GSI_UNUSED(profile);

	return GPITrue;
}

GPResult
gpiSaveDiskProfiles(
  GPConnection * connection
)
{
	GPIBool failed;

	// Open the disk cache.
	///////////////////////
	CHECK_RESULT(gpiOpenDiskProfiles(connection, GPITrue, &failed));
	if(failed)
	{
		gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_HotError,
			"Failed to open the disk cache file.\n");
		return GP_NO_ERROR;
	}

	// Write the version.
	/////////////////////
	gpiWriteVersion(connection, GPI_PROFILE_CACHE_VERSION);

	// Save profiles.
	/////////////////
	gpiProfileMap(connection, gpiSaveDiskProfile, NULL);

	// Close the cache.
	///////////////////
	gpiCloseDiskProfiles(connection);

	return GP_NO_ERROR;
}

#endif

GPResult
gpiProcessNewProfile(
  GPConnection * connection,
  GPIOperation * operation,
  const char * input
)
{
	char buffer[16];
	int pid;
	GPICallback callback;

	// Check for an error.
	//////////////////////
	if(gpiCheckForError(connection, input, GPITrue))
		return GP_SERVER_ERROR;

	// This should be \npr\.
	////////////////////////
	if(strncmp(input, "\\npr\\", 5) != 0)
		CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

	// Get the profile id.
	//////////////////////
	if(!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer)))
		CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	pid = atoi(buffer);

	// Call the callback.
	/////////////////////
	callback = operation->callback;
	if(callback.callback != NULL)
	{
		GPNewProfileResponseArg * arg;
		arg = (GPNewProfileResponseArg *)gsimalloc(sizeof(GPNewProfileResponseArg));
		if(arg == NULL)
			Error(connection, GP_MEMORY_ERROR, "Out of memory.");
		
		arg->profile = (GPProfile)pid;
		arg->result = GP_NO_ERROR;

		CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0));
	}

	// Remove the operation.
	////////////////////////
	gpiRemoveOperation(connection, operation);

	return GP_NO_ERROR;
}

GPIProfile *
gpiProfileListAdd(
  GPConnection * connection,
  int id
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPIProfileList * profileList = &iconnection->profileList;
	GPIProfile profile;
	GPIProfile * pProfile;

	assert(id > 0);

	// Check the parameters. 2000.02.14.JED was not checked in release build.
	/////////////////////////////////////////////////////////////////////////
	if(id <= 0)
		return NULL;

	// Check if this id is already in the list.
	///////////////////////////////////////////
	if(gpiGetProfile(connection, (GPProfile)id, &pProfile))
		return pProfile;

	// Setup the new profile.
	/////////////////////////
	memset(&profile, 0, sizeof(GPIProfile));
	profile.profileId = id;
	profile.userId = 0;
	profile.cache = NULL;
	profile.authSig = NULL;
	profile.peerSig = NULL;
	profile.requestCount = 0;

	// Add it to the table.
	///////////////////////
	TableEnter(profileList->profileTable, &profile);

	// One new one.
	///////////////
	profileList->num++;

	// Get a pointer to the profile.
	////////////////////////////////
	if(gpiGetProfile(connection, (GPProfile)id, &pProfile))
		return pProfile;

	// It wasn't added.
	///////////////////
	return NULL;
}

GPIBool
gpiGetProfile(
  GPConnection * connection,
  GPProfile profileid,
  GPIProfile ** pProfile
)
{
	GPIProfile * profile;
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPIProfile profileTemp;

	profileTemp.profileId = profileid;
	profile = (GPIProfile *)TableLookup(iconnection->profileList.profileTable, &profileTemp);
	if(pProfile)
		*pProfile = profile;

	return ((profile != NULL) ? GPITrue:GPIFalse);
}

GPResult
gpiNewProfile(
  GPConnection * connection,
  const char nick[31],
  GPEnum replace,
  GPEnum blocking,
  GPCallback callback,
  void * param
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPIOperation * operation;
	GPResult result;
	char buffer[31];

	// Error check.
	///////////////
	if(nick == NULL)
		Error(connection, GP_PARAMETER_ERROR, "Invalid nick.");
	if((replace != GP_REPLACE) && (replace != GP_DONT_REPLACE))
		Error(connection, GP_PARAMETER_ERROR, "Invalid replace.");

	// Create a new operation.
	//////////////////////////
	CHECK_RESULT(gpiAddOperation(connection, GPI_NEW_PROFILE, NULL, &operation, blocking, callback, param));

	// Send the request.
	////////////////////
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\newprofile\\");
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\nick\\");
	strzcpy(buffer, nick, GP_NICK_LEN);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, buffer);
	if(replace == GP_REPLACE)
	{
		gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\replace\\");
		gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, 1);
		gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\oldnick\\");
		gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->nick);
	}
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operation->id);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");
/*
	if(string.result != GP_NO_ERROR)
	{
		gpiRemoveOperation(connection, operation);
		return string.result;
	}
*/
	// Process it if blocking.
	//////////////////////////
	if(operation->blocking)
	{
		result = gpiProcess(connection, operation->id);
		if(result != GP_NO_ERROR)
		{
			gpiRemoveOperation(connection, operation);
			return result;
		}
	}

	return GP_NO_ERROR;
}

GPResult gpiProcessDeleteProfle
(
  GPConnection * connection,
  GPIOperation * operation,
  const char * input
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPICallback callback;

	// Check for an error.
	//////////////////////
	if(gpiCheckForError(connection, input, GPITrue))
		return GP_SERVER_ERROR;

	// This should be \dpr\.
	////////////////////////
	if(strncmp(input, "\\dpr\\", 5) != 0)
		CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

	
	// Call the callback.
	/////////////////////
	callback = operation->callback;
	if(callback.callback != NULL)
	{
		GPDeleteProfileResponseArg * arg;
		arg = (GPDeleteProfileResponseArg *)gsimalloc(sizeof(GPDeleteProfileResponseArg));
		if(arg == NULL)
			Error(connection, GP_MEMORY_ERROR, "Out of memory.");
		
		arg->profile = iconnection->profileid;
		arg->result = GP_NO_ERROR;

		CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0));
	}

	// Remove the operation.
	////////////////////////
	gpiRemoveOperation(connection, operation);

	return GP_NO_ERROR;
}


GPResult
gpiDeleteProfile(
  GPConnection * connection,
  GPCallback callback,
  void *param
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPIOperation * operation;
	GPResult result;

	CHECK_RESULT(gpiAddOperation(connection, GPI_DELETE_PROFILE, NULL, &operation, GP_BLOCKING, callback, param));
	// Send the message.
	////////////////////
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\delprofile\\");
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operation->id);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");

	// Remove the profile object.
	/////////////////////////////
	gpiRemoveProfileByID(connection, iconnection->profileid);

	// Disconnect the connection.
	// PANTS|05.16.00
	/////////////////////////////
	iconnection->connectState = GPI_PROFILE_DELETING;	
	result = gpiProcess(connection, operation->id);
	if (result != GP_NO_ERROR)
	{
		gpiRemoveOperation(connection, operation);
		return result;
	}

	gpiDisconnect(connection, GPIFalse);
	return GP_NO_ERROR;
}

void
gpiRemoveProfileByID(
  GPConnection * connection,
  int profileid
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPIProfile * profile;

	if(gpiGetProfile(connection, (GPProfile)profileid, &profile))
		TableRemove(iconnection->profileList.profileTable, profile);
}

void
gpiRemoveProfile(
  GPConnection * connection,
  GPIProfile * profile
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;

	TableRemove(iconnection->profileList.profileTable, profile);
}

typedef struct GPIFindProfileByUserData
{
	char * nick;
	char * email;
	GPIProfile ** profile;
	GPIBool found;
} GPIFindProfileByUserData;

static GPIBool gpiCheckProfileForUser(
  GPConnection * connection,
  GPIProfile * profile,
  void *udata
)
{
	GPIFindProfileByUserData * data = (GPIFindProfileByUserData *)udata;

	GSI_UNUSED(connection);

	// Check for a valid cache.
	///////////////////////////
	if(profile->cache)
	{
		// Check the nick and email.
		////////////////////////////
		if((strcmp(data->nick, profile->cache->nick) == 0) && (strcmp(data->email, profile->cache->email) == 0))
		{
			// Found it.
			////////////
			*data->profile = profile;
			data->found = GPITrue;
			return GPIFalse;
		}
	}

	return GPITrue;
}

GPResult
gpiFindProfileByUser(
  GPConnection * connection,
  char nick[GP_NICK_LEN],
  char email[GP_EMAIL_LEN],
  GPIProfile ** profile
)
{
	GPIFindProfileByUserData data;

	data.nick = nick;
	data.email = email;
	data.profile = profile;
	data.found = GPIFalse;

	gpiProfileMap(connection, gpiCheckProfileForUser, &data);

	if(!data.found)
		*profile = NULL;

	return GP_NO_ERROR;
}

typedef struct GPIProfileMapData
{
	GPConnection * connection;
	gpiProfileMapFunc func;
	void * data;
} GPIProfileMapData;

static int
gpiProfileMapCallback(
  void *arg,
  void *udata
)
{
	GPIProfile * profile = (GPIProfile *)arg;
	GPIProfileMapData * data = (GPIProfileMapData *)udata;
	return (int)data->func(data->connection, profile, data->data);
}

GPIBool
gpiProfileMap(
  GPConnection * connection,
  gpiProfileMapFunc func,
  void * data
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPIProfileMapData mapData;

	mapData.connection = connection;
	mapData.func = func;
	mapData.data = data;

	return (!TableMapSafe2(iconnection->profileList.profileTable, gpiProfileMapCallback, &mapData)) ? GPITrue:GPIFalse;
}

typedef struct GPIFindProfileData
{
	int index;
	GPIProfile * profile;
} GPIFindProfileData;

static GPIBool
gpiCheckForBuddy(
  GPConnection * connection,
  GPIProfile * profile,
  void *udata
)
{
	GPIFindProfileData * data = (GPIFindProfileData *)udata;
	if (profile->buddyStatus && (data->index == profile->buddyStatus->buddyIndex))
	{
		data->profile = profile;
		return GPIFalse;
	}
	else if (profile->buddyStatusInfo && (data->index == profile->buddyStatusInfo->buddyIndex))
	{
		data->profile = profile;
		return GPIFalse;
	}

	GSI_UNUSED(connection);

	return GPITrue;
}

GPIProfile *
gpiFindBuddy(
  GPConnection * connection,
  int buddyIndex
)
{
	GPIFindProfileData data;

	data.index = buddyIndex;
	data.profile = NULL;

	gpiProfileMap(connection, gpiCheckForBuddy, &data);

	return data.profile;
}

void gpiRemoveBuddyStatus(GPIBuddyStatus *buddyStatus)
{
	GS_ASSERT(buddyStatus);
	
	freeclear(buddyStatus->locationString);
	freeclear(buddyStatus->statusString);
	freeclear(buddyStatus);
}

void gpiRemoveBuddyStatusInfo(GPIBuddyStatusInfo *buddyStatusInfo)
{
	GS_ASSERT(buddyStatusInfo);
		
	freeclear(buddyStatusInfo->richStatus);
	freeclear(buddyStatusInfo->gameType);
	freeclear(buddyStatusInfo->gameVariant);
	freeclear(buddyStatusInfo->gameMapName);
	ArrayFree(buddyStatusInfo->extendedInfoKeys);
	buddyStatusInfo->extendedInfoKeys = NULL;
	freeclear(buddyStatusInfo);
}

GPIBool
gpiCanFreeProfile(
  GPIProfile * profile
)
{
	return ((profile && !profile->cache && !profile->buddyStatus && !profile->buddyStatusInfo && !profile->peerSig && !profile->authSig && !profile->blocked)) ? GPITrue:GPIFalse;
}

void gpiSetInfoCacheFilename(
  const char filename[FILENAME_MAX + 1]
)
{
	strzcpy(GPIInfoCacheFilename, filename, sizeof(GPIInfoCacheFilename));
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Blocked List functionality
GPResult
gpiAddToBlockedList(
  GPConnection * connection,
  int profileid
)
{
    GPIConnection * iconnection = (GPIConnection*)*connection;
    GPIProfile * profile;
    int index;

    // Check if profile already in list
    ///////////////////////////////////
    if(!gpiGetProfile(connection, profileid, &profile))
    {
        // It's not, so Add profile
        /////////////////////////////////////
        profile = gpiProfileListAdd(connection, profileid);
        if(!profile)
            Error(connection, GP_MEMORY_ERROR, "Out of memory.");
    }
    else
    {
        // Already in the list, nix the buddy status if it's a buddy
        ////////////////////////////////////////////////////////////
        if (profile->buddyStatus)
        {
            index = profile->buddyStatus->buddyIndex;
            freeclear(profile->buddyStatus->statusString);
            freeclear(profile->buddyStatus->locationString);
            freeclear(profile->buddyStatus);
            iconnection->profileList.numBuddies--;
            assert(iconnection->profileList.numBuddies >= 0);
#ifndef _PS2
            gpiProfileMap(connection, gpiFixBuddyIndices, (void *)(unsigned long)index);
#else
            gpiProfileMap(connection, gpiFixBuddyIndices, (void *)index);
#endif
        }
        if (profile->buddyStatusInfo)
        {
            index = profile->buddyStatusInfo->buddyIndex;
            freeclear(profile->buddyStatusInfo->richStatus);
            freeclear(profile->buddyStatusInfo->gameType);
            freeclear(profile->buddyStatusInfo->gameVariant);
            freeclear(profile->buddyStatusInfo->gameMapName);
            freeclear(profile->buddyStatusInfo);
            if (profile->buddyStatusInfo->extendedInfoKeys)
            {
                ArrayFree(profile->buddyStatusInfo->extendedInfoKeys);
                profile->buddyStatusInfo->extendedInfoKeys = NULL;
            }

            iconnection->profileList.numBuddies--;
            assert(iconnection->profileList.numBuddies >= 0);
#ifndef _PS2
            gpiProfileMap(connection, gpiFixBuddyIndices, (void *)(unsigned long)index);
#else
            gpiProfileMap(connection, gpiFixBuddyIndices, (void *)index);
#endif
        }
    }

    // Set profile as blocked if it wasn't already
    //////////////////////////////////////////////
    if (!profile->blocked)
    {
        profile->blocked = gsi_true;
        profile->blockIndex = iconnection->profileList.numBlocked++;

#ifdef _PS3
        // Only perform if profile isn't already locally blocked and the sync isnt taking place
        ///////////////////////////////////////////////////////////////////////////////////////
        if (!iconnection->npSyncLock)
            gpiAddToNpBlockList(connection, profileid);
#endif
    }

    // NOTE: There is no callback for this function simply in order to remain consistent
    // with the existing GP structure. If an error occurs, it gets passed to the error callback
    // else its considered the request was processed correctly.

    // Add to outgoing buffer - have server handle error check if block already exists (consistency)
    /////////////////////////////////////////////////////////////////////////////////////////////////
    gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\addblock\\\\sesskey\\");
    gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey);
    gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\");
    gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid);
    gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");

    return GP_NO_ERROR;
}

static GPIBool
gpiFixBlockIndices(
  GPConnection * connection,
  GPIProfile * profile,
  void * data
)
{
#ifndef _PS2
    int baseIndex = (int)(unsigned long)data;
#else
    int baseIndex = (int)data;
#endif

    GSI_UNUSED(connection);

    if(profile->blocked && (profile->blockIndex > baseIndex))
        profile->blockIndex--;
    return GPITrue;
}

GPResult
gpiRemoveFromBlockedList(
  GPConnection * connection,
  int profileid
)
{
    GPIConnection * iconnection = (GPIConnection*)*connection;
    GPIProfile * profile;
    int index;

    // Grab the profile if already in list
    //////////////////////////////////////
    if(gpiGetProfile(connection, profileid, &profile) && profile->blocked)
    {
        // Set profile as non-blocked 
        /////////////////////////////
        profile->blocked = gsi_false;

        iconnection->profileList.numBlocked--;
        index = profile->blockIndex;

#ifndef _PS2
        gpiProfileMap(connection, gpiFixBlockIndices, (void *)(unsigned long)index);
#else
        gpiProfileMap(connection, gpiFixBlockIndices, (void *)index);
#endif
    }
   
    // NOTE: There is no callback for this function simply in order to remain consistent
    // with the existing GP structure. If an error occurs, it gets passed to the error callback
    // else its considered the request was processed correctly.

    // Add to outgoing buffer - have server handle error check if block already removed (consistency)
    /////////////////////////////////////////////////////////////////////////////////////////////////
    gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\removeblock\\\\sesskey\\");
    gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey);
    gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\");
    gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid);
    gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");

    return GP_NO_ERROR;
}

static GPIBool
gpiCheckForBlock(
  GPConnection * connection,
  GPIProfile * profile,
  void *udata
)
{
    GPIFindProfileData * data = (GPIFindProfileData *)udata;
    if (profile->blocked && data->index == profile->blockIndex)
    {
        data->profile = profile;
        return GPIFalse;
    }

    GSI_UNUSED(connection);

    return GPITrue;
}

GPIProfile *
gpiFindBlockedProfile(
  GPConnection * connection,
  int blockIndex
)
{
    GPIFindProfileData data;

    data.index = blockIndex;
    data.profile = NULL;

    gpiProfileMap(connection, gpiCheckForBlock, &data);

    return data.profile;
}

GPResult
gpiProcessRecvBlockedList(
  GPConnection * connection,
  const char * input
)
{
    int i=0, j=0;
    int num = 0;
    int index = 0;
    char c;
    char *str = NULL;
    char buffer[512];
    GPIProfile * profile;
    GPProfile profileid;
    GPIConnection * iconnection = (GPIConnection*)*connection;

    // Check for an error.
    //////////////////////
    if(gpiCheckForError(connection, input, GPITrue))
        return GP_SERVER_ERROR;

    // Process Block List Retrieval msg - Format like:
    /* ===============================================
       \blk\<num in list>\list\<block list - comma delimited>\final\
       =============================================== */

    if(!gpiValueForKeyWithIndex(input, "\\blk\\", &index, buffer, sizeof(buffer)))
        CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
    num = atoi(buffer);

    // Check to make sure list is there 
    ///////////////////////////////////
	str = strstr(input, "\\list\\");
    if (str == NULL)
        CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
    
    // Then increment index to get ready for parsing
    ////////////////////////////////////////////////
    str += 6;
    index += 6;

    for (i=0; i < num; i++)
    {     
        if (i==0)
        {
            // Manually grab first profile in list - comma delimiter
            ////////////////////////////////////////////////////////
            for(j=0 ; (j < sizeof(buffer)) && ((c = str[j]) != '\0') && (c != ',') ; j++)
            {
                buffer[j] = c;
            }
            buffer[j] = '\0';
            index += j;
        }
        else
        {
            if(!gpiValueForKeyWithIndex(input, ",", &index, buffer, sizeof(buffer)))
                CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
        }

        profileid = atoi(buffer);

        // Get the profile, adding if needed.
        /////////////////////////////////////
        profile = gpiProfileListAdd(connection, profileid);
        if(!profile)
            Error(connection, GP_MEMORY_ERROR, "Out of memory.");

        // Mark as blocked, increment list counter
        //////////////////////////////////////////
        profile->blocked = gsi_true; 
        profile->blockIndex = iconnection->profileList.numBlocked++;
    }

    return GP_NO_ERROR;
}
