/*
 * Pidgin - Internet Messenger
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Pidgin 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, see <https://www.gnu.org/licenses/>.
 */

#include <glib/gi18n.h>

#include <gtk/gtk.h>

#include <birb.h>

#include "pidginaccountsenabledmenu.h"

#include "pidginapplication.h"

struct _PidginAccountsEnabledMenu {
	GMenuModel parent;

	GListModel *accounts;
};

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
pidgin_accounts_enabled_menu_foreach_add_action_groups(const char *prefix,
                                                       GActionGroup *group,
                                                       gpointer data)
{
	PidginApplication *application = data;

	pidgin_application_add_action_group(application, prefix, group);
}

static void
pidgin_accounts_enabled_menu_foreach_remove_action_groups(const char *prefix,
                                                          G_GNUC_UNUSED GActionGroup *group,
                                                          gpointer data)
{
	PidginApplication *application = data;

	pidgin_application_add_action_group(application, prefix, NULL);
}

static void
pidgin_accounts_enabled_menu_update(PidginAccountsEnabledMenu *menu,
                                    PurpleAccount *account)
{
	PurpleProtocol *protocol = NULL;

	for(guint index = 0; index < g_list_model_get_n_items(menu->accounts); index++) {
		PurpleAccount *account2 = g_list_model_get_item(menu->accounts, index);

		if(account == account2) {
			/* Tell the model that the account needs to be updated. */
			g_menu_model_items_changed(G_MENU_MODEL(menu), index, 1, 1);
			g_clear_object(&account2);
			break;
		}

		g_clear_object(&account2);
	}

	/* If the protocol has actions add them to the application windows. */
	protocol = purple_account_get_protocol(account);
	if(PURPLE_IS_PROTOCOL(protocol)) {
		BirbActionMenu *action_menu = NULL;

		action_menu = purple_protocol_get_action_menu(protocol, account);
		if(BIRB_IS_ACTION_MENU(action_menu)) {
			GApplication *application = g_application_get_default();

			birb_action_menu_foreach_action_group(action_menu,
			                                      pidgin_accounts_enabled_menu_foreach_add_action_groups,
			                                      application);
		}
		g_clear_object(&action_menu);
	}
}

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
pidgin_accounts_enabled_menu_items_changed_cb(G_GNUC_UNUSED GListModel *model,
                                              guint position,
                                              guint removed,
                                              guint added,
                                              gpointer data)
{
	PidginAccountsEnabledMenu *menu = data;

	g_menu_model_items_changed(G_MENU_MODEL(menu), position, removed, added);
}

static void
pidgin_accounts_enabled_menu_account_connected_cb(G_GNUC_UNUSED PurpleAccountManager *m,
                                                  PurpleAccount *account,
                                                  gpointer data)
{
	pidgin_accounts_enabled_menu_update(data, account);
}

static void
pidgin_accounts_enabled_menu_account_disconnected_cb(G_GNUC_UNUSED PurpleAccountManager *manager,
                                                     PurpleAccount *account,
                                                     gpointer data)
{
	PidginAccountsEnabledMenu *menu = data;
	PurpleProtocol *protocol = NULL;

	for(guint index = 0; index < g_list_model_get_n_items(menu->accounts); index++) {
		PurpleAccount *account2 = g_list_model_get_item(menu->accounts, index);

		if(account == account2) {
			/* Tell the model that the account needs to be updated. */
			g_menu_model_items_changed(G_MENU_MODEL(menu), index, 1, 1);
			g_clear_object(&account2);
			break;
		}

		g_clear_object(&account2);
	}

	/* Figure out if this is the last connected account for this protocol, and
	 * if so, remove the action group from the application windows.
	 */
	protocol = purple_account_get_protocol(account);
	if(PURPLE_IS_PROTOCOL(protocol)) {
		gboolean found = FALSE;

		manager = purple_account_manager_get_default();

		for(guint i = 0; i < g_list_model_get_n_items(menu->accounts); i++) {
			PurpleAccount *account2 = g_list_model_get_item(menu->accounts, i);
			PurpleProtocol *protocol2 = purple_account_get_protocol(account2);

			if(!found && protocol2 == protocol) {
				found = TRUE;
				g_clear_object(&account2);
				break;
			}

			g_clear_object(&account2);
		}

		if(!found) {
			GApplication *application = g_application_get_default();
			BirbActionMenu *action_menu = NULL;

			action_menu = purple_protocol_get_action_menu(protocol, account);
			birb_action_menu_foreach_action_group(action_menu,
			                                      pidgin_accounts_enabled_menu_foreach_remove_action_groups,
			                                      application);
			g_clear_object(&action_menu);
		}
	}
}

/******************************************************************************
 * GMenuModel Implementation
 *****************************************************************************/
static gboolean
pidgin_accounts_enabled_menu_is_mutable(G_GNUC_UNUSED GMenuModel *model) {
	return TRUE;
}

static gint
pidgin_accounts_enabled_menu_get_n_items(GMenuModel *model) {
	PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model);

	return g_list_model_get_n_items(menu->accounts);
}

static void
pidgin_accounts_enabled_menu_get_item_attributes(GMenuModel *model, gint index,
                                                 GHashTable **attributes)
{
	PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model);
	PurpleAccount *account = NULL;
	PurpleProtocol *protocol = NULL;
	GVariant *value = NULL;
	gchar *label = NULL;
	const gchar *account_name = NULL, *protocol_name = NULL, *icon_name = NULL;

	/* Create our hash table of attributes to return. This must always be
	 * populated.
	 */
	*attributes = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
	                                    (GDestroyNotify)g_variant_unref);

	/* Get the account the caller is interested in. */
	account = g_list_model_get_item(menu->accounts, index);
	if(!PURPLE_IS_ACCOUNT(account)) {
		return;
	}

	account_name = purple_account_get_username(account);

	/* Get the protocol from the account. */
	protocol = purple_account_get_protocol(account);
	if(PURPLE_IS_PROTOCOL(protocol)) {
		protocol_name = purple_protocol_get_name(protocol);
		icon_name = purple_protocol_get_icon_name(protocol);
	}

	/* Add the label. */

	/* translators: This format string is intended to contain the account
	 * name followed by the protocol name to uniquely identify a specific
	 * account.
	 */
	label = g_strdup_printf(_("%s (%s)"), account_name, protocol_name);
	value = g_variant_new_string(label);
	g_free(label);
	g_hash_table_insert(*attributes, G_MENU_ATTRIBUTE_LABEL,
	                    g_variant_ref_sink(value));

	/* Add the icon if we have one. */
	if(icon_name != NULL) {
		value = g_variant_new_string(icon_name);
		g_hash_table_insert(*attributes, G_MENU_ATTRIBUTE_ICON,
		                    g_variant_ref_sink(value));
	}

	g_clear_object(&account);
}

static void
pidgin_accounts_enabled_menu_get_item_links(GMenuModel *model, gint index,
                                            GHashTable **links)
{
	PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model);
	PurpleAccount *account = NULL;
	PurpleConnection *connection = NULL;
	PurpleProtocol *protocol = NULL;
	GApplication *application = g_application_get_default();
	GMenu *submenu = NULL, *template = NULL;
	const gchar *account_id = NULL, *connection_id = NULL;

	/* Create our hash table for links, this must always be populated. */
	*links = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
	                               g_object_unref);

	account = g_list_model_get_item(menu->accounts, index);
	if(!PURPLE_IS_ACCOUNT(account)) {
		return;
	}

	account_id = purple_account_get_id(account);

	connection = purple_account_get_connection(account);
	if(PURPLE_IS_CONNECTION(connection)) {
		connection_id = purple_connection_get_id(connection);
	}

	/* Create a copy of our template menu. */
	template = gtk_application_get_menu_by_id(GTK_APPLICATION(application),
	                                          "enabled-account");
	submenu = purple_menu_copy(G_MENU_MODEL(template));

	/* Add the account actions if we have any. */
	protocol = purple_account_get_protocol(account);
	if(PURPLE_IS_PROTOCOL(protocol)) {
		BirbActionMenu *action_menu = NULL;

		action_menu = purple_protocol_get_action_menu(protocol, account);

		if(BIRB_IS_ACTION_MENU(action_menu)) {
			GMenu *protocol_menu = NULL;

			protocol_menu = birb_action_menu_get_menu(action_menu);
			if(G_IS_MENU(protocol_menu)) {
				g_menu_insert_section(submenu, 1, NULL,
				                      G_MENU_MODEL(protocol_menu));
			}

			g_clear_object(&action_menu);
		}
	}

	purple_menu_populate_dynamic_targets(submenu,
	                                     "account", account_id,
	                                     "connection", connection_id,
	                                     NULL);

	g_hash_table_insert(*links, G_MENU_LINK_SUBMENU, submenu);

	g_clear_object(&account);
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_FINAL_TYPE(PidginAccountsEnabledMenu, pidgin_accounts_enabled_menu,
                    G_TYPE_MENU_MODEL)

static void
pidgin_accounts_enabled_menu_constructed(GObject *obj) {
	PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(obj);
	guint count = 0;

	G_OBJECT_CLASS(pidgin_accounts_enabled_menu_parent_class)->constructed(obj);

	count = g_list_model_get_n_items(menu->accounts);
	g_menu_model_items_changed(G_MENU_MODEL(obj), 0, 0, count);
}

static void
pidgin_accounts_enabled_menu_init(PidginAccountsEnabledMenu *menu) {
	PurpleAccountManager *account_manager = NULL;

	account_manager = purple_account_manager_get_default();

	menu->accounts = purple_account_manager_get_enabled(account_manager);
	g_signal_connect_object(menu->accounts, "items-changed",
	                        G_CALLBACK(pidgin_accounts_enabled_menu_items_changed_cb),
	                        menu, G_CONNECT_DEFAULT);

	g_signal_connect_object(account_manager, "account-connected",
	                        G_CALLBACK(pidgin_accounts_enabled_menu_account_connected_cb),
	                        menu, 0);
	g_signal_connect_object(account_manager, "account-disconnected",
	                        G_CALLBACK(pidgin_accounts_enabled_menu_account_disconnected_cb),
	                        menu, 0);
}

static void
pidgin_accounts_enabled_menu_class_init(PidginAccountsEnabledMenuClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
	GMenuModelClass *model_class = G_MENU_MODEL_CLASS(klass);

	obj_class->constructed = pidgin_accounts_enabled_menu_constructed;

	model_class->is_mutable = pidgin_accounts_enabled_menu_is_mutable;
	model_class->get_n_items = pidgin_accounts_enabled_menu_get_n_items;
	model_class->get_item_attributes = pidgin_accounts_enabled_menu_get_item_attributes;
	model_class->get_item_links = pidgin_accounts_enabled_menu_get_item_links;
}

/******************************************************************************
 * Public API
 *****************************************************************************/
GMenuModel *
pidgin_accounts_enabled_menu_new(void) {
	return g_object_new(PIDGIN_TYPE_ACCOUNTS_ENABLED_MENU, NULL);
}
