/*
 * Integration with Unity's messaging menu and launcher
 * Copyright (C) 2013 Ankit Vani <a@nevitus.org>
 *
 * 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 <glib.h>
#include <glib/gi18n-lib.h>

#include <purple.h>

#include <pidgin.h>

#include <unity.h>
#include <messaging-menu.h>

#define UNITY_PLUGIN_ID "gtk-unity-integration"
#define SETTINGS_SCHEMA_ID "im.pidgin.Pidgin.plugin.Unity"

static MessagingMenuApp *mmapp = NULL;
static UnityLauncherEntry *launcher = NULL;
static guint n_sources = 0;
static gint launcher_count;
static gint messaging_menu_text;
static gboolean alert_chat_nick = TRUE;

enum {
	LAUNCHER_COUNT_DISABLE,
	LAUNCHER_COUNT_MESSAGES,
	LAUNCHER_COUNT_SOURCES,
};

enum {
	MESSAGING_MENU_COUNT,
	MESSAGING_MENU_TIME,
};

static int attach_signals(PurpleConversation *conv);
static void detach_signals(PurpleConversation *conv);

static void
update_launcher(void)
{
	guint count = 0;
	GList *convs = NULL;
	g_return_if_fail(launcher != NULL);
	if (launcher_count == LAUNCHER_COUNT_DISABLE)
		return;

	if (launcher_count == LAUNCHER_COUNT_MESSAGES) {
		PurpleConversationManager *manager = NULL;

		manager = purple_conversation_manager_get_default();
		convs = purple_conversation_manager_get_all(manager);

		while(convs != NULL) {
			PurpleConversation *conv = convs->data;
			count += GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv),
					"unity-message-count"));
			convs = g_list_delete_link(convs, convs);
		}
	} else {
		count = n_sources;
	}

	if (launcher != NULL) {
		if (count > 0)
			unity_launcher_entry_set_count_visible(launcher, TRUE);
		else
			unity_launcher_entry_set_count_visible(launcher, FALSE);
		unity_launcher_entry_set_count(launcher, count);
	}
}

static gchar *
conversation_id(PurpleConversation *conv)
{
	PurpleAccount *account = purple_conversation_get_account(conv);
	const char *type = "misc";

	if(PURPLE_IS_IM_CONVERSATION(conv)) {
		type = "im";
	} else if(PURPLE_IS_CHAT_CONVERSATION(conv)) {
		type = "chat";
	}

	return g_strdup_printf("%s:%s:%s:%s",
	                       type,
	                       purple_conversation_get_name(conv),
	                       purple_account_get_username(account),
	                       purple_account_get_protocol_id(account));
}

static void
messaging_menu_add_conversation(PurpleConversation *conv, gint count)
{
	gchar *id;
	g_return_if_fail(count > 0);
	id = conversation_id(conv);

	/* GBytesIcon may be useful for messaging menu source icons using buddy
	   icon data for IMs */
	if (!messaging_menu_app_has_source(mmapp, id))
		messaging_menu_app_append_source(mmapp, id, NULL,
				purple_conversation_get_title(conv));

	if (messaging_menu_text == MESSAGING_MENU_TIME)
		messaging_menu_app_set_source_time(mmapp, id, g_get_real_time());
	else if (messaging_menu_text == MESSAGING_MENU_COUNT)
		messaging_menu_app_set_source_count(mmapp, id, count);
	messaging_menu_app_draw_attention(mmapp, id);

	g_free(id);
}

static void
messaging_menu_remove_conversation(PurpleConversation *conv)
{
	gchar *id = conversation_id(conv);
	if (messaging_menu_app_has_source(mmapp, id))
		messaging_menu_app_remove_source(mmapp, id);
	g_free(id);
}

static void
refill_messaging_menu(void)
{
	PurpleConversationManager *manager = NULL;
	GList *convs;

	manager = purple_conversation_manager_get_default();
	convs = purple_conversation_manager_get_all(manager);
	while(convs != NULL) {
		PurpleConversation *conv = convs->data;
		messaging_menu_add_conversation(conv,
			GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv),
			"unity-message-count")));
		convs = g_list_delete_link(convs, convs);
	}
}

static int
alert(PurpleConversation *conv)
{
	gint count;
	PidginConversationOld *gtkconv = NULL;
	PidginDisplayWindow *displaywin = NULL;
	GtkRoot *root = NULL;
	GtkWidget *win = NULL;

	if (conv == NULL || PIDGIN_CONVERSATION_OLD(conv) == NULL)
		return 0;

	gtkconv = PIDGIN_CONVERSATION_OLD(conv);
	root = gtk_widget_get_root(gtkconv->tab_cont);
	win = GTK_WIDGET(root);
	displaywin = PIDGIN_DISPLAY_WINDOW(win);

	if (!gtk_widget_has_focus(win) ||
		!pidgin_display_window_conversation_is_selected(displaywin, conv))
	{
		count = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv),
				"unity-message-count"));
		if (!count++)
			++n_sources;

		g_object_set_data(G_OBJECT(conv), "unity-message-count",
				GINT_TO_POINTER(count));
		messaging_menu_add_conversation(conv, count);
		update_launcher();
	}

	return 0;
}

static void
unalert(PurpleConversation *conv)
{
	if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "unity-message-count")) > 0)
		--n_sources;

	g_object_set_data(G_OBJECT(conv), "unity-message-count",
			GINT_TO_POINTER(0));
	messaging_menu_remove_conversation(conv);
	update_launcher();
}

static int
unalert_cb(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data,
           PurpleConversation *conv)
{
	unalert(conv);
	return 0;
}

static gboolean
message_displayed_cb(PurpleConversation *conv, PurpleMessage *msg,
                     G_GNUC_UNUSED gpointer data)
{
	PurpleMessageFlags flags = purple_message_get_flags(msg);

	if ((PURPLE_IS_CHAT_CONVERSATION(conv) && alert_chat_nick &&
			!(flags & PURPLE_MESSAGE_NICK)))
		return FALSE;

	if ((flags & PURPLE_MESSAGE_RECV) && !(flags & PURPLE_MESSAGE_DELAYED))
		alert(conv);

	return FALSE;
}

static void
im_sent_im(PurpleAccount *account, PurpleMessage *msg,
           G_GNUC_UNUSED gpointer data)
{
	PurpleConversation *im = NULL;
	PurpleConversationManager *manager = NULL;

	manager = purple_conversation_manager_get_default();

	im = purple_conversation_manager_find_im(manager, account,
	                                         purple_message_get_recipient(msg));

	unalert(im);
}

static void
chat_sent_im(PurpleAccount *account, G_GNUC_UNUSED PurpleMessage *msg, int id)
{
	PurpleConversation *chat = NULL;
	PurpleConversationManager *manager = NULL;

	manager = purple_conversation_manager_get_default();

	chat = purple_conversation_manager_find_chat_by_id(manager, account, id);

	unalert(chat);
}

static void
conv_created(PurpleConversation *conv)
{
	g_object_set_data(G_OBJECT(conv), "unity-message-count",
			GINT_TO_POINTER(0));
	attach_signals(conv);
}

static void
deleting_conv(PurpleConversation *conv)
{
	detach_signals(conv);
	unalert(conv);
}

static void
message_source_activated(G_GNUC_UNUSED MessagingMenuApp *app,
                         const gchar *id,
                         G_GNUC_UNUSED gpointer user_data)
{
	gchar **sections = g_strsplit(id, ":", 0);
	PurpleConversation *conv = NULL;
	PurpleConversationManager *conversation_manager = NULL;
	PurpleAccount *account;
	PurpleAccountManager *account_manager = NULL;

	char *type     = sections[0];
	char *cname    = sections[1];
	char *aname    = sections[2];
	char *protocol = sections[3];

	conversation_manager = purple_conversation_manager_get_default();

	account_manager = purple_account_manager_get_default();
	account = purple_account_manager_find(account_manager, aname, protocol);

	if (g_strcmp0(type, "im") == 0) {
		conv = purple_conversation_manager_find_im(conversation_manager,
		                                           account, cname);
	} else if (g_strcmp0(type, "chat") == 0) {
		conv = purple_conversation_manager_find_chat(conversation_manager,
		                                             account, cname);
	} else {
		conv = purple_conversation_manager_find(conversation_manager,
		                                        account, cname);
	}

	g_clear_object(&account);

	if (conv) {
		GtkRoot *root = NULL;
		GtkWidget *win = NULL;
		PidginDisplayWindow *displaywin = NULL;

		root = gtk_widget_get_root(PIDGIN_CONVERSATION_OLD(conv)->tab_cont);
		win = GTK_WIDGET(root);
		displaywin = PIDGIN_DISPLAY_WINDOW(win);

		unalert(conv);

		pidgin_display_window_select(displaywin, conv);

		gtk_root_set_focus(root, PIDGIN_CONVERSATION_OLD(conv)->entry);
	}
	g_strfreev (sections);
}

static PurpleSavedStatus *
create_transient_status(PurpleStatusPrimitive primitive,
                        PurpleStatusType *status_type)
{
	PurpleSavedStatus *saved_status = purple_savedstatus_new(NULL, primitive);

	if(status_type != NULL) {
		PurpleAccountManager *manager = NULL;
		GList *active_accts = NULL;

		manager = purple_account_manager_get_default();
		active_accts = purple_account_manager_get_enabled(manager);

		while(active_accts != NULL) {
			PurpleAccount *account = PURPLE_ACCOUNT(active_accts->data);

			purple_savedstatus_set_substatus(saved_status, account,
			                                 status_type, NULL);

			active_accts = g_list_delete_link(active_accts, active_accts);
		}
	}

	return saved_status;
}

static void
status_changed_cb(PurpleSavedStatus *saved_status)
{
	MessagingMenuStatus status = MESSAGING_MENU_STATUS_AVAILABLE;

	switch (purple_savedstatus_get_primitive_type(saved_status)) {
	case PURPLE_STATUS_AVAILABLE:
	case PURPLE_STATUS_UNSET:
		status = MESSAGING_MENU_STATUS_AVAILABLE;
		break;

	case PURPLE_STATUS_AWAY:
	case PURPLE_STATUS_EXTENDED_AWAY:
		status = MESSAGING_MENU_STATUS_AWAY;
		break;

	case PURPLE_STATUS_INVISIBLE:
		status = MESSAGING_MENU_STATUS_INVISIBLE;
		break;

	case PURPLE_STATUS_MOBILE:
	case PURPLE_STATUS_OFFLINE:
		status = MESSAGING_MENU_STATUS_OFFLINE;
		break;

	case PURPLE_STATUS_UNAVAILABLE:
		status = MESSAGING_MENU_STATUS_BUSY;
		break;

	default:
		g_assert_not_reached();
	}
	messaging_menu_app_set_status(mmapp, status);
}

static void
messaging_menu_status_changed(G_GNUC_UNUSED MessagingMenuApp *mmapp,
                              MessagingMenuStatus mm_status,
                              G_GNUC_UNUSED gpointer user_data)
{
	PurpleSavedStatus *saved_status;
	PurpleStatusPrimitive primitive = PURPLE_STATUS_UNSET;

	switch (mm_status) {
	case MESSAGING_MENU_STATUS_AVAILABLE:
		primitive = PURPLE_STATUS_AVAILABLE;
		break;

	case MESSAGING_MENU_STATUS_AWAY:
		primitive = PURPLE_STATUS_AWAY;
		break;

	case MESSAGING_MENU_STATUS_BUSY:
		primitive = PURPLE_STATUS_UNAVAILABLE;
		break;

	case MESSAGING_MENU_STATUS_INVISIBLE:
		primitive = PURPLE_STATUS_INVISIBLE;
		break;

	case MESSAGING_MENU_STATUS_OFFLINE:
		primitive = PURPLE_STATUS_OFFLINE;
		break;

	default:
		g_assert_not_reached();
	}

	saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL);
	if (saved_status == NULL)
		saved_status = create_transient_status(primitive, NULL);
	purple_savedstatus_activate(saved_status);
}

static void
settings_change_cb(GSettings *settings, char *key, G_GNUC_UNUSED gpointer data)
{
	if(purple_strequal(key, "alert-chat-nick")) {
		alert_chat_nick = g_settings_get_boolean(settings, key);
	} else if(purple_strequal(key, "launcher-count")) {
		launcher_count = g_settings_get_enum(settings, key);
		if(launcher_count == LAUNCHER_COUNT_DISABLE) {
			unity_launcher_entry_set_count_visible(launcher, FALSE);
		} else {
			update_launcher();
		}
	} else if(purple_strequal(key, "messaging-menu-text")) {
		messaging_menu_text = g_settings_get_enum(settings, key);
		refill_messaging_menu();
	} else {
		g_warning("Got unity-integration settings change for unknown key: %s",
		          key);
	}
}

static int
attach_signals(PurpleConversation *conv)
{
	PidginConversationOld *gtkconv = NULL;
	guint id;

	gtkconv = PIDGIN_CONVERSATION_OLD(conv);
	if (!gtkconv)
		return 0;

	id = g_signal_connect(G_OBJECT(gtkconv->entry), "focus-in-event",
			G_CALLBACK(unalert_cb), conv);
	g_object_set_data(G_OBJECT(conv), "unity-entry-signal", GUINT_TO_POINTER(id));

	id = g_signal_connect(G_OBJECT(gtkconv->history), "focus-in-event",
			G_CALLBACK(unalert_cb), conv);
	g_object_set_data(G_OBJECT(conv), "unity-history-signal", GUINT_TO_POINTER(id));

	return 0;
}

static void
detach_signals(PurpleConversation *conv)
{
	PidginConversationOld *gtkconv = NULL;
	guint id;
	gtkconv = PIDGIN_CONVERSATION_OLD(conv);
	if (!gtkconv)
		return;

	id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "unity-history-signal"));
	g_signal_handler_disconnect(gtkconv->history, id);

	id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "unity-entry-signal"));
	g_signal_handler_disconnect(gtkconv->entry, id);

	g_object_set_data(G_OBJECT(conv), "unity-message-count",
			GINT_TO_POINTER(0));
}

static GPluginPluginInfo *
unity_query(G_GNUC_UNUSED GError **error)
{
	const gchar * const authors[] = {
		"Ankit Vani <a@nevitus.org>",
		NULL
	};

	return purple_plugin_info_new(
		"id",                   UNITY_PLUGIN_ID,
		"name",                 N_("Unity Integration"),
		"version",              DISPLAY_VERSION,
		"category",             N_("Notification"),
		"summary",              N_("Provides integration with Unity."),
		"description",          N_("Provides integration with Unity's "
		                           "messaging menu and launcher."),
		"authors",              authors,
		"website",              PURPLE_WEBSITE,
		"abi-version",          PURPLE_ABI_VERSION,
		"settings-schema",      SETTINGS_SCHEMA_ID,
		NULL
	);
}

static gboolean
unity_load(GPluginPlugin *plugin, G_GNUC_UNUSED GError **error) {
	GSettings *settings = NULL;
	GList *convs = NULL;
	PurpleConversationManager *manager = NULL;
	PurpleSavedStatus *saved_status;
	void *conv_handle = purple_conversations_get_handle();
	void *gtk_conv_handle = pidgin_conversations_get_handle();
	void *savedstat_handle = purple_savedstatuses_get_handle();

	settings = g_settings_new_with_backend(SETTINGS_SCHEMA_ID,
	                                       purple_core_get_settings_backend());
	g_signal_connect(settings, "changed",
	                 G_CALLBACK(settings_change_cb), NULL);

	alert_chat_nick = g_settings_get_boolean(settings, "alert-chat-nick");

	mmapp = messaging_menu_app_new("pidgin.desktop");
	g_object_ref(mmapp);
	messaging_menu_app_register(mmapp);
	messaging_menu_text = g_settings_get_enum(settings, "messaging-menu-text");

	g_signal_connect(mmapp, "activate-source",
			G_CALLBACK(message_source_activated), NULL);
	g_signal_connect(mmapp, "status-changed",
			G_CALLBACK(messaging_menu_status_changed), NULL);

	saved_status = purple_savedstatus_get_current();
	status_changed_cb(saved_status);

	purple_signal_connect(savedstat_handle, "savedstatus-changed", plugin,
			G_CALLBACK(status_changed_cb), NULL);

	launcher = unity_launcher_entry_get_for_desktop_id("pidgin.desktop");
	g_object_ref(launcher);
	launcher_count = g_settings_get_enum(settings, "launcher-count");

	purple_signal_connect(gtk_conv_handle, "displayed-im-msg", plugin,
			G_CALLBACK(message_displayed_cb), NULL);
	purple_signal_connect(gtk_conv_handle, "displayed-chat-msg", plugin,
			G_CALLBACK(message_displayed_cb), NULL);
	purple_signal_connect(conv_handle, "sent-im-msg", plugin,
			G_CALLBACK(im_sent_im), NULL);
	purple_signal_connect(conv_handle, "sent-chat-msg", plugin,
			G_CALLBACK(chat_sent_im), NULL);
	purple_signal_connect(conv_handle, "conversation-created", plugin,
			G_CALLBACK(conv_created), NULL);
	purple_signal_connect(conv_handle, "deleting-conversation", plugin,
			G_CALLBACK(deleting_conv), NULL);

	manager = purple_conversation_manager_get_default();
	convs = purple_conversation_manager_get_all(manager);
	while(convs != NULL) {
		PurpleConversation *conv = PURPLE_CONVERSATION(convs->data);
		attach_signals(conv);
		convs = g_list_delete_link(convs, convs);
	}

	g_object_unref(settings);

	return TRUE;
}

static gboolean
unity_unload(G_GNUC_UNUSED GPluginPlugin *plugin,
             G_GNUC_UNUSED gboolean shutdown,
             G_GNUC_UNUSED GError **error)
{
	GList *convs = NULL;
	PurpleConversationManager *manager = NULL;

	manager = purple_conversation_manager_get_default();
	convs = purple_conversation_manager_get_all(manager);

	while(convs != NULL) {
		PurpleConversation *conv = PURPLE_CONVERSATION(convs->data);
		unalert(conv);
		detach_signals(conv);
		convs = g_list_delete_link(convs, convs);
	}

	unity_launcher_entry_set_count_visible(launcher, FALSE);
	messaging_menu_app_unregister(mmapp);

	g_object_unref(launcher);
	g_object_unref(mmapp);
	return TRUE;
}

GPLUGIN_NATIVE_PLUGIN_DECLARE(unity)
