/*
 * Copyright(C) 2013 Canonical Ltd.
 *
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 *
 * Authored by Ken VanDine <ken.vandine@canonical.com>
 */

#include <glib.h>
#include <gio/gdesktopappinfo.h>

#include <glib/gi18n.h>

#include <gtk/gtk.h>
#include <gdk/gdkx.h>

#include <telepathy-glib/telepathy-glib.h>
#include <telepathy-glib/simple-approver.h>

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

static MessagingMenuApp *mmapp;
GHashTable *dispatch_ops;
static gint n_sources;
UnityLauncherEntry *launcher = NULL;
GHashTable *conns; /* connection object -> set of mmapp ids for that connection */
TpBaseClient *approver;
TpBaseClient *observer;

static void update_launcher (gint count)
{
	if (launcher != NULL)
	{
		g_debug ("unity launcher: count is %d", count);
		if (count > 0)
		{
			g_debug ("unity launcher: setting count to %d", count);
			unity_launcher_entry_set_count (launcher, count);
			unity_launcher_entry_set_count_visible (launcher, TRUE);
		} else {
			unity_launcher_entry_set_count (launcher, count);
			g_debug ("unity launcher: hiding count");
			unity_launcher_entry_set_count_visible (launcher, FALSE);
		}
	}
}

void
remember_id_for_connection (TpConnection *con,
			    const gchar  *id)
{
	GHashTable *ids;

	ids = g_hash_table_lookup (conns, con);
	if (ids != NULL)
		g_hash_table_add (ids, g_strdup (id));
	else
		g_warning ("%s: never seen chanel '%s' before", G_STRFUNC, id);
}

static gchar *
channel_id (TpChannel *channel)
{
	TpConnection *con;
	gchar *id;

	con = tp_channel_get_connection (channel);
	id = g_strconcat ("channel:",
			  tp_proxy_get_object_path (TP_PROXY (con)), ":",
			  tp_proxy_get_object_path (TP_PROXY (channel)),
			  NULL);

	remember_id_for_connection (con, id);

	return id;
}

static gchar *
contact_id (TpContact *contact)
{
	TpConnection *con;
	gchar *id;

	con = tp_contact_get_connection (contact);
	id = g_strconcat ("contact:",
			  tp_proxy_get_object_path (TP_PROXY (con)), ":",
			  tp_contact_get_identifier (contact),
			  NULL);

	remember_id_for_connection (con, id);

	return id;
}

static void
dispatch_op_finished_cb (TpProxy *self, guint domain, gint code, gchar *message, gpointer user_data)
{
	TpChannelDispatchOperation *dispatch_op = TP_CHANNEL_DISPATCH_OPERATION (self);
	GPtrArray *channels;

	channels = tp_channel_dispatch_operation_get_channels (dispatch_op);
	if (channels != NULL && channels->len)
	{
		TpChannel *channel;
		gchar *id;

		channel = g_ptr_array_index (channels, 0);
		id = channel_id (channel);

		if (mmapp)
			messaging_menu_app_remove_source (mmapp, id);
		g_hash_table_remove (dispatch_ops, id);
		update_launcher (n_sources--);

		g_free (id);
	}
}

static void
handle_with_cb (GObject *self, GAsyncResult *result, gpointer user_data)
{
	TpChannelDispatchOperation *dispatch_op = TP_CHANNEL_DISPATCH_OPERATION (self);
	GError *error = NULL;
	TpChannel *channel;
	GPtrArray *channels;
	gchar *id;

	channels = tp_channel_dispatch_operation_get_channels (dispatch_op);
	channel = g_ptr_array_index (channels, 0);
	id = channel_id (channel);

	if (!tp_channel_dispatch_operation_handle_with_finish (dispatch_op, result, &error))
	{
                if (error)
		{
			g_warning ("Failed to handle operations: %s\n", error->message);
			g_error_free (error);
			if (mmapp)
				messaging_menu_app_remove_source (mmapp, id);
			update_launcher (n_sources--);
		}
	}

	g_free (id);
}

static void
observer_handle_with_cb (GObject *self, GAsyncResult *result, gpointer user_data)
{
	g_debug ("observer_handle_with_cb");

	TpChannelDispatcher *cd = TP_CHANNEL_DISPATCHER (self);
	GError *error = NULL;

	tp_channel_dispatcher_present_channel_finish (cd, result, &error);
	if (error)
	{
		g_warning ("Failed to handle operations: %s\n", error->message);
		g_error_free (error);
	}
}

static void
contact_request_subscription_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{ 
	TpContact *contact = (TpContact *) source;
	GError *error = NULL;

	if (!tp_contact_request_subscription_finish (contact, result, &error))
	{
		g_debug ("Failed to request_subscription on %s\n", tp_contact_get_identifier (contact));
		g_error_free (error);
	}
}

static void
contact_authorize_publication_cb (GObject *source, GAsyncResult *result, gpointer user_data) 
{ 
        TpContact *contact = (TpContact *) source;
        GError *error = NULL; 

        if (!tp_contact_authorize_publication_finish (contact, result, &error))
        {
                g_debug ("Failed to authorize_publication on %s\n", tp_contact_get_identifier (contact)); 
                g_error_free (error);
        }
}

static void
contact_unblock_cb (GObject *source, GAsyncResult *result, gpointer user_data) 
{ 
        TpContact *contact = (TpContact *) source;
        GError *error = NULL; 

        if (!tp_contact_unblock_finish (contact, result, &error))
        {
                g_debug ("Failed to unblock on %s\n", tp_contact_get_identifier (contact)); 
                g_error_free (error);
        }
}

static void 
contact_remove_cb (GObject *source, GAsyncResult *result, gpointer user_data)             
{
        TpContact *contact = (TpContact *) source;
        GError *error = NULL; 

	g_debug ("contact_remove_cb");

        if (!tp_contact_remove_finish (contact, result, &error))
        {
                g_debug ("Failed to remove on %s\n", tp_contact_get_identifier (contact));
                g_error_free (error);
        }
}

void
contact_add_to_contact_list (TpContact *contact)
{
	g_return_if_fail (contact != NULL);
	g_debug ("contact_add_to_contact_list");
	const gchar *message;
	message = tp_contact_get_publish_request (contact);
	tp_contact_request_subscription_async (contact, message, contact_request_subscription_cb, NULL);
	tp_contact_authorize_publication_async (contact, contact_authorize_publication_cb, NULL);
	tp_contact_unblock_async (contact, contact_unblock_cb, NULL);
}

void
contact_remove_from_contact_list (TpContact *contact)
{
	g_return_if_fail (contact != NULL);
	g_debug ("contact_remove_from_contact_list");
	tp_contact_remove_async (contact, contact_remove_cb, NULL);
}

static void
contact_add_response_cb (GtkWidget *dialog, gint response, TpContact *contact)
{
	g_debug ("contact_add_response_cb");

	gtk_widget_destroy (dialog);
	dialog = NULL;

	if (response != GTK_RESPONSE_ACCEPT)
		contact_remove_from_contact_list (contact);

	else
		contact_add_to_contact_list (contact);
}

static void
handle_contacts_subscription_add_indicator_cb (GObject     *object,
					       GAsyncResult *result,
					       gpointer      user_data)
{
	TpContact *contact;
	GtkWidget *dialog;
	const gchar *alias = "";
	gchar *title;
	const gchar *message;
	GFile *avatar = NULL;
	GdkPixbuf *pixbuf = NULL;
	GtkWidget *image = NULL;
	GError *error = NULL;

	contact = tp_connection_dup_contact_by_id_finish (TP_CONNECTION (object), result, &error);
	if (contact == NULL)
	{
		g_warning ("unable to find contact: %s", error->message);
		g_error_free (error);
		return;
	}

	message = tp_contact_get_publish_request (contact);

	alias = tp_contact_get_alias (contact);

	g_debug ("Getting avatar for %s", alias);

	avatar = tp_contact_get_avatar_file (contact);

	if (avatar)
	{
		pixbuf = gdk_pixbuf_new_from_file_at_scale (g_file_get_path (avatar), 48, 48, TRUE, &error);
	}

	if (error)
        {
		g_warning ("Failed to create pixbuf: %s", error->message);
		g_error_free (error);
	}

	if (pixbuf)
        {
		image = gtk_image_new_from_pixbuf (pixbuf);
		g_object_unref(G_OBJECT(pixbuf));
	}


	dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);

	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("%s would like permission to see when you are online"), alias);

	title = g_strdup_printf (_("Subscription Request"));
	gtk_window_set_title (GTK_WINDOW (dialog), title);
	g_free (title);

	if (image)
	{
		gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
		gtk_widget_show (image);
	}

	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);

	gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Decline"), GTK_RESPONSE_REJECT);

	gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Accept"), GTK_RESPONSE_ACCEPT);

	g_signal_connect (dialog, "response", G_CALLBACK (contact_add_response_cb), contact);

	gtk_widget_show (GTK_WIDGET (dialog));

	if (mmapp) {
		gchar *id;

		id = contact_id (contact);
		messaging_menu_app_remove_source (mmapp, id);

		g_free (id);
	}
}

static TpConnection *
get_tp_connection (TpAccountManager *tp_am,
		    const gchar      *object_path)
{
	TpSimpleClientFactory *factory;
	TpConnection *con;
	GError *error = NULL;

	factory = tp_proxy_get_factory (tp_am);

	con = tp_simple_client_factory_ensure_connection (factory, object_path, NULL, &error);
	if (con == NULL)
	{
		g_warning ("cannot find telepathy connection '%s': %s",
			   object_path, error->message);
		g_error_free (error);
	}

	return con;
}

static TpChannel *
get_tp_channel (TpAccountManager *tp_am,
		 const gchar *connection_object_path,
		 const gchar *channel_object_path)
{
	TpSimpleClientFactory *factory;
	TpConnection *con;
	TpChannel *channel;
	GError *error = NULL;

	factory = tp_proxy_get_factory (tp_am);

	con = get_tp_connection (tp_am, connection_object_path);
	if (con == NULL)
		return NULL;

	channel = tp_simple_client_factory_ensure_channel (factory, con, channel_object_path, NULL, &error);
	if (channel == NULL)
	{
		g_warning ("cannot find telepathy channel '%s': %s",
			   channel_object_path, error->message);
		g_error_free (error);
	}

	g_object_unref (con);
	return channel;
}

static TpChannelDispatcher *
get_tp_channel_dispatcher ()
{
	TpDBusDaemon *bus;
	TpChannelDispatcher *dispatcher;
	GError *error = NULL;

	bus = tp_dbus_daemon_dup (&error);
	if (bus == NULL)
	{
		g_warning ("Failed to get Daemon: %s\n", error->message);
		g_error_free (error);
		return NULL;
	}

	dispatcher = tp_channel_dispatcher_new (bus);

	g_object_unref (bus);
	return dispatcher;
}

static void
message_source_activated (MessagingMenuApp *app,
			  const gchar      *id,
			  gpointer          user_data)
{
	TpAccountManager *tp_am;
	gchar **sections;

	tp_am = tp_account_manager_dup ();

	/* message source ids contain the type of source, the
	 * tpconnection's object path, and the object path of the actual
	 * source object, separated by colons */
	sections = g_strsplit (id, ":", 0);
	if (g_strv_length (sections) != 3)
		goto out;

	if (g_str_equal (sections[0], "channel"))
	{
		TpChannelDispatchOperation *dispatch_op;

		/* accept dispatch operation if there is one for this channel */
		dispatch_op = g_hash_table_lookup (dispatch_ops, id);
		if (dispatch_op != NULL)
		{
			tp_channel_dispatch_operation_handle_with_async (dispatch_op, NULL, handle_with_cb, NULL);
			g_hash_table_remove (dispatch_ops, id);
		}
		else
		{
			TpChannel *channel;
			TpChannelDispatcher *dispatcher;

			channel = get_tp_channel (tp_am, sections[1], sections[2]);
			dispatcher = get_tp_channel_dispatcher ();
			if (channel != NULL && dispatcher != NULL)
			{
				tp_channel_dispatcher_present_channel_async (dispatcher, channel,
									     g_get_real_time () / G_USEC_PER_SEC,
									     observer_handle_with_cb, NULL);
			}

			g_clear_object (&channel);
			g_clear_object (&dispatcher);
		}
	}
	else if (g_str_equal (sections[0], "contact"))
	{
		TpConnection *con;

		static TpContactFeature features[] = {
			TP_CONTACT_FEATURE_ALIAS,
			TP_CONTACT_FEATURE_AVATAR_DATA,
		};

		con = get_tp_connection (tp_am, sections[1]);
		if (con != NULL)
		{
			tp_connection_dup_contact_by_id_async (con, sections[2], 2, features,
							       handle_contacts_subscription_add_indicator_cb, NULL);
			g_object_unref (con);
		}
	}

out:
	g_strfreev (sections);
	g_object_unref (tp_am);
}

static void
upgrade_contacts_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
	TpChannel *channel = user_data;
	GPtrArray *contacts;
	GError *error = NULL;
	const gchar *alias = "";
	GFile *avatar_file;
	GIcon *avatar = NULL;
	TpContact *contact = NULL;

	if (!tp_connection_upgrade_contacts_finish (TP_CONNECTION (source_object), result, &contacts, &error)) {
		g_warning ("unable to fetch contact information: %s", error->message);
		g_error_free (error);
		return;
	}

	g_return_if_fail (contacts->len == 1);

	contact = g_ptr_array_index (contacts, 0);

	if (contact)
	{
		alias = tp_contact_get_alias (contact);
		avatar_file = tp_contact_get_avatar_file (contact);
		avatar = avatar_file ? g_file_icon_new (avatar_file) : NULL;
		g_debug ("handle_contacts_pending_add_indicator_cb HAS CONTACT alias: %s", alias);
	}
	g_debug ("handle_contacts_pending_add_indicator_cb alias: %s", alias);

	if (mmapp) {
		gchar *id;

		id = channel_id (channel);
		messaging_menu_app_append_source (mmapp, id, avatar, alias);
		messaging_menu_app_draw_attention (mmapp, id);

		g_free (id);
	}

	update_launcher (++n_sources);

	g_clear_object (&avatar);
	g_ptr_array_free (contacts, TRUE);
}

gboolean check_pending_messages (gpointer data)
{
	TpTextChannel *channel = TP_TEXT_CHANNEL (data);
	TpConnection *connection;
	GList *pending;
	TpContact *contact;

	static TpContactFeature features[] = {
		TP_CONTACT_FEATURE_ALIAS,
		TP_CONTACT_FEATURE_AVATAR_DATA,
	};

	pending = tp_text_channel_dup_pending_messages (TP_TEXT_CHANNEL (channel));
	g_debug ("pending: %u", g_list_length (pending));
	if (g_list_length (pending) > 0)
	{
		gchar *id;

		id = channel_id (TP_CHANNEL (channel));

		if (mmapp && !messaging_menu_app_has_source (mmapp, id))
		{
			connection = tp_channel_get_connection (TP_CHANNEL (channel));
			contact = tp_channel_get_initiator_contact (TP_CHANNEL (channel));
			if (contact)
				tp_connection_upgrade_contacts_async (connection, 1, &contact, 2, features, upgrade_contacts_cb, channel);
		}

		g_free (id);
	}
	g_list_free_full (pending, g_object_unref);
	return FALSE;
}

static void
pending_message_removed_cb (TpTextChannel *channel, TpMessage *message, gpointer user_data)
{
	g_debug ("pending_message_removed_cb: %s", tp_channel_get_identifier (TP_CHANNEL (channel)));

	if (mmapp) {
		gchar *id;

		id = channel_id (TP_CHANNEL (channel));
		messaging_menu_app_remove_source (mmapp, id);

		g_free (id);
	}

	update_launcher (--n_sources);
}

static void
message_received_cb (TpTextChannel *channel, TpMessage *message, gpointer user_data)
{
	g_debug ("message_received_cb: %s", tp_channel_get_identifier (TP_CHANNEL (channel)));
	g_timeout_add (100, check_pending_messages, channel);
}

static void
handle_contacts_add_indicator_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
	TpChannelDispatchOperation *dispatch_op = user_data;
	GPtrArray *contacts;
	GError *error = NULL;
	gchar *id;
	TpChannel *channel;
	GPtrArray *channels;
	channels = tp_channel_dispatch_operation_get_channels (dispatch_op);
	channel = g_ptr_array_index (channels, 0);

	if (!tp_connection_upgrade_contacts_finish (TP_CONNECTION (source_object), result, &contacts, &error)) {
		g_warning ("unable to fetch contact information: %s", error->message);
		g_error_free (error);
		return;
	}

	g_return_if_fail (contacts->len == 1);

	id = channel_id (channel);

	if (mmapp) {
		if (messaging_menu_app_has_source (mmapp, id))
		{
			messaging_menu_app_set_source_time (mmapp, id, g_get_real_time ());
		}
		else
		{
			TpContact *contact;
			const gchar *alias = "";
			GFile *avatar_file;
			GIcon *avatar;

			contact = g_ptr_array_index (contacts, 0);
			alias = tp_contact_get_alias (contact);
			avatar_file = tp_contact_get_avatar_file (contact);
			avatar = avatar_file ? g_file_icon_new (avatar_file) : NULL;

			messaging_menu_app_append_source (mmapp, id, avatar, alias);

			g_clear_object (&avatar);
		}

		messaging_menu_app_draw_attention (mmapp, id);
	}

	update_launcher (++n_sources);

	g_hash_table_insert (dispatch_ops, g_strdup (id), dispatch_op);
	g_signal_connect (dispatch_op, "invalidated", G_CALLBACK (dispatch_op_finished_cb), NULL);

	g_free (id);
	g_ptr_array_free (contacts, TRUE);
}

static void
add_dispatch_operation_cb (TpSimpleApprover *self, TpAccount *account, TpConnection *connection,
    GList *channels, TpChannelDispatchOperation *dispatch_op, TpAddDispatchOperationContext *context,
    gpointer user_data)
{
	GList *itr;
	TpChannel *channel;
	TpContact *contact;

	static TpContactFeature features[] = {
		TP_CONTACT_FEATURE_ALIAS,
		TP_CONTACT_FEATURE_AVATAR_DATA,
	};

	g_object_ref (dispatch_op);
	for (itr = channels; itr != NULL; itr = g_list_next (itr))
	{
		channel = itr->data;
		contact = tp_channel_get_initiator_contact (channel);
		tp_connection_upgrade_contacts_async (connection, 1, &contact, 2, features, handle_contacts_add_indicator_cb, dispatch_op);
	}
	g_list_free(itr);
	tp_add_dispatch_operation_context_accept (context);
}

static void
observer_observe_channels_cb (TpSimpleObserver *self, TpAccount *account, TpConnection *connection,
    GList *channels, TpChannelDispatchOperation *dispatch_op, GList *requests, TpObserveChannelsContext *context,
    gpointer user_data)
{
	GList *itr;
	TpChannel *channel;

	for (itr = channels; itr != NULL; itr = g_list_next (itr))
	{
		channel = itr->data;
		if (TP_IS_TEXT_CHANNEL (channel))
		{
			g_debug ("Handling text channel: %s", tp_channel_get_identifier (channel));

			g_object_ref (channel);

			g_signal_connect (channel, "message-received",
				G_CALLBACK (message_received_cb), NULL);
			g_signal_connect (channel, "pending-message-removed",
				G_CALLBACK (pending_message_removed_cb), NULL);

			check_pending_messages (TP_CHANNEL (channel));
		}
	}
	g_list_free(itr);
	tp_observe_channels_context_accept (context);
}

static void
conn_invalidated_cb (TpConnection *conn, guint domain, gint code, gchar *message, gpointer user_data)
{
	g_debug ("conn_invalidated_cb");
	g_hash_table_remove (conns, conn);
}

static void
contact_list_changed_cb (TpConnection *conn, GPtrArray *added, GPtrArray *removed, gpointer user_data)
{
	g_debug ("contact_list_changed_cb");
	gchar *id;
	const gchar *alias = "";
	guint i;
	GFile *avatar_file;
	GIcon *avatar = NULL;

	for (i = 0; i < added->len; i++)
	{
		TpContact *contact = g_ptr_array_index (added, i);
		if (TP_IS_CONTACT (contact))
		{
			TpSubscriptionState state = tp_contact_get_publish_state (contact);
			if (state == TP_SUBSCRIPTION_STATE_ASK)
			{
				g_debug ("Getting avatar for %s", alias);

				id = contact_id (contact);

				if (mmapp) {
					if (messaging_menu_app_has_source (mmapp, id))
					{
						messaging_menu_app_set_source_time (mmapp, id, g_get_real_time ());
					}
					else
					{
						alias = tp_contact_get_alias (contact);
						avatar_file = tp_contact_get_avatar_file (contact);
						avatar = avatar_file ? g_file_icon_new (avatar_file) : NULL;

						messaging_menu_app_append_source (mmapp, id, avatar, alias);
						messaging_menu_app_draw_attention (mmapp, id);

						n_sources++;

						g_clear_object (&avatar);
					}
				}

				g_debug ("contact_list_changed_cb: adding %s", alias);

				g_free (id);
			}
		}
	}

	for (i = 0; i < removed->len; i++)
	{
		TpContact *contact = g_ptr_array_index (removed, i);
		if (TP_IS_CONTACT (contact))
		{
			alias = tp_contact_get_alias (contact);
			g_debug ("contact_list_changed_cb: removing %s", alias);
			contact_remove_from_contact_list (contact);
		}

		if (mmapp)
			messaging_menu_app_remove_source (mmapp, tp_contact_get_identifier (contact));
		n_sources--;
	}

	update_launcher (n_sources);
}


void
check_account (TpAccount *account, TpAccountManager *manager)
{
	g_debug ("check_account");
	TpConnection *connection = tp_account_get_connection (account);
	if (connection == NULL)
		return;

	if (g_hash_table_contains (conns, connection))
		return;

	g_debug ("account_manager_prepared_cb %s", tp_connection_get_cm_name (connection));

	g_hash_table_insert (conns, g_object_ref (connection),
			     g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL));

	tp_g_signal_connect_object (connection, "contact-list-changed",
		G_CALLBACK (contact_list_changed_cb), manager, 0);
	tp_g_signal_connect_object (connection, "invalidated",
		G_CALLBACK (conn_invalidated_cb), manager, 0);
}

static void
account_conn_changed_cb (TpAccount *account, GParamSpec *spec, TpAccountManager *manager)
{
	g_debug ("account_conn_changed_cb");
	g_return_if_fail (TP_IS_ACCOUNT (account));
	check_account (account, manager);
}

void
add_account (TpAccount *account, TpAccountManager *manager)
{
	g_debug ("add_account");
	tp_g_signal_connect_object (account, "notify::connection",
		G_CALLBACK (account_conn_changed_cb), manager, 0);
	if (mmapp != NULL)
		messaging_menu_app_register (mmapp);
	check_account (account, manager);
}

static void
account_enabled_cb (TpAccountManager *manager, TpAccount *account, gboolean valid, gpointer user_data)
{
	g_debug ("account_enabled_cb");
	add_account (account, manager);
}

static void
account_manager_prepared_cb (GObject *object, GAsyncResult *res, gpointer user_data)
{
	TpAccountManager *manager = TP_ACCOUNT_MANAGER (object);
	GList *accounts;
	GError *error = NULL;

	g_debug ("account_manager_prepared_cb");

	if (!tp_proxy_prepare_finish (object, res, &error))
	{
		g_print ("Error preparing AM: %s\n", error->message);
	}

	accounts = tp_account_manager_dup_valid_accounts (manager);
	while (accounts != NULL)
	{
		TpAccount *account = accounts->data;
		g_debug ("account_manager_prepared_cb %s", tp_account_get_path_suffix (account));

		add_account (account, manager);

		accounts = accounts->next;
	}

	tp_g_signal_connect_object (manager, "account-validity-changed",
		G_CALLBACK (account_enabled_cb), manager, 0);

	g_list_free_full (accounts, g_object_unref);
}

void
contact_list_setup (TpAccountManager *tp_am)
{
	g_return_if_fail (TP_IS_ACCOUNT_MANAGER (tp_am));

	TpSimpleClientFactory *factory;
	factory = tp_proxy_get_factory (tp_am);
	tp_simple_client_factory_add_account_features_varargs (factory,
		TP_ACCOUNT_FEATURE_CONNECTION,
		0);
	tp_simple_client_factory_add_connection_features_varargs (factory,
		TP_CONNECTION_FEATURE_CONTACT_LIST,
		0);
	tp_simple_client_factory_add_contact_features_varargs (factory,
		TP_CONTACT_FEATURE_ALIAS,
		TP_CONTACT_FEATURE_CONTACT_GROUPS,
		TP_CONTACT_FEATURE_INVALID);

	tp_proxy_prepare_async (tp_am, NULL, account_manager_prepared_cb,  tp_am);
}

void
most_available_presence_changed_cb (TpAccountManager *manager, guint presence, gchar *status, gchar *message, gpointer user_data)
{
	contact_list_setup (manager);
}

static TpBaseClient *
approver_setup (TpAccountManager *tp_am, GError **error)
{
	g_return_val_if_fail (TP_IS_ACCOUNT_MANAGER (tp_am), NULL);

	approver = tp_simple_approver_new_with_am (tp_am, "IndicatorApprover",
				FALSE, add_dispatch_operation_cb, NULL, NULL);

	/* contact text chat */
	tp_base_client_take_approver_filter (approver, tp_asv_new (
		TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
		TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
		NULL));

	/* room text chat */
	tp_base_client_take_approver_filter (approver, tp_asv_new (
		TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
		TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_ROOM,
		NULL));

	if (!tp_base_client_register (approver, error))
	{
		g_clear_object (&approver);
		return NULL;
	}
	return approver;
}

static TpBaseClient *
observer_setup (TpAccountManager *tp_am, GError **error)
{
	g_return_val_if_fail (TP_IS_ACCOUNT_MANAGER (tp_am), NULL);

	observer = tp_simple_observer_new_with_am (tp_am, TRUE, "IndicatorObserver",
				FALSE, observer_observe_channels_cb, NULL, NULL);

	/* contact text chat */
	tp_base_client_take_observer_filter (observer, tp_asv_new (
		TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
		TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
		NULL));

	if (!tp_base_client_register (observer, error))
	{
		g_clear_object (&observer);
		return NULL;
	}
	return observer;
}

static void
presence_changed (TpAccountManager *manager,
		  guint             presence,
		  gchar            *status,
		  gchar            *message,
		  gpointer          user_data)
{
	MessagingMenuStatus mm_status;

	if (mmapp == NULL)
		return;

	switch (presence)
	{
	case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
		mm_status = MESSAGING_MENU_STATUS_AVAILABLE;
		break;

	case TP_CONNECTION_PRESENCE_TYPE_AWAY:
	case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
		mm_status = MESSAGING_MENU_STATUS_AWAY;
		break;

	case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
		mm_status = MESSAGING_MENU_STATUS_INVISIBLE;
		break;

	case TP_CONNECTION_PRESENCE_TYPE_BUSY:
		mm_status = MESSAGING_MENU_STATUS_BUSY;
		break;

	default:
	case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
	case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
	case TP_CONNECTION_PRESENCE_TYPE_UNSET:
		mm_status = MESSAGING_MENU_STATUS_OFFLINE;
		break;
	}

	messaging_menu_app_set_status (mmapp, mm_status);
}

static void
messaging_menu_status_changed (MessagingMenuApp    *mmapp,
			       MessagingMenuStatus mm_status,
			       gpointer            user_data)
{
	TpAccountManager *tp_am;
	TpConnectionPresenceType status;

	tp_am = tp_account_manager_dup ();

	switch (mm_status)
	{
	case MESSAGING_MENU_STATUS_AVAILABLE:
		status = TP_CONNECTION_PRESENCE_TYPE_AVAILABLE;
		break;

	case MESSAGING_MENU_STATUS_AWAY:
		status = TP_CONNECTION_PRESENCE_TYPE_AWAY;
		break;

	case MESSAGING_MENU_STATUS_BUSY:
		status = TP_CONNECTION_PRESENCE_TYPE_BUSY;
		break;

	case MESSAGING_MENU_STATUS_INVISIBLE:
		status = TP_CONNECTION_PRESENCE_TYPE_HIDDEN;
		break;

	case MESSAGING_MENU_STATUS_OFFLINE:
		status = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
		break;

	default:
		g_assert_not_reached ();
	}

	tp_account_manager_set_all_requested_presences (tp_am, status, "", "");

	g_object_unref (tp_am);
}

gboolean check_enabled_accounts ()
{
	gboolean r = FALSE;
	TpAccountManager* tp_am = tp_account_manager_dup ();
	GList* account_service_list = NULL;
	GList* account_list = tp_account_manager_dup_valid_accounts (tp_am);

	for (account_service_list = account_list; account_service_list != NULL; account_service_list = account_service_list->next) {
		TpAccount* account = NULL;
		account = (TpAccount*) account_service_list->data;
		if (tp_account_is_enabled (account))
		{
			g_debug ("ENABLED: %s", tp_account_get_cm_name (account));
			r = TRUE;
		} else
		{
			g_debug ("DISABLED: %s", tp_account_get_cm_name (account));
		}
	}

	g_list_free_full (account_list, g_object_unref);
	g_object_unref(G_OBJECT(tp_am));
	return r;
}

static void on_enabled_event (TpAccountManager* self, TpAccount* account, gpointer user_data)
{
	g_debug ("on_enabled_event");
	if (mmapp)
		messaging_menu_app_register (mmapp);
}

static void on_disabled_event (TpAccountManager* self, TpAccount* account, gpointer user_data)
{
	g_debug ("on_disabled_event");
	if (mmapp && !check_enabled_accounts ())
	{
		messaging_menu_app_unregister (mmapp);
	}
}


static void on_account_removed_event (TpAccountManager* self, TpAccount* account, gpointer user_data)
{
	g_debug ("on_account_removed_event");
	if (mmapp && !check_enabled_accounts ())
	{
		messaging_menu_app_unregister (mmapp);
	}
}

static void
setup (GMainLoop *loop)
{
	TpAccountManager *tp_am;
	TpBaseClient *approver;
	TpBaseClient *observer;
	GError *error = NULL;

	g_debug (G_STRFUNC);

	tp_am = tp_account_manager_dup ();

	approver = approver_setup (tp_am, &error);
	if (approver == NULL)
	{
		g_warning ("Failed to register the approver: %s", error->message);
		g_error_free (error);
		g_object_unref (tp_am);
		g_main_loop_quit (loop);
		return;
	}

	observer = observer_setup (tp_am, &error);
	if (observer == NULL)
	{
		g_warning ("Failed to register the observer: %s", error->message);
		g_error_free (error);
		g_object_unref (tp_am);
		g_main_loop_quit (loop);
		return;
	}

	g_signal_connect(tp_am, "most-available-presence-changed",
			 G_CALLBACK (presence_changed), NULL);
	g_signal_connect (tp_am, "account-enabled", 
			  G_CALLBACK (on_enabled_event), NULL);
	g_signal_connect (tp_am, "account-disabled", 
			  G_CALLBACK (on_disabled_event), NULL);
	g_signal_connect (tp_am, "account-removed", 
			  G_CALLBACK (on_account_removed_event), NULL);

	dispatch_ops = g_hash_table_new_full (g_str_hash, g_str_equal,
					      g_free, g_object_unref);
	launcher = unity_launcher_entry_get_for_desktop_id ("empathy.desktop");

	contact_list_setup (tp_am);

	g_object_unref (tp_am);
}

static void
free_ids_hash (gpointer data)
{
  GHashTable *ids = data;

  if (mmapp)
    {
      GHashTableIter iter;
      gpointer id;

      g_hash_table_iter_init (&iter, ids);
      while (g_hash_table_iter_next (&iter, &id, NULL))
        {
          messaging_menu_app_remove_source (mmapp, id);
          --n_sources;
        }

      update_launcher (n_sources);
    }

  g_hash_table_unref (ids);
}

static void
empathy_appeared (GDBusConnection *connection,
		  const gchar     *name,
		  const gchar     *name_owner,
		  gpointer         user_data)
{
	TpAccountManager *tp_am;
	TpConnectionPresenceType presence;

	mmapp = messaging_menu_app_new ("empathy.desktop");
	conns = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, free_ids_hash);

	if (check_enabled_accounts ())
		messaging_menu_app_register (mmapp);

	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);

	tp_am = tp_account_manager_dup ();
	presence = tp_account_manager_get_most_available_presence (tp_am, NULL, NULL);
	presence_changed (tp_am, presence, "", "", NULL);

	g_object_unref (tp_am);
}

static void
empathy_vanished (GDBusConnection *connection,
		  const gchar     *name,
		  gpointer         user_data)
{
	g_debug (G_STRFUNC);

	g_clear_object (&launcher);
	g_clear_object (&mmapp);
	g_clear_pointer (&conns, g_hash_table_unref);
}

int
main (int argc, char **argv)
{
	int ret = 0;
	GMainLoop *loop = NULL;
	gtk_init (&argc, &argv);

	loop = g_main_loop_new (NULL, FALSE);
	setup (loop);

	g_bus_watch_name (G_BUS_TYPE_SESSION,
			  "org.gnome.Empathy",
			  G_BUS_NAME_WATCHER_FLAGS_NONE,
			  empathy_appeared,
			  empathy_vanished,
			  NULL, NULL);

	g_debug ("Telepathy Indicator started");
	g_main_loop_run (loop);

	g_main_loop_unref (loop);
	return ret;
}
