/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * 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 library 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 library 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 library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib/gi18n-lib.h>

#include "purpleircv3messagehandlers.h"

#include "purpleircv3connection.h"
#include "purpleircv3core.h"
#include "purpleircv3ctcphandlers.h"

/******************************************************************************
 * Helpers
 *****************************************************************************/
static PurpleConversationMember *
purple_ircv3_add_contact_to_conversation(PurpleContact *contact,
                                         PurpleConversation *conversation,
                                         gboolean announce)
{
	PurpleContactInfo *info = PURPLE_CONTACT_INFO(contact);
	PurpleConversationMember *member = NULL;
	PurpleConversationMembers *members = NULL;

	members = purple_conversation_get_members(conversation);

	member = purple_conversation_members_find_member(members, info);
	if(!PURPLE_IS_CONVERSATION_MEMBER(member)) {
		char *message = NULL;

		if(announce) {
			message = g_strdup_printf(_("%s has joined %s"),
			                          purple_contact_info_get_sid(info),
			                          purple_conversation_get_title_for_display(conversation));
		}

		member = purple_conversation_members_add_member(members, info,
		                                                announce, message);
		g_clear_pointer(&message, g_free);
	}

	return member;
}

static void
purple_ircv3_add_badges_to_member(PurpleConversationMember *member,
                                  IbisClient *client, const char *nick)
{
	PurpleBadgeManager *manager = NULL;
	PurpleBadges *badges = NULL;
	char *prefixes = NULL;
	char *iter = NULL;

	g_return_if_fail(PURPLE_IS_CONVERSATION_MEMBER(member));
	g_return_if_fail(IBIS_IS_CLIENT(client));

	badges = purple_conversation_member_get_badges(member);
	manager = purple_badge_manager_get_default();
	prefixes = ibis_client_get_source_prefix(client, nick);
	iter = prefixes;

	while(iter != NULL) {
		PurpleBadge *badge = NULL;
		const char *id = NULL;
		const char *mnemonic = NULL;
		int priority = 0;

		switch(g_utf8_get_char(iter)) {
		case '~':
			id = "ircv3-badge-founder";
			priority = 500;
			mnemonic = "~";
			break;
		case '&':
			id = "ircv3-badge-protected";
			priority = 400;
			mnemonic = "&";
			break;
		case '@':
			id = "ircv3-badge-operator";
			priority = 300;
			mnemonic = "@";
			break;
		case '%':
			id = "ircv3-badge-halfop";
			priority = 200;
			mnemonic = "%%";
			break;
		case '+':
			id = "ircv3-badge-voice";
			priority = 100;
			mnemonic = "+";
			break;
		}

		if(id == NULL) {
			break;
		}

		badge = purple_badge_manager_find(manager, id);
		if(!PURPLE_IS_BADGE(badge)) {
			badge = purple_badge_new(id, priority, id, mnemonic);
			purple_badge_manager_add(manager, badge);

			/* The manager holds a reference but we need to ditch our reference
			 * so we unref the instance and use the manager's reference.
			 */
			g_object_unref(badge);
		}

		purple_badges_add_badge(badges, badge);

		g_clear_object(&badge);

		iter = g_utf8_next_char(iter);
	}

	g_free(prefixes);
}

/******************************************************************************
 * General Commands
 *****************************************************************************/
gboolean
purple_ircv3_message_handler_join(G_GNUC_UNUSED IbisClient *client,
                                  G_GNUC_UNUSED const char *command,
                                  IbisMessage *message,
                                  gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleContact *contact = NULL;
	PurpleConversation *conversation = NULL;
	GStrv params = NULL;
	const char *conversation_name = NULL;

	contact = purple_ircv3_connection_find_or_create_contact(connection,
	                                                         message);

	params = ibis_message_get_params(message);

	/* A normal join command has the channel as the only parameter. */
	if(g_strv_length(params) == 1) {
		conversation_name = params[0];
	} else {
		/* TODO: write this to join to the status window saying we didn't know
		 * how to parse it.
		 */

		return TRUE;
	}

	conversation = purple_ircv3_connection_find_or_create_conversation(connection,
	                                                                   conversation_name);
	purple_ircv3_add_contact_to_conversation(contact, conversation, TRUE);

	return TRUE;
}

gboolean
purple_ircv3_message_handler_part(G_GNUC_UNUSED IbisClient *client,
                                  G_GNUC_UNUSED const char *command,
                                  IbisMessage *message,
                                  gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleAccount *account = NULL;
	PurpleContact *contact = NULL;
	PurpleConversation *conversation = NULL;
	PurpleConversationManager *manager = NULL;
	PurpleConversationMembers *members = NULL;
	GStrv params = NULL;
	guint n_params = 0;
	char *reason = NULL;
	const char *conversation_name = NULL;

	params = ibis_message_get_params(message);
	n_params = g_strv_length(params);
	if(n_params == 0) {
		/* TODO: mention unparsable message in the status window. */
		return TRUE;
	}

	/* TODO: The spec says servers _SHOULD NOT_ send a comma separated list of
	 * channels, but we should support that at some point just in case.
	 */
	conversation_name = params[0];

	account = purple_connection_get_account(PURPLE_CONNECTION(connection));
	manager = purple_conversation_manager_get_default();
	conversation = purple_conversation_manager_find_with_id(manager, account,
	                                                        conversation_name);

	if(!PURPLE_IS_CONVERSATION(conversation)) {
		/* TODO: write status message unknown channel. */

		return TRUE;
	}

	members = purple_conversation_get_members(conversation);

	/* We do want to find or create the contact, even on a part, because we
	 * could have connected to a BNC and we weren't told about the contact yet.
	 */
	contact = purple_ircv3_connection_find_or_create_contact(connection,
	                                                         message);

	/* If a part message was given, join the remaining parameters with a space.
	 */
	if(n_params > 1) {
		char *part_message = NULL;

		part_message = g_strjoinv(" ", params + 1);
		reason = g_strdup_printf(_("%s has left %s (%s)"),
		                         purple_contact_info_get_sid(PURPLE_CONTACT_INFO(contact)),
		                         purple_conversation_get_title_for_display(conversation),
		                         part_message);
	} else {
		reason = g_strdup_printf(_("%s has left %s"),
		                         purple_contact_info_get_sid(PURPLE_CONTACT_INFO(contact)),
		                         purple_conversation_get_title_for_display(conversation));
	}

	purple_conversation_members_remove_member(members,
	                                          PURPLE_CONTACT_INFO(contact),
	                                          TRUE, reason);

	g_clear_pointer(&reason, g_free);

	return TRUE;
}

gboolean
purple_ircv3_message_handler_namreply(IbisClient *client,
                                      G_GNUC_UNUSED const char *command,
                                      IbisMessage *message, gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleConversation *conversation = NULL;
	GStrv params = NULL;
	GStrv nicks = NULL;
	const char *target = NULL;

	params = ibis_message_get_params(message);
	if(params == NULL) {
		g_warning("namreply received with no parameters");

		return FALSE;
	}

	if(g_strv_length(params) != 4) {
		char *body = g_strjoinv(" ", params);
		g_warning("unknown namreply format: '%s'", body);
		g_free(body);

		return FALSE;
	}

	/* params[0] holds nick of the user and params[1] holds the channel type
	 * (public/private) but we don't care about either of these.
	 */

	target = params[2];
	if(!ibis_client_is_channel(client, target)) {
		g_warning("received namreply for '%s' which is not a channel.",
		          target);

		return FALSE;
	}

	conversation = purple_ircv3_connection_find_or_create_conversation(connection,
	                                                                   target);

	/* Split the last parameter on space to get a list of all the nicks. */
	nicks = g_strsplit(params[3], " ", -1);
	if(nicks != NULL) {
		PurpleAccount *account = NULL;
		PurpleConnection *purple_connection = NULL;
		PurpleContactManager *manager = purple_contact_manager_get_default();
		PurpleConversationMembers *members = NULL;

		purple_connection = PURPLE_CONNECTION(connection);
		account = purple_connection_get_account(purple_connection);

		members = purple_conversation_get_members(conversation);

		for(guint i = 0; i < g_strv_length(nicks); i++) {
			PurpleContact *contact = NULL;
			PurpleConversationMember *member = NULL;
			const char *nick = nicks[i];
			char *stripped = NULL;

			stripped = ibis_client_strip_source_prefix(client, nick);

			contact = purple_contact_manager_find_with_id(manager, account,
			                                              stripped);
			if(!PURPLE_IS_CONTACT(contact)) {
				contact = purple_contact_new(account, stripped);
				purple_contact_info_set_username(PURPLE_CONTACT_INFO(contact),
				                                 stripped);
				purple_contact_manager_add(manager, contact);
			}

			member = purple_conversation_members_add_member(members,
			                                                PURPLE_CONTACT_INFO(contact),
			                                                FALSE, NULL);

			purple_ircv3_add_badges_to_member(member, client, nick);

			g_free(stripped);
		}
	}

	g_strfreev(nicks);

	return TRUE;
}

gboolean
purple_ircv3_message_handler_tagmsg(IbisClient *client,
                                    G_GNUC_UNUSED const char *command,
                                    IbisMessage *ibis_message, gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleContact *contact = NULL;
	PurpleConversation *conversation = NULL;
	GStrv params = NULL;
	IbisTags *tags = NULL;
	const char *target = NULL;
	const char *value = NULL;

	params = ibis_message_get_params(ibis_message);
	tags = ibis_message_get_tags(ibis_message);

	if(params == NULL) {
		g_warning("tagmsg received with no parameters");

		return FALSE;
	}

	if(g_strv_length(params) != 1) {
		char *body = g_strjoinv(" ", params);
		g_warning("unknown tagmsg message format: '%s'", body);
		g_free(body);

		return FALSE;
	}

	/* Find or create the contact. */
	contact = purple_ircv3_connection_find_or_create_contact(connection,
	                                                         ibis_message);

	/* Find or create the conversation. */
	target = params[0];
	if(!ibis_client_is_channel(client, target)) {
		target = purple_contact_info_get_id(PURPLE_CONTACT_INFO(contact));
	}

	conversation = purple_ircv3_connection_find_or_create_conversation(connection,
	                                                                   target);

	purple_ircv3_add_contact_to_conversation(contact, conversation, FALSE);

	/* Handle typing notifications. */
	value = ibis_tags_lookup(tags, IBIS_TAG_TYPING);
	if(!purple_strempty(value)) {
		PurpleConversationMember *member = NULL;
		PurpleConversationMembers *members = NULL;
		PurpleTypingState state = PURPLE_TYPING_STATE_NONE;
		guint timeout = 1;

		members = purple_conversation_get_members(conversation);
		member = purple_conversation_members_find_member(members,
		                                                 PURPLE_CONTACT_INFO(contact));

		if(purple_strequal(value, IBIS_TYPING_ACTIVE)) {
			state = PURPLE_TYPING_STATE_TYPING;
			timeout = 6;
		} else if(purple_strequal(value, IBIS_TYPING_PAUSED)) {
			state = PURPLE_TYPING_STATE_PAUSED;
			timeout = 30;
		}

		purple_conversation_member_set_typing_state(member, state, timeout);
	}

	return TRUE;
}

gboolean
purple_ircv3_message_handler_privmsg(IbisClient *client, const char *command,
                                     IbisMessage *ibis_message, gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleContact *contact = NULL;
	PurpleConversation *conversation = NULL;
	PurpleConversationMember *member = NULL;
	PurpleMessage *message = NULL;
	GDateTime *dt = NULL;
	IbisCTCPMessage *ctcp_message = NULL;
	IbisTags *tags = NULL;
	GStrv params = NULL;
	const char *target = NULL;
	gboolean announce = TRUE;

	params = ibis_message_get_params(ibis_message);
	ctcp_message = ibis_message_get_ctcp_message(ibis_message);
	tags = ibis_message_get_tags(ibis_message);

	if(params == NULL) {
		g_warning("privmsg received with no parameters");

		return FALSE;
	}

	if(g_strv_length(params) != 2) {
		char *body = g_strjoinv(" ", params);
		g_warning("unknown privmsg message format: '%s'", body);
		g_free(body);

		return FALSE;
	}

	/* Find or create the contact. */
	contact = purple_ircv3_connection_find_or_create_contact(connection,
	                                                         ibis_message);

	/* Find or create the conversation. */
	target = params[0];
	if(!ibis_client_is_channel(client, target)) {
		target = purple_contact_info_get_id(PURPLE_CONTACT_INFO(contact));
	}

	if(!ibis_client_get_registered(client)) {
		conversation = purple_ircv3_connection_get_status_conversation(connection);
		announce = FALSE;
	} else if(IBIS_IS_CTCP_MESSAGE(ctcp_message)) {
		conversation = purple_ircv3_connection_get_status_conversation(connection);
		announce = FALSE;
	} else {
		conversation = purple_ircv3_connection_find_or_create_conversation(connection,
		                                                                   target);
	}

	member = purple_ircv3_add_contact_to_conversation(contact, conversation,
	                                                  announce);

	if(IBIS_IS_CTCP_MESSAGE(ctcp_message)) {
		if(ibis_ctcp_message_is_command(ctcp_message, IBIS_CTCP_ACTION)) {
			GStrv ctcp_params = NULL;
			char *ctcp_body = NULL;
			char *stripped = NULL;

			ctcp_params = ibis_ctcp_message_get_params(ctcp_message);
			ctcp_body = g_strjoinv(" ", ctcp_params);
			stripped = ibis_formatting_strip(ctcp_body);
			g_free(ctcp_body);

			message = purple_message_new(member, stripped);
			g_free(stripped);

			purple_message_set_action(message, TRUE);
		} else {
			char *body = NULL;

			body = g_strdup_printf(_("requested CTCP '%s' (to %s) from %s"),
			                       ibis_ctcp_message_get_command(ctcp_message),
			                       params[0],
			                       purple_contact_info_get_id(PURPLE_CONTACT_INFO(contact)));

			message = purple_message_new(member, body);
			g_free(body);
		}
	}

	if(!PURPLE_IS_MESSAGE(message)) {
		char *stripped = NULL;

		stripped = ibis_formatting_strip(params[1]);
		message = purple_message_new(member, stripped);
		g_clear_pointer(&stripped, g_free);
	}


	if(purple_strequal(command, IBIS_MSG_NOTICE)) {
		purple_message_set_notice(message, TRUE);
	}

	if(IBIS_IS_TAGS(tags)) {
		const char *raw_tag = NULL;

		/* Grab the msgid if one was provided. */
		raw_tag = ibis_tags_lookup(tags, "msgid");
		if(!purple_strempty(raw_tag)) {
			purple_message_set_id(message, raw_tag);
		}

		/* Determine the timestamp of the message. */
		raw_tag = ibis_tags_lookup(tags, "time");
		if(!purple_strempty(raw_tag)) {
			GTimeZone *tz = g_time_zone_new_utc();

			dt = g_date_time_new_from_iso8601(raw_tag, tz);
			g_time_zone_unref(tz);

			purple_message_set_timestamp(message, dt);
			g_date_time_unref(dt);
		}
	}

	/* If the server didn't provide a time, use the current local time. */
	if(dt == NULL) {
		purple_message_set_timestamp_now(message);
	}

	purple_conversation_write_message(conversation, message);

	g_clear_object(&message);

	/* If the message contained a CTCP message and was a PRIVMSG, we then need
	 * to attempt to handle it.
	 */
	if(IBIS_IS_CTCP_MESSAGE(ctcp_message) &&
	   purple_strequal(command, IBIS_MSG_PRIVMSG))
	{
		purple_ircv3_ctcp_handler(connection, client, ibis_message);
	}

	return TRUE;
}

gboolean
purple_ircv3_message_handler_topic(G_GNUC_UNUSED IbisClient *client,
                                   const char *command, IbisMessage *message,
                                   gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleConversation *conversation = NULL;
	GStrv params = NULL;
	const char *channel = NULL;
	const char *topic = NULL;
	guint n_params = 0;

	params = ibis_message_get_params(message);
	n_params = g_strv_length(params);

	if(purple_strequal(command, IBIS_MSG_TOPIC)) {
		if(n_params != 2) {
			g_message("received TOPIC with %u parameters, expected 2",
			          n_params);

			return FALSE;
		}

		channel = params[0];
		topic = params[1];
	} else if(purple_strequal(command, IBIS_RPL_NOTOPIC)) {
		if(n_params != 3) {
			g_message("received RPL_NOTOPIC with %u parameters, expected 3",
			          n_params);

			return FALSE;
		}

		channel = params[1];
		topic = "";
	} else if(purple_strequal(command, IBIS_RPL_TOPIC)) {
		if(n_params != 3) {
			g_message("received RPL_TOPIC with %u parameters, expected 3",
			          n_params);

			return FALSE;
		}

		channel = params[1];
		topic = params[2];
	} else {
		g_message("unexpected command %s", command);

		return FALSE;
	}

	conversation = purple_ircv3_connection_find_or_create_conversation(connection,
	                                                                   channel);
	if(!PURPLE_IS_CONVERSATION(conversation)) {
		g_message("failed to find or create channel '%s'", channel);

		return FALSE;
	}

	purple_conversation_set_topic(conversation, topic);

	return TRUE;
}

gboolean
purple_ircv3_message_handler_quit(G_GNUC_UNUSED IbisClient *client,
                                  G_GNUC_UNUSED const char *command,
                                  IbisMessage *ibis_message,
                                  gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleContact *contact = NULL;
	PurpleContactInfo *info = NULL;
	PurpleConversationManager *manager = NULL;
	GList *conversations = NULL;
	GStrv params = NULL;
	guint n_params = 0;
	char *message = NULL;

	params = ibis_message_get_params(ibis_message);
	n_params = g_strv_length(params);

	contact = purple_ircv3_connection_find_or_create_contact(connection,
	                                                         ibis_message);
	info = PURPLE_CONTACT_INFO(contact);

	if(n_params > 0) {
		char *reason = NULL;

		reason = g_strjoinv(" ", params);
		message = g_strdup_printf("%s has quit (%s)",
		                          purple_contact_info_get_sid(info),
		                          reason);
		g_free(reason);
	} else {
		message = g_strdup_printf("%s has quit",
		                          purple_contact_info_get_sid(info));
	}

	manager = purple_conversation_manager_get_default();
	conversations = purple_conversation_manager_get_all(manager);
	while(conversations != NULL) {
		PurpleConversation *conversation = conversations->data;
		PurpleConversationMembers *members = NULL;

		members = purple_conversation_get_members(conversation);
		purple_conversation_members_remove_member(members, info, TRUE,
		                                          message);

		conversations = g_list_delete_link(conversations, conversations);
	}

	g_free(message);

	return TRUE;
}

gboolean
purple_ircv3_message_handler_nick(G_GNUC_UNUSED IbisClient *client,
                                  G_GNUC_UNUSED const char *command,
                                  IbisMessage *ibis_message,
                                  gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleContact *contact = NULL;
	PurpleContactInfo *info = NULL;
	IbisTags *tags = NULL;
	GStrv params = NULL;
	guint n_params = 0;
	char *new_source = NULL;
	char *user = NULL;
	char *host = NULL;
	const char *source = NULL;
	const char *nick = NULL;

	params = ibis_message_get_params(ibis_message);
	n_params = g_strv_length(params);

	if(n_params != 1) {
		g_message("received NICK with %d params, expected 1", n_params);
		return FALSE;
	}

	nick = params[0];
	source = ibis_message_get_source(ibis_message);
	ibis_source_parse(source, NULL, &user, &host);
	new_source = ibis_source_serialize(nick, user, host);
	g_clear_pointer(&user, g_free);
	g_clear_pointer(&host, g_free);

	contact = purple_ircv3_connection_find_or_create_contact(connection,
	                                                         ibis_message);
	info = PURPLE_CONTACT_INFO(contact);

	/* If the account tag doesn't exist, we need to update the id property of
	 * the contact.
	 */
	tags = ibis_message_get_tags(ibis_message);
	if(!ibis_tags_exists(tags, IBIS_TAG_ACCOUNT)) {
		purple_contact_info_set_id(info, nick);
	}

	purple_contact_info_set_display_name(info, nick);
	purple_contact_info_set_sid(info, new_source);
	g_clear_pointer(&new_source, g_free);

	return TRUE;
}

gboolean
purple_ircv3_message_handler_error(IbisClient *client,
                                   G_GNUC_UNUSED const char *command,
                                   IbisMessage *ibis_message,
                                   gpointer data)
{
	PurpleIRCv3Connection *v3_connection = data;
	PurpleAccount *account = NULL;
	PurpleConnection *connection = data;
	GError *error = NULL;
	GStrv params = NULL;
	guint n_params = 0;
	char *reason = NULL;

	account = purple_connection_get_account(connection);

	params = ibis_message_get_params(ibis_message);
	n_params = g_strv_length(params);

	if(n_params > 0) {
		reason = g_strjoinv(" ", params);
	} else {
		reason = g_strdup(_("unknown error"));
	}

	purple_ircv3_connection_write_status_message(v3_connection, ibis_message,
	                                             TRUE);

	error = g_error_new_literal(PURPLE_IRCV3_DOMAIN, 0, reason);
	g_clear_pointer(&reason, g_free);

	ibis_client_stop(client, NULL);

	purple_account_disconnect_with_error(account, error);

	return TRUE;
}

gboolean
purple_ircv3_message_handler_wallops(G_GNUC_UNUSED IbisClient *client,
                                     G_GNUC_UNUSED const char *command,
                                     IbisMessage *ibis_message,
                                     gpointer data)
{
	PurpleIRCv3Connection *v3_connection = data;
	PurpleAccount *account = NULL;
	PurpleConnection *connection = data;
	PurpleContact *contact = NULL;
	PurpleContactInfo *info = NULL;
	PurpleNotification *notification = NULL;
	PurpleNotificationManager *manager = NULL;
	GStrv params = NULL;
	char *wallops_title = NULL;
	guint n_params = 0;

	params = ibis_message_get_params(ibis_message);
	n_params = g_strv_length(params);

	if(n_params != 1) {
		g_message("received WALLOPS with %u params, expected 1", n_params);
		return FALSE;
	}

	contact = purple_ircv3_connection_find_or_create_contact(v3_connection,
	                                                         ibis_message);
	info = PURPLE_CONTACT_INFO(contact);

	wallops_title = g_strdup_printf(_("WALLOPS from %s"),
	                                purple_contact_info_get_name_for_display(info));
	notification = purple_notification_new(NULL, wallops_title);
	g_free(wallops_title);

	account = purple_connection_get_account(connection);
	purple_notification_set_account(notification, account);
	purple_notification_set_subtitle(notification, params[0]);
	purple_notification_set_icon_name(notification, PURPLE_IRCV3_ICON_NAME);

	manager = purple_notification_manager_get_default();
	purple_notification_manager_add(manager, notification);
	g_clear_object(&notification);

	return TRUE;
}

gboolean
purple_ircv3_message_handler_kick(G_GNUC_UNUSED IbisClient *client,
                                  G_GNUC_UNUSED const char *command,
                                  IbisMessage *ibis_message,
                                  gpointer data)
{
	PurpleIRCv3Connection *v3_connection = data;
	PurpleAccount *account = NULL;
	PurpleContact *kicker = NULL;
	PurpleContact *kickee = NULL;
	PurpleContactInfo *me = NULL;
	PurpleConversation *conversation = NULL;
	GStrv params = NULL;
	char *reason = NULL;
	const char *kickee_id = NULL;
	const char *me_id = NULL;
	guint n_params = 0;

	params = ibis_message_get_params(ibis_message);
	n_params = g_strv_length(params);

	if(n_params < 2) {
		g_message("received KICK with %u params, need at least 2", n_params);
		return FALSE;
	}

	account = purple_connection_get_account(PURPLE_CONNECTION(v3_connection));
	me = purple_account_get_contact_info(account);
	me_id = purple_contact_info_get_id(me);

	kicker = purple_ircv3_connection_find_or_create_contact(v3_connection,
	                                                        ibis_message);

	conversation = purple_ircv3_connection_find_or_create_conversation(v3_connection,
	                                                                   params[0]);

	kickee = purple_ircv3_connection_find_or_create_contact_from_nick(v3_connection,
	                                                                  params[1]);
	kickee_id = purple_contact_info_get_id(PURPLE_CONTACT_INFO(kickee));

	if(n_params > 2) {
		reason = g_strjoinv(" ", params + 2);
	} else {
		reason = g_strdup(_("no reason"));
	}

	if(purple_strequal(kickee_id, me_id)) {
		GError *error = NULL;

		error = g_error_new(PURPLE_IRCV3_DOMAIN, 0,
		                    _("You were kicked from %s by %s: %s"),
		                    params[0],
		                    purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(kicker)),
		                    reason);
		purple_conversation_set_error(conversation, error);
		g_clear_error(&error);

		purple_conversation_set_online(conversation, FALSE);
	} else {
		PurpleConversationMembers *members = NULL;
		char *contents = NULL;

		contents = g_strdup_printf(_("%s kicked %s from %s: %s"),
		                           purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(kicker)),
		                           purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(kickee)),
		                           params[0],
		                           reason);

		members = purple_conversation_get_members(conversation);
		purple_conversation_members_remove_member(members,
		                                          PURPLE_CONTACT_INFO(kickee),
		                                          TRUE,
		                                          contents);
		g_free(contents);
	}

	g_free(reason);

	return TRUE;
}
