// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright 2015 Red Hat, Inc.
 */

#include "nm-default.h"
#include "nma-private.h"

#include <string.h>

#include "nma-ui-utils.h"

/*---------------------------------------------------------------------------*/
/* Password storage icon */

#define PASSWORD_STORAGE_MENU_TAG  "password-storage-menu"
#define MENU_WITH_NOT_REQUIRED_TAG "menu-with-not-required"
#define ASK_MODE_TAG               "ask-mode"

typedef enum {
	ITEM_STORAGE_USER    = 0,
	ITEM_STORAGE_SYSTEM  = 1,
	ITEM_STORAGE_ASK     = 2,
	ITEM_STORAGE_UNUSED  = 3,
	__ITEM_STORAGE_MAX,
	ITEM_STORAGE_MAX = __ITEM_STORAGE_MAX - 1,
} MenuItem;

static const char *icon_name_table[ITEM_STORAGE_MAX + 1] = {
	[ITEM_STORAGE_USER]    = "user-info-symbolic",
	[ITEM_STORAGE_SYSTEM]  = "system-users-symbolic",
	[ITEM_STORAGE_ASK]     = "dialog-question-symbolic",
	[ITEM_STORAGE_UNUSED]  = "edit-clear-all-symbolic",
};
static const char *icon_desc_table[ITEM_STORAGE_MAX + 1] = {
	[ITEM_STORAGE_USER]    = N_("Store the password only for this user"),
	[ITEM_STORAGE_SYSTEM]  = N_("Store the password for all users"),
	[ITEM_STORAGE_ASK]     = N_("Ask for this password every time"),
	[ITEM_STORAGE_UNUSED]  = N_("The password is not required"),
};

static void
g_free_str0 (gpointer mem)
{
	/* g_free a char pointer and set it to 0 before (for passwords). */
	if (mem) {
		char *p = mem;
		memset (p, 0, strlen (p));
		g_free (p);
	}
}

static void
change_password_storage_icon (GtkWidget *passwd_entry, MenuItem item)
{
	const char *old_pwd;
	gboolean ask_mode;

	g_return_if_fail (item >= 0 && item <= ITEM_STORAGE_MAX);

	gtk_entry_set_icon_from_icon_name (GTK_ENTRY (passwd_entry),
	                                   GTK_ENTRY_ICON_SECONDARY,
	                                   icon_name_table[item]);
	gtk_entry_set_icon_tooltip_text (GTK_ENTRY (passwd_entry),
	                                 GTK_ENTRY_ICON_SECONDARY,
	                                 _(icon_desc_table[item]));

	/* We want to make entry insensitive when ITEM_STORAGE_ASK is selected
	 * Unfortunately, making GtkEntry insensitive will also make the icon
	 * insensitive, which prevents user from reverting the action.
	 * Let's workaround that by disabling focus for entry instead of
	 * sensitivity change.
	*/
	ask_mode = !!g_object_get_data (G_OBJECT (passwd_entry), ASK_MODE_TAG);
	if (   (item == ITEM_STORAGE_ASK && !ask_mode)
	    || item == ITEM_STORAGE_UNUSED) {
		/* Store the old password */
		old_pwd = gtk_editable_get_text (GTK_EDITABLE (passwd_entry));
		if (old_pwd && *old_pwd)
			g_object_set_data_full (G_OBJECT (passwd_entry), "password-old",
		                                g_strdup (old_pwd), g_free_str0);
		gtk_editable_set_text (GTK_EDITABLE (passwd_entry), "");

		if (gtk_widget_is_focus (passwd_entry))
			gtk_widget_child_focus ((gtk_widget_get_toplevel (passwd_entry)), GTK_DIR_TAB_BACKWARD);
		gtk_widget_set_can_focus (passwd_entry, FALSE);
	} else {
		/* Set the old password to the entry */
		old_pwd = g_object_get_data (G_OBJECT (passwd_entry), "password-old");
		if (old_pwd && *old_pwd)
			gtk_editable_set_text (GTK_EDITABLE (passwd_entry), old_pwd);
		g_object_set_data (G_OBJECT (passwd_entry), "password-old", NULL);

		if (!gtk_widget_get_can_focus (passwd_entry)) {
			gtk_widget_set_can_focus (passwd_entry, TRUE);
			gtk_widget_grab_focus (passwd_entry);
		}
	}
}

static MenuItem
secret_flags_to_menu_item (NMSettingSecretFlags flags, gboolean with_not_required)
{
	MenuItem idx;

	if (flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
		idx = ITEM_STORAGE_ASK;
	else if (with_not_required && (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED))
		idx = ITEM_STORAGE_UNUSED;
	else if (flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED)
		idx = ITEM_STORAGE_USER;
	else
		idx = ITEM_STORAGE_SYSTEM;

	return idx;
}

static NMSettingSecretFlags
menu_item_to_secret_flags (MenuItem item)
{
	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;

	switch (item) {
	case ITEM_STORAGE_USER:
		flags |= NM_SETTING_SECRET_FLAG_AGENT_OWNED;
		break;
	case ITEM_STORAGE_ASK:
		flags |= NM_SETTING_SECRET_FLAG_NOT_SAVED;
		break;
	case ITEM_STORAGE_UNUSED:
		flags |= NM_SETTING_SECRET_FLAG_NOT_REQUIRED;
		break;
	case ITEM_STORAGE_SYSTEM:
	default:
		break;
	}
	return flags;
}

typedef struct {
	NMSetting *setting;
	char *password_flags_name;
	MenuItem item_number;
	GtkWidget *passwd_entry;
} PopupMenuItemInfo;

static void
popup_menu_item_info_destroy (gpointer data, GClosure *closure)
{
	PopupMenuItemInfo *info = (PopupMenuItemInfo *) data;

	if (info->setting)
		g_object_unref (info->setting);
	g_clear_pointer (&info->password_flags_name, g_free);
	if (info->passwd_entry)
		g_object_remove_weak_pointer (G_OBJECT (info->passwd_entry), (gpointer *) &info->passwd_entry);
	g_slice_free (PopupMenuItemInfo, info);
}

static void
activate_menu_item_cb (GtkMenuItem *menuitem, gpointer user_data)
{
	PopupMenuItemInfo *info = (PopupMenuItemInfo *) user_data;
	NMSettingSecretFlags flags;

	/* Update password flags according to the password-storage popup menu */
	if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem))) {
		flags = menu_item_to_secret_flags (info->item_number);

		/* Update the secret flags in the setting */
		if (info->setting)
			nm_setting_set_secret_flags (info->setting, info->password_flags_name,
			                             flags, NULL);

		/* Change icon */
		if (info->passwd_entry) {
			change_password_storage_icon (info->passwd_entry, info->item_number);

			/* Emit "changed" signal on the entry */
			g_signal_emit_by_name (G_OBJECT (info->passwd_entry), "changed");
		}
	}
}

static void
popup_menu_item_info_register (GtkWidget *item,
                               NMSetting *setting,
                               const char *password_flags_name,
                               MenuItem item_number,
                               GtkWidget *passwd_entry)
{
	PopupMenuItemInfo *info;

	info = g_slice_new0 (PopupMenuItemInfo);
	info->setting = setting ? g_object_ref (setting) : NULL;
	info->password_flags_name = g_strdup (password_flags_name);
	info->item_number = item_number;
	info->passwd_entry = passwd_entry;

	if (info->passwd_entry)
		g_object_add_weak_pointer (G_OBJECT (info->passwd_entry), (gpointer *) &info->passwd_entry);

	g_signal_connect_data (item, "activate",
	                       G_CALLBACK (activate_menu_item_cb),
	                       info,
	                       (GClosureNotify) popup_menu_item_info_destroy, 0);
}

void
nma_gtk_widget_activate_default (GtkWidget *widget)
{
#if GTK_CHECK_VERSION(3,90,0)
	gtk_widget_activate_default (widget);
#else
	gtk_window_activate_default (GTK_WINDOW (widget));
#endif
}

static void
icon_release_cb (GtkEntry *entry,
                 GtkEntryIconPosition position,
#if !GTK_CHECK_VERSION(3,90,0)
                 GdkEventButton *event,
#endif
                 gpointer data)
{
	GtkMenu *menu = GTK_MENU (data);

	if (position == GTK_ENTRY_ICON_SECONDARY) {
#if GTK_CHECK_VERSION(3,90,0)
		gtk_widget_show (GTK_WIDGET (data));
		gtk_menu_popup_at_pointer (menu, NULL);
#else
		gtk_widget_show_all (GTK_WIDGET (data));
		gtk_menu_popup (menu, NULL, NULL, NULL, NULL,
		                event->button, event->time);
#endif
	}
}

/**
 * nma_utils_setup_password_storage:
 * @passwd_entry: password #GtkEntry which the icon is attached to
 * @initial_flags: initial secret flags to setup password menu from
 * @setting: #NMSetting containing the password, or NULL
 * @password_flags_name: name of the secret flags (like psk-flags), or NULL
 * @with_not_required: whether to include "Not required" menu item
 * @ask_mode: %TRUE if the entry is shown in ASK mode. That means,
 *   while prompting for a password, contrary to being inside the
 *   editor mode.
 *   If %TRUE, the entry should be sensivive on selected "always-ask"
 *   icon (this is e.f. for nm-applet asking for password), otherwise
 *   not.
 *   If %TRUE, it shall not be possible to select a different storage,
 *   because we only prompt for a password, we cannot change the password
 *   location.
 *
 * Adds a secondary icon and creates a popup menu for password entry.
 * The active menu item is set up according to initial_flags, or
 * from @setting/@password_flags_name (if they are not NULL).
 * If the @setting/@password_flags_name are not NULL, secret flags will
 * be automatically updated in the setting when menu is changed.
 */
void
nma_utils_setup_password_storage (GtkWidget *passwd_entry,
                                  NMSettingSecretFlags initial_flags,
                                  NMSetting *setting,
                                  const char *password_flags_name,
                                  gboolean with_not_required,
                                  gboolean ask_mode)
{
	GtkWidget *popup_menu;
	GtkWidget *item[4];
	GSList *group;
	MenuItem idx;
	NMSettingSecretFlags secret_flags;

	/* Whether entry should be sensitive if "always-ask" is active " */
	g_object_set_data (G_OBJECT (passwd_entry), ASK_MODE_TAG, GUINT_TO_POINTER (ask_mode));

	popup_menu = gtk_menu_new ();
	g_object_set_data (G_OBJECT (popup_menu), PASSWORD_STORAGE_MENU_TAG, GUINT_TO_POINTER (TRUE));
	g_object_set_data (G_OBJECT (popup_menu), MENU_WITH_NOT_REQUIRED_TAG, GUINT_TO_POINTER (with_not_required));
	group = NULL;
	item[0] = gtk_radio_menu_item_new_with_label (group, _(icon_desc_table[0]));
	group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item[0]));
	item[1] = gtk_radio_menu_item_new_with_label (group, _(icon_desc_table[1]));
	item[2] = gtk_radio_menu_item_new_with_label (group, _(icon_desc_table[2]));
	if (with_not_required)
		item[3] = gtk_radio_menu_item_new_with_label (group, _(icon_desc_table[3]));

	gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), item[0]);
	gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), item[1]);
	gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), item[2]);
	if (with_not_required)
		gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), item[3]);

	popup_menu_item_info_register (item[0], setting, password_flags_name, ITEM_STORAGE_USER, passwd_entry);
	popup_menu_item_info_register (item[1], setting, password_flags_name, ITEM_STORAGE_SYSTEM, passwd_entry);
	popup_menu_item_info_register (item[2], setting, password_flags_name, ITEM_STORAGE_ASK, passwd_entry);
	if (with_not_required)
		popup_menu_item_info_register (item[3], setting, password_flags_name, ITEM_STORAGE_UNUSED, passwd_entry);

	g_signal_connect (passwd_entry, "icon-release", G_CALLBACK (icon_release_cb), popup_menu);
	gtk_entry_set_icon_activatable (GTK_ENTRY (passwd_entry), GTK_ENTRY_ICON_SECONDARY,
	                                !ask_mode);
	gtk_menu_attach_to_widget (GTK_MENU (popup_menu), passwd_entry, NULL);

	/* Initialize active item for password-storage popup menu */
	if (setting && password_flags_name)
		nm_setting_get_secret_flags (setting, password_flags_name, &secret_flags, NULL);
	else
		secret_flags = initial_flags;

	idx = secret_flags_to_menu_item (secret_flags, with_not_required);
	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item[idx]), TRUE);
	change_password_storage_icon (passwd_entry, idx);
}

/**
 * nma_utils_menu_to_secret_flags:
 * @passwd_entry: password #GtkEntry which the password icon/menu is attached to
 *
 * Returns secret flags corresponding to the selected password storage menu
 * in the attached icon
 *
 * Returns: secret flags corresponding to the active item in password menu
 */
NMSettingSecretFlags
nma_utils_menu_to_secret_flags (GtkWidget *passwd_entry)
{
	GList *menu_list, *iter;
	GtkWidget *menu = NULL;
	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;

	menu_list = gtk_menu_get_for_attach_widget (passwd_entry);
	for (iter = menu_list; iter; iter = g_list_next (iter)) {
		if (g_object_get_data (G_OBJECT (iter->data), PASSWORD_STORAGE_MENU_TAG)) {
			menu = iter->data;
			break;
		}
	}

	/* Translate password popup menu to secret flags */
	if (menu) {
		MenuItem idx = 0;
		gs_free_list GList *list = NULL;
		int i, length;

		list = gtk_container_get_children (GTK_CONTAINER (menu));
		iter = list;
		length = g_list_length (list);
		for (i = 0; i < length; i++) {
			if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (iter->data))) {
				idx =  (MenuItem) i;
				break;
			}
			iter = g_list_next (iter);
		}

		flags = menu_item_to_secret_flags (idx);
	}
	return flags;
}

/**
 * nma_utils_update_password_storage:
 * @passwd_entry: #GtkEntry with the password
 * @secret_flags: secret flags to set
 * @setting: #NMSetting containing the password, or NULL
 * @password_flags_name: name of the secret flags (like psk-flags), or NULL
 *
 * Updates secret flags in the password storage popup menu and also
 * in the @setting (if @setting and @password_flags_name are not NULL).
 *
 */
void
nma_utils_update_password_storage (GtkWidget *passwd_entry,
                                   NMSettingSecretFlags secret_flags,
                                   NMSetting *setting,
                                   const char *password_flags_name)
{
	GList *menu_list, *iter;
	GtkWidget *menu = NULL;

	/* Update secret flags (WEP_KEY_FLAGS, PSK_FLAGS, ...) in the security setting */
	if (setting && password_flags_name)
		nm_setting_set_secret_flags (setting, password_flags_name, secret_flags, NULL);

	/* Update password-storage popup menu to reflect secret flags */
	menu_list = gtk_menu_get_for_attach_widget (passwd_entry);
	for (iter = menu_list; iter; iter = g_list_next (iter)) {
		if (g_object_get_data (G_OBJECT (iter->data), PASSWORD_STORAGE_MENU_TAG)) {
			menu = iter->data;
			break;
		}
	}

	if (menu) {
		GtkRadioMenuItem *item;
		MenuItem idx;
		GSList *group;
		gboolean with_not_required;
		int i, last;

		/* radio menu group list contains the menu items in reverse order */
		item = (GtkRadioMenuItem *) gtk_menu_get_active (GTK_MENU (menu));
		group = gtk_radio_menu_item_get_group (item);
		with_not_required = !!g_object_get_data (G_OBJECT (menu), MENU_WITH_NOT_REQUIRED_TAG);

		idx = secret_flags_to_menu_item (secret_flags, with_not_required);
		last = g_slist_length (group) - idx - 1;
		for (i = 0; i < last; i++)
			group = g_slist_next (group);

		gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (group->data), TRUE);
		change_password_storage_icon (passwd_entry, idx);
	}
}
/*---------------------------------------------------------------------------*/

