/* MySpaceIM Protocol Plugin, header file
 *
 * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
 *
 * 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 the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */

#include "myspace.h"

static void msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user);
static gchar *msim_format_now_playing(const gchar *band, const gchar *song);
static void msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text,
		gsize len, const gchar *error_message);

/* Callbacks for setting the username bit */
static void msim_check_username_availability_cb(PurpleConnection *gc, const char *value);
static void msim_username_is_available_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
static void msim_set_username_confirmed_cb(PurpleConnection *gc);
static void msim_username_is_set_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
static void msim_set_username(MsimSession *session, const gchar *username,
		MSIM_USER_LOOKUP_CB cb, gpointer data);
static char *msim_username_to_set;

/** Format the "now playing" indicator, showing the artist and song.
 * @return Return a new string (must be g_free()'d), or NULL.
 */
static gchar *
msim_format_now_playing(const gchar *band, const gchar *song)
{
	if ((band && *band) || (song && *song)) {
		return g_strdup_printf("%s - %s",
			(band && *band) ? band : "Unknown Artist",
			(song && *song) ? song : "Unknown Song");
	} else {
		return NULL;
	}
}
/** Get the MsimUser from a PurpleBuddy, creating it if needed. */
MsimUser *
msim_get_user_from_buddy(PurpleBuddy *buddy)
{
	MsimUser *user;

	if (!buddy) {
		return NULL;
	}

	if (!buddy->proto_data) {
		/* No MsimUser for this buddy; make one. */

		/* TODO: where is this freed? */
		user = g_new0(MsimUser, 1);
		user->buddy = buddy;
		user->id = purple_blist_node_get_int(&buddy->node, "UserID");
		buddy->proto_data = (gpointer)user;
	} 

	user = (MsimUser *)(buddy->proto_data);

	return user;
}

/** Find and return an MsimUser * representing a user on the buddy list, or NULL. */
MsimUser *
msim_find_user(MsimSession *session, const gchar *username)
{
	PurpleBuddy *buddy;
	MsimUser *user;

	buddy = purple_find_buddy(session->account, username);
	if (!buddy) {
		return NULL;
	}

	user = msim_get_user_from_buddy(buddy);

	return user;
}

/** Append user information to a PurpleNotifyUserInfo, given an MsimUser. 
 * Used by msim_tooltip_text() and msim_get_info_cb() to show a user's profile.
 */
void
msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full)
{
	PurplePresence *presence;
	gchar *str;
	guint cv;

	/* Useful to identify the account the tooltip refers to. 
	 *  Other prpls show this. */
	if (user->username) {
		purple_notify_user_info_add_pair(user_info, _("User"), user->username);
	}

	/* a/s/l...the vitals */
	if (user->age) {
		char age[16];
		g_snprintf(age, sizeof(age), "%d", user->age);
		purple_notify_user_info_add_pair(user_info, _("Age"), age);
	}

	if (user->gender && *user->gender) {
		purple_notify_user_info_add_pair(user_info, _("Gender"), user->gender);
	}

	if (user->location && *user->location) {
		purple_notify_user_info_add_pair(user_info, _("Location"), user->location);
	}

	/* Other information */
	if (user->headline && *user->headline) {
		purple_notify_user_info_add_pair(user_info, _("Headline"), user->headline);
	}

	if (user->buddy != NULL) {
		presence = purple_buddy_get_presence(user->buddy);

		if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
			PurpleStatus *status;
			const char *artist, *title;

			status = purple_presence_get_status(presence, "tune");
			title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
			artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);

			str = msim_format_now_playing(artist, title);
			if (str && *str) {
				purple_notify_user_info_add_pair(user_info, _("Song"), str);
			}
			g_free(str);
		}
	}

	/* Note: total friends only available if looked up by uid, not username. */
	if (user->total_friends) {
		char friends[16];
		g_snprintf(friends, sizeof(friends), "%d", user->total_friends);
		purple_notify_user_info_add_pair(user_info, _("Total Friends"), friends);
	}

	if (full) {
		/* Client information */
		char *client = NULL;

		str = user->client_info;
		cv = user->client_cv;

		if (str && cv != 0) {
			client = g_strdup_printf("%s (build %d)", str, cv);
		} else if (str) {
			client = g_strdup(str);
		} else if (cv) {
			client = g_strdup_printf("Build %d", cv);
		}
		if (client && *client)
			purple_notify_user_info_add_pair(user_info, _("Client Version"), client);
		g_free(client);
	}

	if (full && user->id) {
		/* TODO: link to username, if available */
		char *profile;
		purple_notify_user_info_add_section_break(user_info);
		profile = g_strdup_printf("<a href=\"http://myspace.com/%d\">%s</a>",
				user->id, _("View web profile"));
		purple_notify_user_info_add_pair(user_info, NULL, profile);
		g_free(profile);
	}
}

/** Set the currently playing song artist and or title.
 *
 * @param user User associated with the now playing information.
 *
 * @param new_artist New artist to set, or NULL/empty to not change artist.
 *
 * @param new_title New title to set, or NULL/empty to not change title.
 *
 * If new_artist and new_title are NULL/empty, deactivate PURPLE_STATUS_TUNE.
 *
 * This function is useful because it lets you set the artist or title
 * individually, which purple_prpl_got_user_status() doesn't do.
 */
static void msim_set_artist_or_title(MsimUser *user, const char *new_artist, const char *new_title)
{
	PurplePresence *presence;
	const char *prev_artist, *prev_title;

	if (user->buddy == NULL)
		/* User not on buddy list so nothing to do */
		return;

	prev_artist = NULL;
	prev_title = NULL;

	if (new_artist && !*new_artist)
		new_artist = NULL;
	if (new_title && !*new_title)
		new_title = NULL;

	if (!new_artist && !new_title) {
		purple_prpl_got_user_status_deactive(user->buddy->account, user->buddy->name, "tune");
		return;
	}

	presence = purple_buddy_get_presence(user->buddy);

	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
		PurpleStatus *status;
		
		status = purple_presence_get_status(presence, "tune");
		prev_title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
		prev_artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
	} 

	if (!new_artist)
		new_artist = prev_artist;
	
	if (!new_title)
		new_title = prev_title;

	purple_prpl_got_user_status(user->buddy->account, user->buddy->name, "tune",
			PURPLE_TUNE_TITLE, new_title,
			PURPLE_TUNE_ARTIST, new_artist,
			NULL);
}

/** Store a field of information about a buddy. 
 *
 * @param key_str Key to store.
 * @param value_str Value string, either user takes ownership of this string
 *                  or it is freed if MsimUser doesn't store the string.
 * @param user User to store data in. Existing data will be replaced.
 * */
void 
msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user)
{
	if (g_str_equal(key_str, "UserID") || g_str_equal(key_str, "ContactID")) {
		/* Save to buddy list, if it exists, for quick cached uid lookup with msim_uid2username_from_blist(). */
		user->id = atol(value_str);
		if (user->buddy)
		{
			purple_debug_info("msim", "associating uid %s with username %s\n", key_str, user->buddy->name);
			purple_blist_node_set_int(&user->buddy->node, "UserID", user->id);
		}
		/* Need to store in MsimUser, too? What if not on blist? */
	} else if (g_str_equal(key_str, "Age")) {
		user->age = atol(value_str);
		g_free(value_str);
	} else if (g_str_equal(key_str, "Gender")) {
		g_free(user->gender);
		user->gender = value_str;
	} else if (g_str_equal(key_str, "Location")) {
		g_free(user->location);
		user->location = value_str;
	} else if (g_str_equal(key_str, "TotalFriends")) {
		user->total_friends = atol(value_str);
	} else if (g_str_equal(key_str, "DisplayName")) {
		g_free(user->display_name);
		user->display_name = value_str;
	} else if (g_str_equal(key_str, "BandName")) {
		msim_set_artist_or_title(user, value_str, NULL);
	} else if (g_str_equal(key_str, "SongName")) {
		msim_set_artist_or_title(user, NULL, value_str);
	} else if (g_str_equal(key_str, "UserName") || g_str_equal(key_str, "IMName") || g_str_equal(key_str, "NickName")) {
		/* Ignore because PurpleBuddy knows this already */
		g_free(value_str);
	} else if (g_str_equal(key_str, "ImageURL") || g_str_equal(key_str, "AvatarURL")) {
		const gchar *previous_url;

		if (user->temporary_user) {
			/* This user will be destroyed soon; don't try to look up its image or avatar, 
			 * since that won't return immediately and we will end up accessing freed data.
			 */
			g_free(value_str);
			return;
		}

		if (user->temporary_user) {
			/* This user will be destroyed soon; don't try to look up its image or avatar, 
			 * since that won't return immediately and we will end up accessing freed data.
			 */
			g_free(value_str);
			return;
		}

		g_free(user->image_url);

		user->image_url = value_str;

		/* Instead of showing 'no photo' picture, show nothing. */
		if (g_str_equal(user->image_url, "http://x.myspace.com/images/no_pic.gif"))
		{
			purple_buddy_icons_set_for_user(user->buddy->account,
				user->buddy->name,
				NULL, 0, NULL);
			return;
		}

		/* TODO: use ETag for checksum */
		previous_url = purple_buddy_icons_get_checksum_for_user(user->buddy);

		/* Only download if URL changed */
		if (!previous_url || !g_str_equal(previous_url, user->image_url)) {
			purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user);
		}
	} else if (g_str_equal(key_str, "LastImageUpdated")) {
		/* TODO: use somewhere */
		user->last_image_updated = atol(value_str);
		g_free(value_str);
	} else if (g_str_equal(key_str, "Headline")) {
		g_free(user->headline);
		user->headline = value_str;
	} else {
		/* TODO: other fields in MsimUser */
		gchar *msg;

		msg = g_strdup_printf("msim_store_user_info_each: unknown field %s=%s",
				key_str, value_str);
		g_free(value_str);

		msim_unrecognized(NULL, NULL, msg);

		g_free(msg);
	}
}

/** Save buddy information to the buddy list from a user info reply message.
 *
 * @param session
 * @param msg The user information reply, with any amount of information.
 * @param user The structure to save to, or NULL to save in PurpleBuddy->proto_data.
 *
 * Variable information is saved to the passed MsimUser structure. Permanent
 * information (UserID) is stored in the blist node of the buddy list (and
 * ends up in blist.xml, persisted to disk) if it exists.
 *
 * If the function has no buddy information, this function
 * is a no-op (and returns FALSE).
 *
 */
gboolean 
msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user)
{
	gchar *username;
	MsimMessage *body, *body_node;

	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
	g_return_val_if_fail(msg != NULL, FALSE);

	body = msim_msg_get_dictionary(msg, "body");
	if (!body) {
		return FALSE;
	}

	username = msim_msg_get_string(body, "UserName");

	if (!username) {
		purple_debug_info("msim", 
			"msim_process_reply: not caching body, no UserName\n");
		msim_msg_free(body);
		g_free(username);
		return FALSE;
	}
	
	/* Null user = find and store in PurpleBuddy's proto_data */
	if (!user) {
		user = msim_find_user(session, username);
		if (!user) {
			msim_msg_free(body);
			g_free(username);
			return FALSE;
		}
	}

	/* TODO: make looping over MsimMessage's easier. */
	for (body_node = body; 
		body_node != NULL; 
		body_node = msim_msg_get_next_element_node(body_node))
	{
		const gchar *key_str;
		gchar *value_str;
		MsimMessageElement *elem;

		elem = (MsimMessageElement *)body_node->data;
		key_str = elem->name;

		value_str = msim_msg_get_string_from_element(elem);
		msim_store_user_info_each(key_str, value_str, user);
	}

	if (msim_msg_get_integer(msg, "dsn") == MG_OWN_IM_INFO_DSN &&
		msim_msg_get_integer(msg, "lid") == MG_OWN_IM_INFO_LID) {
		/* TODO: do something with our own IM info, if we need it for some
		 * specific purpose. Otherwise it is available on the buddy list,
		 * if the user has themselves as their own buddy. 
		 *
		 * However, much of the info is already available in MsimSession,
		 * stored in msim_we_are_logged_on(). */
	} else if (msim_msg_get_integer(msg, "dsn") == MG_OWN_MYSPACE_INFO_DSN &&
			msim_msg_get_integer(msg, "lid") == MG_OWN_MYSPACE_INFO_LID) {
		/* TODO: same as above, but for MySpace info. */
	}

	msim_msg_free(body);
	g_free(username);

	return TRUE;
}

/**
 * Asynchronously lookup user information, calling callback when receive result.
 *
 * @param session
 * @param user The user id, email address, or username. Not freed.
 * @param cb Callback, called with user information when available.
 * @param data An arbitray data pointer passed to the callback.
 */
/* TODO: change to not use callbacks */
void 
msim_lookup_user(MsimSession *session, const gchar *user, MSIM_USER_LOOKUP_CB cb, gpointer data)
{
	MsimMessage *body;
	gchar *field_name;
	guint rid, cmd, dsn, lid;

	g_return_if_fail(MSIM_SESSION_VALID(session));
	g_return_if_fail(user != NULL);
	/* Callback can be null to not call anything, just lookup & store information. */
	/*g_return_if_fail(cb != NULL);*/

	purple_debug_info("msim", "msim_lookup_userid: "
			"asynchronously looking up <%s>\n", user);

	msim_msg_dump("msim_lookup_user: data=%s\n", (MsimMessage *)data);

	/* Setup callback. Response will be associated with request using 'rid'. */
	rid = msim_new_reply_callback(session, cb, data);

	/* Send request */

	cmd = MSIM_CMD_GET;

	if (msim_is_userid(user)) {
		field_name = "UserID";
		dsn = MG_MYSPACE_INFO_BY_ID_DSN; 
		lid = MG_MYSPACE_INFO_BY_ID_LID; 
	} else if (msim_is_email(user)) {
		field_name = "Email";
		dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
		lid = MG_MYSPACE_INFO_BY_STRING_LID;
	} else {
		field_name = "UserName";
		dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
		lid = MG_MYSPACE_INFO_BY_STRING_LID;
	}

	body = msim_msg_new(
			field_name, MSIM_TYPE_STRING, g_strdup(user),
			NULL);

	g_return_if_fail(msim_send(session,
			"persist", MSIM_TYPE_INTEGER, 1,
			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
			"cmd", MSIM_TYPE_INTEGER, 1,
			"dsn", MSIM_TYPE_INTEGER, dsn,
			"uid", MSIM_TYPE_INTEGER, session->userid,
			"lid", MSIM_TYPE_INTEGER, lid,
			"rid", MSIM_TYPE_INTEGER, rid,
			"body", MSIM_TYPE_DICTIONARY, body,
			NULL));
} 


/**
 * Check if a string is a userid (all numeric).
 *
 * @param user The user id, email, or name.
 *
 * @return TRUE if is userid, FALSE if not.
 */
gboolean 
msim_is_userid(const gchar *user)
{
	g_return_val_if_fail(user != NULL, FALSE);

	return strspn(user, "0123456789") == strlen(user);
}

/** Return whether a given username is syntactically valid. 
 * Note: does not actually check that the user exists. */
gboolean
msim_is_valid_username(const gchar *user)
{
	return !msim_is_userid(user) &&  /* Not all numeric */
		strlen(user) <= MSIM_MAX_USERNAME_LENGTH
		&& strspn(user, "0123456789"
			"abcdefghijklmnopqrstuvwxyz"
			"_"
			"ABCDEFGHIJKLMNOPQRSTUVWXYZ") == strlen(user);
}

/**
 * Check if a string is an email address (contains an @).
 *
 * @param user The user id, email, or name.
 *
 * @return TRUE if is an email, FALSE if not.
 *
 * This function is not intended to be used as a generic
 * means of validating email addresses, but to distinguish
 * between a user represented by an email address from
 * other forms of identification.
 */ 
gboolean 
msim_is_email(const gchar *user)
{
	g_return_val_if_fail(user != NULL, FALSE);

	return strchr(user, '@') != NULL;
}


/** Callback for when a buddy icon finished being downloaded. */
static void
msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data,
		gpointer user_data,
		const gchar *url_text,
		gsize len,
		const gchar *error_message)
{
	MsimUser *user;

	user = (MsimUser *)user_data;

	purple_debug_info("msim_downloaded_buddy_icon",
			"Downloaded %" G_GSIZE_FORMAT " bytes\n", len);

	if (!url_text) {
		purple_debug_info("msim_downloaded_buddy_icon",
				"failed to download icon for %s",
				user->buddy->name);
		return;
	}

	purple_buddy_icons_set_for_user(user->buddy->account,
			user->buddy->name,
			g_memdup((gchar *)url_text, len), len, 
			/* Use URL itself as buddy icon "checksum" (TODO: ETag) */
			user->image_url);		/* checksum */
}

/*** 
 * If they hit cancel or no at any point in the Setting Username process, we come here.              *
 * Currently.. We're safe letting them get by without setting it.. Unless we hear otherwise..        *
 * So for now, give them a menu.. If this becomes an issue with the Official client.. boot them here */
void msim_do_not_set_username_cb(PurpleConnection *gc) {
	purple_debug_info("msim", "Don't set username");

	/* Protocol won't log in now without a username set.. Disconnect */
	purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("No username set"));
}
 
/** They've decided to set a username! Yay! */
void msim_set_username_cb(PurpleConnection *gc) {
	g_return_if_fail(gc != NULL);
	purple_debug_info("msim","Set username\n");
	purple_request_input(gc, _("MySpaceIM - Please Set a Username"),
			_("Please enter a username to check its availability:"),
			NULL,
			"", FALSE, FALSE, NULL,
			_("OK"), G_CALLBACK(msim_check_username_availability_cb),
			_("Cancel"), G_CALLBACK(msim_do_not_set_username_cb),
			purple_connection_get_account(gc),
			NULL,
			NULL,
			gc);
}

/** Once they've submitted their desired new username, 
 * check if it is available here. */
static void msim_check_username_availability_cb(PurpleConnection *gc, const char *username_to_check) 
{
	MsimMessage *user_msg;
	MsimSession *session;

	g_return_if_fail(gc != NULL);

	session = (MsimSession *)gc->proto_data;

	g_return_if_fail(MSIM_SESSION_VALID(session));

	purple_debug_info("msim_check_username_availability_cb", "Checking username: %s\n", username_to_check);
	
	user_msg = msim_msg_new(
			"user", MSIM_TYPE_STRING, g_strdup(username_to_check),
			NULL);

	/* 25 characters: letters, numbers, underscores */
	/* TODO: VERIFY ABOVE */

	/* \persist\1\sesskey\288500516\cmd\1\dsn\5\uid\204084363\lid\7\rid\367\body\UserName=Jaywalker\final\ */
	/* Official client uses a standard lookup... So do we! */
	msim_lookup_user(session, username_to_check, msim_username_is_available_cb, user_msg);
}

/** This is where we do a bit more than merely prompt the user. 
 * Now we have some real data to tell us the state of their requested username 
 * \persistr\\cmd\257\dsn\5\uid\204084363\lid\7\rid\367\body\UserName=TheAlbinoRhino1\final\ */
static void msim_username_is_available_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) 
{
	MsimMessage *msg;
	gchar *username;
	MsimMessage *body;
	gint userid;

	purple_debug_info("msim_username_is_available_cb", "Look up username callback made\n");
	
	msg = (MsimMessage *)data;
	g_return_if_fail(MSIM_SESSION_VALID(session));
	g_return_if_fail(msg != NULL);
	
	username = msim_msg_get_string(msg, "user");
	body = msim_msg_get_dictionary(userinfo, "body");

	if (!body) {
		purple_debug_info("msim_username_is_available_cb", "No body for %s?!\n", username);
		purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, 
				"An error occurred while trying to set the username.\n"
				"Please try again, or visit http://editprofile.myspace.com/index.cfm?"
				"fuseaction=profile.username to set your username.");
		return;
	}

	userid = msim_msg_get_integer(body, "UserID");

	purple_debug_info("msim_username_is_available_cb", "Returned username is %s and userid is %d\n", username, userid);
	msim_msg_free(body);
	msim_msg_free(msg);

	/* The response for a free username will ONLY have the UserName in it.. 
	 * thus making UserID return 0 when we msg_get_integer it */
	if (userid == 0) {
		/* This username is currently unused */
		purple_debug_info("msim_username_is_available_cb", "Username available. Prompting to Confirm.\n");
		msim_username_to_set = g_strdup(username);
		g_free(username);
		purple_request_yes_no(session->gc,
			_("MySpaceIM - Username Available"),
			_("This username is available. Would you like to set it?"),
			_("ONCE SET, THIS CANNOT BE CHANGED!"),
			0,
			session->account,
			NULL,
			NULL,
			session->gc, 
			G_CALLBACK(msim_set_username_confirmed_cb), 
			G_CALLBACK(msim_do_not_set_username_cb));
	} else {
		/* Looks like its in use or we have an invalid response */
		purple_debug_info("msim_username_is_available_cb", "Username unavaiable. Prompting for new entry.\n");
		purple_request_input(session->gc, _("MySpaceIM - Please Set a Username"),
			_("This username is unavailable."),
				_("Please try another username:"),
				"", FALSE, FALSE, NULL,
				_("OK"), G_CALLBACK(msim_check_username_availability_cb),
				_("Cancel"), G_CALLBACK(msim_do_not_set_username_cb),
				session->account,
				NULL,
				NULL,
				session->gc);
	}
}

/* They've confirmed that username that was available, Lets make the call to set it */
static void msim_set_username_confirmed_cb(PurpleConnection *gc) 
{
	MsimMessage *user_msg;
	MsimSession *session;

	g_return_if_fail(gc != NULL);

	session = (MsimSession *)gc->proto_data;

	g_return_if_fail(MSIM_SESSION_VALID(session));


	user_msg = msim_msg_new(
			"user", MSIM_TYPE_STRING, g_strdup(msim_username_to_set),
			NULL);

	purple_debug_info("msim_set_username_confirmed_cb", "Setting username to %s\n", msim_username_to_set);
	
	/* Sets our username... keep your fingers crossed :) */
	msim_set_username(session, msim_username_to_set, msim_username_is_set_cb, user_msg);
	g_free(msim_username_to_set);
}

/**
 * Asynchronously set new username, calling callback when receive result.
 *
 * @param session
 * @param username The username we're setting for ourselves. Not freed.
 * @param cb Callback, called with user information when available.
 * @param data An arbitray data pointer passed to the callback.
 */
static void 
msim_set_username(MsimSession *session, const gchar *username,
		MSIM_USER_LOOKUP_CB cb, gpointer data)
{
	MsimMessage *body;
	guint rid;

	g_return_if_fail(MSIM_SESSION_VALID(session));
	g_return_if_fail(username != NULL);
	g_return_if_fail(cb != NULL);

	purple_debug_info("msim", "msim_set_username: "
			"Setting username %s\n", username);

	msim_msg_dump("msim_set_username: data=%s\n", (MsimMessage *)data);

	/* Setup callback. Response will be associated with request using 'rid'. */
	rid = msim_new_reply_callback(session, cb, data);

	/* TODO: I dont know if the ContactType is -/ALWAYS/- 1 */

	body = msim_msg_new("UserName", MSIM_TYPE_STRING, g_strdup(username),NULL);
/* \setinfo\\sesskey\469958979\info\Age=21.AvatarUrl=.BandName=.ContactType=1.DisplayName=Msim.Gender=M.ImageURL=http:/1/1x.myspace.com/1images/1no_pic.gif.LastLogin=128335268400000000.Location=US.ShowAvatar=False.SongName=.TotalFriends=1.UserName=msimprpl2\final\
*/

	/* Send request */
	g_return_if_fail(msim_send(session,
			 "setinfo", MSIM_TYPE_BOOLEAN, TRUE,
			 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
			 "info", MSIM_TYPE_DICTIONARY, body,
			 NULL));
	body = msim_msg_new("UserName", MSIM_TYPE_STRING, g_strdup(username),NULL);
	g_return_if_fail(msim_send(session,
			 "persist", MSIM_TYPE_INTEGER, 1,
			 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
			 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET,
			 "dsn", MSIM_TYPE_INTEGER, MG_MYSPACE_INFO_BY_STRING_DSN,
			 "uid", MSIM_TYPE_INTEGER, session->userid,
			 "lid", MSIM_TYPE_INTEGER, MG_MYSPACE_INFO_BY_STRING_LID,
			 "rid", MSIM_TYPE_INTEGER, rid,
			 "body", MSIM_TYPE_DICTIONARY, body,
			 NULL));
}

/** Called after username is set. */
static void msim_username_is_set_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) 
{
	gchar *username, *errmsg;
	MsimMessage *body;

	guint rid;
	gint cmd,dsn,uid,lid,code;
	/* \persistr\\cmd\258\dsn\9\uid\204084363\lid\14\rid\369\body\UserName=TheAlbinoRhino1.Code=0\final\ */

	purple_debug_info("msim","username_is_set made\n");
	
	g_return_if_fail(MSIM_SESSION_VALID(session));


	msim_msg_dump("username_is_set message is: %s\n", userinfo);
	cmd = msim_msg_get_integer(userinfo, "cmd");
	dsn = msim_msg_get_integer(userinfo, "dsn");
	uid = msim_msg_get_integer(userinfo, "uid");
	lid = msim_msg_get_integer(userinfo, "lid");
	body = msim_msg_get_dictionary(userinfo, "body");
	errmsg = g_strdup("An error occurred while trying to set the username.\n"
			"Please try again, or visit http://editprofile.myspace.com/index.cfm?"
			"fuseaction=profile.username to set your username.");
	
	if (!body) {
		purple_debug_info("msim_username_is_set_cb", "No body");
		/* Error: No body! */
		purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg);
	}
	username = msim_msg_get_string(body, "UserName");
	code = msim_msg_get_integer(body,"Code");

	msim_msg_free(body);

	purple_debug_info("msim_username_is_set_cb", 
			"cmd = %d, dsn = %d, lid = %d, code = %d, username = %s\n", 
			cmd, dsn, lid, code, username);

	if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_PUT) 
			&& dsn == MC_SET_USERNAME_DSN 
			&& lid == MC_SET_USERNAME_LID) {
		purple_debug_info("msim_username_is_set_cb", "Proper cmd,dsn,lid for username_is_set!\n");
		purple_debug_info("msim_username_is_set_cb", "Username Set with return code %d\n",code);
		if (code == 0) {
			/* Good! */
			session->username = username;
			msim_we_are_logged_on(session);
		} else {
			purple_debug_info("msim_username_is_set", "code is %d",code);
			/* TODO: what to do here? */
		}
	} else if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET) 
			&& dsn == MG_MYSPACE_INFO_BY_STRING_DSN 
			&& lid == MG_MYSPACE_INFO_BY_STRING_LID) {
		/* Not quite done... ONE MORE STEP :) */
		rid = msim_new_reply_callback(session, msim_username_is_set_cb, data);
		body = msim_msg_new("UserName", MSIM_TYPE_STRING, g_strdup(username), NULL);
		if (!msim_send(session, "persist", MSIM_TYPE_INTEGER, 1, 
					"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
					"cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT, 
					"dsn", MSIM_TYPE_INTEGER, MC_SET_USERNAME_DSN, 
					"uid", MSIM_TYPE_INTEGER, session->userid,
					"lid", MSIM_TYPE_INTEGER, MC_SET_USERNAME_LID, 
					"rid", MSIM_TYPE_INTEGER, rid, 
					"body", MSIM_TYPE_DICTIONARY, body, 
					NULL)) {
			/* Error! */
			/* Can't set... Disconnect */
			purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg);
		}
			
	} else {
		/* Error! */
		purple_debug_info("msim","username_is_set Error: Invalid cmd/dsn/lid combination");
		purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg);
	}
	g_free(errmsg);
}
