/**
 * @file userlist.c MSN user list support
 *
 * purple
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * 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 "msn.h"
#include "userlist.h"

#include "contact.h"

const char *lists[] = { "FL", "AL", "BL", "RL" };

typedef struct
{
	PurpleConnection *gc;
	char *who;
	char *friendly;

} MsnPermitAdd;

/**************************************************************************
 * Callbacks
 **************************************************************************/
static void
msn_accept_add_cb(gpointer data)
{
	MsnPermitAdd *pa = data;

	purple_debug_misc("msn", "Accepted the new buddy: %s\n", pa->who);

	if (PURPLE_CONNECTION_IS_VALID(pa->gc))
	{
		MsnSession *session = pa->gc->proto_data;
		MsnUserList *userlist = session->userlist;

		msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_AL);

		msn_del_contact_from_list(session, NULL, pa->who, MSN_LIST_PL);
	}

	g_free(pa->who);
	g_free(pa->friendly);
	g_free(pa);
}

static void
msn_cancel_add_cb(gpointer data)
{
	MsnPermitAdd *pa = data;

	purple_debug_misc("msn", "Denied the new buddy: %s\n", pa->who);

	if (PURPLE_CONNECTION_IS_VALID(pa->gc))
	{
		MsnSession *session = pa->gc->proto_data;
		MsnUserList *userlist = session->userlist;
		MsnCallbackState *state = msn_callback_state_new(session);

		msn_callback_state_set_action(state, MSN_DENIED_BUDDY);

		msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_BL);
		msn_del_contact_from_list(session, state, pa->who, MSN_LIST_PL);
	}

	g_free(pa->who);
	g_free(pa->friendly);
	g_free(pa);
}

static void
got_new_entry(PurpleConnection *gc, const char *passport, const char *friendly, const char *message)
{
	PurpleAccount *acct;
	MsnPermitAdd *pa;

	pa = g_new0(MsnPermitAdd, 1);
	pa->who = g_strdup(passport);
	pa->friendly = g_strdup(friendly);
	pa->gc = gc;

	acct = purple_connection_get_account(gc);
	purple_account_request_authorization(acct, passport, NULL, friendly, message,
										 purple_find_buddy(acct, passport) != NULL,
										 msn_accept_add_cb, msn_cancel_add_cb, pa);

}

/**************************************************************************
 * Utility functions
 **************************************************************************/

gboolean
msn_userlist_user_is_in_group(MsnUser *user, const char * group_id)
{
	if (user == NULL)
		return FALSE;

	if (group_id == NULL)
		return FALSE;

	if (g_list_find_custom(user->group_ids, group_id, (GCompareFunc)strcmp))
		return TRUE;

	return FALSE;
}

gboolean
msn_userlist_user_is_in_list(MsnUser *user, MsnListId list_id)
{
	int list_op;

	if (user == NULL)
		return FALSE;

	list_op = 1 << list_id;

	if (user->list_op & list_op)
		return TRUE;
	else
		return FALSE;
}

/**************************************************************************
 * Server functions
 **************************************************************************/

void
msn_got_lst_user(MsnSession *session, MsnUser *user,
				 int list_op, GSList *group_ids)
{
	PurpleConnection *gc;
	PurpleAccount *account;
	const char *passport;
	const char *store;
	const char *message;

	account = session->account;
	gc = purple_account_get_connection(account);

	passport = msn_user_get_passport(user);
	store = msn_user_get_friendly_name(user);
	message = msn_user_get_invite_message(user);

	msn_user_set_op(user, list_op);

	if (list_op & MSN_LIST_FL_OP)
	{
		GSList *c;
		for (c = group_ids; c != NULL; c = g_slist_next(c))
		{
			char *group_id = c->data;
			msn_user_add_group_id(user, group_id);
		}

		/* FIXME: It might be a real alias */
		/* Umm, what? This might fix bug #1385130 */
		serv_got_alias(gc, passport, store);
	}

	if (list_op & MSN_LIST_AL_OP)
	{
		/* These are users who are allowed to see our status. */
		purple_privacy_deny_remove(account, passport, TRUE);
		purple_privacy_permit_add(account, passport, TRUE);
	}

	if (list_op & MSN_LIST_BL_OP)
	{
		/* These are users who are not allowed to see our status. */
		purple_privacy_permit_remove(account, passport, TRUE);
		purple_privacy_deny_add(account, passport, TRUE);
	}

	if (list_op & MSN_LIST_RL_OP)
	{
		/* These are users who have us on their buddy list. */
		/*
		 * TODO: What is store name set to when this happens?
		 *       For one of my accounts "something@hotmail.com"
		 *       the store name was "something."  Maybe we
		 *       should use the friendly name, instead? --KingAnt
		 */

		if (!(list_op & (MSN_LIST_AL_OP | MSN_LIST_BL_OP)))
		{
/*			got_new_entry(gc, passport, store, NULL); */
		}
	}

	if (list_op & MSN_LIST_PL_OP)
	{
		got_new_entry(gc, passport, store, message);
	}
}

/**************************************************************************
 * UserList functions
 **************************************************************************/

MsnUserList*
msn_userlist_new(MsnSession *session)
{
	MsnUserList *userlist;

	userlist = g_new0(MsnUserList, 1);

	userlist->session = session;
	userlist->buddy_icon_requests = g_queue_new();

	/* buddy_icon_window is the number of allowed simultaneous buddy icon requests.
	 * XXX With smarter rate limiting code, we could allow more at once... 5 was the limit set when
	 * we weren't retrieiving any more than 5 per MSN session. */
	userlist->buddy_icon_window = 1;

	return userlist;
}

void
msn_userlist_destroy(MsnUserList *userlist)
{
	GList *l;

	/*destroy userlist*/
	for (l = userlist->users; l != NULL; l = l->next)
	{
		msn_user_destroy(l->data);
	}
	g_list_free(userlist->users);

	/*destroy group list*/
	for (l = userlist->groups; l != NULL; l = l->next)
	{
		msn_group_destroy(l->data);
	}
	g_list_free(userlist->groups);

	g_queue_free(userlist->buddy_icon_requests);

	if (userlist->buddy_icon_request_timer)
		purple_timeout_remove(userlist->buddy_icon_request_timer);

	g_free(userlist);
}

MsnUser *
msn_userlist_find_add_user(MsnUserList *userlist,const char *passport,const char *userName)
{
	MsnUser *user;

	user = msn_userlist_find_user(userlist, passport);
	if (user == NULL)
	{
		user = msn_user_new(userlist, passport, userName);
		msn_userlist_add_user(userlist, user);
	} else {
		msn_user_set_friendly_name(user, userName);
	}
	return user;
}

void
msn_userlist_add_user(MsnUserList *userlist, MsnUser *user)
{
	userlist->users = g_list_prepend(userlist->users, user);
}

void
msn_userlist_remove_user(MsnUserList *userlist, MsnUser *user)
{
	userlist->users = g_list_remove(userlist->users, user);
}

MsnUser *
msn_userlist_find_user(MsnUserList *userlist, const char *passport)
{
	GList *l;

	g_return_val_if_fail(passport != NULL, NULL);

	for (l = userlist->users; l != NULL; l = l->next)
	{
		MsnUser *user = (MsnUser *)l->data;

		g_return_val_if_fail(user->passport != NULL, NULL);

		if (!g_ascii_strcasecmp(passport, user->passport)){
			return user;
		}
	}

	return NULL;
}

MsnUser *
msn_userlist_find_user_with_id(MsnUserList *userlist, const char *uid)
{
 	GList *l;

	g_return_val_if_fail(uid != NULL, NULL);

	for (l = userlist->users; l != NULL; l = l->next) {
		MsnUser *user = (MsnUser *)l->data;

		if (user->uid == NULL) {
			continue;
		}

		if ( !g_ascii_strcasecmp(uid, user->uid) ) {
			return user;
		}
	}

	return NULL;
}

MsnUser *
msn_userlist_find_user_with_mobile_phone(MsnUserList *userlist, const char *number)
{
	GList *l;

	g_return_val_if_fail(number != NULL, NULL);

	for (l = userlist->users; l != NULL; l = l->next) {
		MsnUser *user = (MsnUser *)l->data;

		if (user->phone.mobile == NULL) {
			continue;
		}

		if (!g_ascii_strcasecmp(number, user->phone.mobile)) {
			return user;
		}
	}

	return NULL;
}

void
msn_userlist_add_group(MsnUserList *userlist, MsnGroup *group)
{
	userlist->groups = g_list_append(userlist->groups, group);
}

void
msn_userlist_remove_group(MsnUserList *userlist, MsnGroup *group)
{
	userlist->groups = g_list_remove(userlist->groups, group);
}

MsnGroup *
msn_userlist_find_group_with_id(MsnUserList *userlist, const char * id)
{
	GList *l;

	g_return_val_if_fail(userlist != NULL, NULL);
	g_return_val_if_fail(id       != NULL, NULL);

	for (l = userlist->groups; l != NULL; l = l->next)
	{
		MsnGroup *group = l->data;

		if (!g_ascii_strcasecmp(group->id,id))
			return group;
	}

	return NULL;
}

MsnGroup *
msn_userlist_find_group_with_name(MsnUserList *userlist, const char *name)
{
	GList *l;

	g_return_val_if_fail(userlist != NULL, NULL);
	g_return_val_if_fail(name     != NULL, NULL);

	for (l = userlist->groups; l != NULL; l = l->next)
	{
		MsnGroup *group = l->data;

		if ((group->name != NULL) && !g_ascii_strcasecmp(name, group->name))
			return group;
	}

	return NULL;
}

const char *
msn_userlist_find_group_id(MsnUserList *userlist, const char *group_name)
{
	MsnGroup *group;

	group = msn_userlist_find_group_with_name(userlist, group_name);

	if (group != NULL)
		return msn_group_get_id(group);
	else
		return NULL;
}

const char *
msn_userlist_find_group_name(MsnUserList *userlist, const char * group_id)
{
	MsnGroup *group;

	group = msn_userlist_find_group_with_id(userlist, group_id);

	if (group != NULL)
		return msn_group_get_name(group);
	else
		return NULL;
}

void
msn_userlist_rename_group_id(MsnUserList *userlist, const char * group_id,
							 const char *new_name)
{
	MsnGroup *group;

	group = msn_userlist_find_group_with_id(userlist, group_id);

	if (group != NULL)
		msn_group_set_name(group, new_name);
}

void
msn_userlist_remove_group_id(MsnUserList *userlist, const char * group_id)
{
	MsnGroup *group;

	group = msn_userlist_find_group_with_id(userlist, group_id);

	if (group != NULL)
	{
		msn_userlist_remove_group(userlist, group);
		msn_group_destroy(group);
	}
}

typedef struct {
	MsnSession *session;
	char *uid;
} MsnUserlistABData;

static void
userlist_ab_delete_cb(void *data, int choice)
{
	MsnUserlistABData *ab = (MsnUserlistABData *)data;

	/* msn_delete_contact(ab->session, ab->uid, (gboolean)choice); */

	g_free(ab->uid);
	g_free(ab);
}

void
msn_userlist_rem_buddy(MsnUserList *userlist, const char *who)
{
	MsnUser *user = NULL;

	g_return_if_fail(userlist != NULL);
	g_return_if_fail(userlist->session != NULL);
	g_return_if_fail(who != NULL);

	user = msn_userlist_find_user(userlist, who);

	msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_FL);

	/* delete the contact from address book via soap action */
	if (user != NULL) {
		if (0 /*not ready yet*/ && userlist->session->passport_info.email_enabled) {
			MsnUserlistABData *ab = g_new0(MsnUserlistABData, 1);
			ab->session = userlist->session;
			ab->uid = g_strdup(user->uid); /* Not necessary? */
			purple_request_yes_no(userlist->session->account,
				_("Delete Buddy from Address Book?"),
				_("Do you want to delete this buddy from your address book as well?"),
				user->passport, 0, userlist->session->account, user->passport,
				NULL, ab,
				G_CALLBACK(userlist_ab_delete_cb), G_CALLBACK(userlist_ab_delete_cb));
		} else
			msn_delete_contact(userlist->session, user);
	}
}

void
msn_userlist_rem_buddy_from_list(MsnUserList *userlist, const char *who,
				 MsnListId list_id)
{
	MsnUser *user;
	const gchar *list;
	MsnListOp list_op = 1 << list_id;

	user = msn_userlist_find_user(userlist, who);

	g_return_if_fail(user != NULL);

	if ( !msn_userlist_user_is_in_list(user, list_id)) {
		list = lists[list_id];
		purple_debug_info("msn", "User %s is not in list %s, not removing.\n", who, list);
		return;
	}

	msn_user_unset_op(user, list_op);

	msn_notification_rem_buddy_from_list(userlist->session->notification, list_id, user);
}

/*add buddy*/
void
msn_userlist_add_buddy(MsnUserList *userlist, const char *who, const char *group_name)
{
	MsnUser *user;
	MsnCallbackState *state = NULL;
	const char *group_id = NULL, *new_group_name;

	new_group_name = group_name == NULL ? MSN_INDIVIDUALS_GROUP_NAME : group_name;

	g_return_if_fail(userlist != NULL);
	g_return_if_fail(userlist->session != NULL);

	purple_debug_info("msn", "Add user: %s to group: %s\n", who, new_group_name);

	if (!purple_email_is_valid(who))
	{
		/* only notify the user about problems adding to the friends list
		 * maybe we should do something else for other lists, but it probably
		 * won't cause too many problems if we just ignore it */

		char *str = g_strdup_printf(_("Unable to add \"%s\"."), who);

		purple_notify_error(NULL, NULL, str,
				  _("The username specified is invalid."));
		g_free(str);

		return;
	}

	state = msn_callback_state_new(userlist->session);
	msn_callback_state_set_who(state, who);
	msn_callback_state_set_new_group_name(state, new_group_name);

	group_id = msn_userlist_find_group_id(userlist, new_group_name);

	if (group_id == NULL)
	{
		/* Whoa, we must add that group first. */
		purple_debug_info("msn", "Adding user %s to a new group, creating group %s first\n", who, new_group_name);

		msn_callback_state_set_action(state, MSN_ADD_BUDDY);

		msn_add_group(userlist->session, state, new_group_name);
		return;
	} else {
		msn_callback_state_set_guid(state, group_id);
	}

	/* XXX: adding user here may not be correct (should add them in the
 	 * ACK to the ADL command), but for now we need to make sure they exist
	 * early enough that the ILN command doesn't screw us up */

	user = msn_userlist_find_add_user(userlist, who, who);

	if ( msn_userlist_user_is_in_list(user, MSN_LIST_FL) ) {

		purple_debug_info("msn", "User %s already exists\n", who);

		msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_BL);

		if (msn_userlist_user_is_in_group(user, group_id)) {
			purple_debug_info("msn", "User %s is already in group %s, returning\n", who, new_group_name);
			msn_callback_state_free(state);
			return;
		}
	}

	purple_debug_info("msn", "Adding user: %s to group id: %s\n", who, group_id);

	msn_callback_state_set_action(state, MSN_ADD_BUDDY);

	/* Add contact in the Contact server with a SOAP request and if
	   successful, send ADL with MSN_LIST_AL and MSN_LIST_FL and a FQY */
	msn_add_contact_to_group(userlist->session, state, who, group_id);
}

/*
 * Save a buddy address/group until we get back response from FQY
 */
void
msn_userlist_save_pending_buddy(MsnUserList *userlist,
                               const char *who,
                               const char *group_name)
{
	MsnUser *user;

	g_return_if_fail(userlist != NULL);

	user = msn_user_new(userlist, who, NULL);
	msn_user_set_pending_group(user, group_name);
	msn_user_set_network(user, MSN_NETWORK_UNKNOWN);
	userlist->pending = g_list_prepend(userlist->pending, user);
}

/*
 * Actually adds a buddy once we have the response from FQY
 */
void
msn_userlist_add_pending_buddy(MsnSession *session,
                               const char *who,
                               /*MsnNetwork*/ int network)
{
	MsnUserList *userlist = session->userlist;
	MsnUser *user = NULL;
	MsnUser *user2;
	GList *l;
	char *group;

	for (l = userlist->pending; l != NULL; l = l->next)
	{
		user = (MsnUser *)l->data;

		if (!g_ascii_strcasecmp(who, user->passport)) {
			userlist->pending = g_list_delete_link(userlist->pending, l);
			break;
		}
	}

	if (user == NULL) {
		purple_debug_error("msn", "Attempting to add a pending user that does not exist.\n");
		return;
	}

	group = msn_user_remove_pending_group(user);

	user2 = msn_userlist_find_user(userlist, who);
	if (user2 != NULL) {
		/* User already in userlist, so just update it. */
		msn_user_destroy(user);
		user = user2;
	} else {
		msn_userlist_add_user(userlist, user);
	}

	msn_user_set_network(user, network);
	msn_userlist_add_buddy(userlist, who, group);
	g_free(group);
}

void
msn_userlist_add_buddy_to_list(MsnUserList *userlist, const char *who,
							MsnListId list_id)
{
	MsnUser *user = NULL;
	const gchar *list;
	MsnListOp list_op = 1 << list_id;

	g_return_if_fail(userlist != NULL);

	user = msn_userlist_find_add_user(userlist, who, who);

	/* First we're going to check if it's already there. */
	if (msn_userlist_user_is_in_list(user, list_id))
	{
		list = lists[list_id];
		purple_debug_info("msn", "User '%s' is already in list: %s\n", who, list);
		return;
	}

	/* XXX: see XXX above, this should really be done when we get the response from
		the server */

	msn_user_set_op(user, list_op);

	msn_notification_add_buddy_to_list(userlist->session->notification, list_id, user);
}

gboolean
msn_userlist_add_buddy_to_group(MsnUserList *userlist, const char *who,
				const char *group_name)
{
	MsnUser *user;
	gchar * group_id;

	g_return_val_if_fail(userlist != NULL, FALSE);
	g_return_val_if_fail(group_name != NULL, FALSE);
	g_return_val_if_fail(who != NULL, FALSE);

	purple_debug_info("msn", "Adding buddy with passport %s to group %s\n", who, group_name);

	if ( (group_id = (gchar *)msn_userlist_find_group_id(userlist, group_name)) == NULL) {
		purple_debug_error("msn", "Group %s has no guid!\n", group_name);
		return FALSE;
	}

	if ( (user = msn_userlist_find_user(userlist, who)) == NULL) {
		purple_debug_error("msn", "User %s not found!\n", who);
		return FALSE;
	}

	msn_user_add_group_id(user, group_id);

	return TRUE;
}


gboolean
msn_userlist_rem_buddy_from_group(MsnUserList *userlist, const char *who,
				const char *group_name)
{
	const gchar * group_id;
	MsnUser *user;

	g_return_val_if_fail(userlist != NULL, FALSE);
	g_return_val_if_fail(group_name != NULL, FALSE);
	g_return_val_if_fail(who != NULL, FALSE);

	purple_debug_info("msn", "Removing buddy with passport %s from group %s\n", who, group_name);

	if ( (group_id = msn_userlist_find_group_id(userlist, group_name)) == NULL) {
		purple_debug_error("msn", "Group %s has no guid!\n", group_name);
		return FALSE;
	}

	if ( (user = msn_userlist_find_user(userlist, who)) == NULL) {
		purple_debug_error("msn", "User %s not found!\n", who);
		return FALSE;
	}

	msn_user_remove_group_id(user, group_id);

	return TRUE;
}

void
msn_userlist_move_buddy(MsnUserList *userlist, const char *who,
			const char *old_group_name, const char *new_group_name)
{
	const char *new_group_id;
	MsnCallbackState *state;

	g_return_if_fail(userlist != NULL);
	g_return_if_fail(userlist->session != NULL);

	state = msn_callback_state_new(userlist->session);
	msn_callback_state_set_who(state, who);
	msn_callback_state_set_action(state, MSN_MOVE_BUDDY);
	msn_callback_state_set_old_group_name(state, old_group_name);
	msn_callback_state_set_new_group_name(state, new_group_name);

	new_group_id = msn_userlist_find_group_id(userlist, new_group_name);

	if (new_group_id == NULL)
	{
		msn_add_group(userlist->session, state, new_group_name);
		return;
	}

	/* add the contact to the new group, and remove it from the old one in
	 * the callback
	*/
	msn_add_contact_to_group(userlist->session, state, who, new_group_id);
}

/*load userlist from the Blist file cache*/
void
msn_userlist_load(MsnSession *session)
{
	PurpleBlistNode *gnode, *cnode, *bnode;
	PurpleAccount *account = session->account;
	PurpleConnection *gc = purple_account_get_connection(account);
	GSList *l;
	MsnUser * user;

	g_return_if_fail(gc != NULL);

	for (gnode = purple_blist_get_root(); gnode;
			gnode = purple_blist_node_get_sibling_next(gnode))
	{
		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
			continue;
		for (cnode = purple_blist_node_get_first_child(gnode);
				cnode;
				cnode = purple_blist_node_get_sibling_next(cnode))
		{
			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
				continue;
			for (bnode = purple_blist_node_get_first_child(cnode);
					bnode;
					bnode = purple_blist_node_get_sibling_next(bnode))
			{
				PurpleBuddy *b;
				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
					continue;
				b = (PurpleBuddy *)bnode;
				if (purple_buddy_get_account(b) == account)
				{
					user = msn_userlist_find_add_user(session->userlist,
						purple_buddy_get_name(b), NULL);
					purple_buddy_set_protocol_data(b, user);
					msn_user_set_op(user, MSN_LIST_FL_OP);
				}
			}
		}
	}
	for (l = session->account->permit; l != NULL; l = l->next)
	{
		user = msn_userlist_find_add_user(session->userlist,
						(char *)l->data,NULL);
		msn_user_set_op(user, MSN_LIST_AL_OP);
	}
	for (l = session->account->deny; l != NULL; l = l->next)
	{
		user = msn_userlist_find_add_user(session->userlist,
						(char *)l->data,NULL);
		msn_user_set_op(user, MSN_LIST_BL_OP);
	}

}

