// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2013 Red Hat, Inc.
 */

/**
 * SECTION:nmt-page-bond
 * @short_description: The editor page for Bond connections
 *
 * Note that this is fairly different from most of the other editor
 * pages, because #NMSettingBond doesn't have properties, so we
 * can't just use #GBinding.
 */

#include "nm-default.h"

#include "nmt-page-bond.h"

#include "nmt-mac-entry.h"
#include "nmt-address-list.h"
#include "nmt-slave-list.h"

G_DEFINE_TYPE (NmtPageBond, nmt_page_bond, NMT_TYPE_EDITOR_PAGE_DEVICE)

#define NMT_PAGE_BOND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NMT_TYPE_PAGE_BOND, NmtPageBondPrivate))

typedef enum {
	NMT_PAGE_BOND_MONITORING_UNKNOWN = -1,
	NMT_PAGE_BOND_MONITORING_MII = 0,
	NMT_PAGE_BOND_MONITORING_ARP = 1,
} NmtPageBondMonitoringMode;

typedef struct {
	NmtSlaveList *slaves;

	/* Note: when adding new options to the UI also ensure they are
	 * initialized in bond_connection_setup_func()
	 */
	NmtNewtPopup *mode;
	NmtNewtEntry *primary;
	NmtNewtPopup *monitoring;
	NmtNewtEntry *miimon;
	NmtNewtEntry *updelay;
	NmtNewtEntry *downdelay;
	NmtNewtEntry *arp_interval;
	NmtAddressList *arp_ip_target;

	NmtPageBondMonitoringMode monitoring_mode;

	NMSettingBond *s_bond;
	GType slave_type;
	gboolean updating;
} NmtPageBondPrivate;

NmtEditorPage *
nmt_page_bond_new (NMConnection   *conn,
                   NmtDeviceEntry *deventry)
{
	return g_object_new (NMT_TYPE_PAGE_BOND,
	                     "connection", conn,
	                     "device-entry", deventry,
	                     NULL);
}

static void
nmt_page_bond_init (NmtPageBond *bond)
{
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond);

	priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_UNKNOWN;
	priv->slave_type = G_TYPE_NONE;
}

static NmtNewtPopupEntry bond_mode[] = {
	{ N_("Round-robin"),                            "balance-rr" },
	{ N_("Active Backup"),                          "active-backup" },
	{ N_("XOR"),                                    "balance-xor" },
	{ N_("Broadcast"),                              "broadcast" },
	{ N_("802.3ad"),                                "802.3ad" },
	{ N_("Adaptive Transmit Load Balancing (tlb)"), "balance-tlb" },
	{ N_("Adaptive Load Balancing (alb)"),          "balance-alb" },
	{ NULL, NULL }
};

/* NB: the ordering/numbering here corresponds to NmtPageBondMonitoringMode */
static NmtNewtPopupEntry bond_monitoring[] = {
	{ N_("MII (recommended)"), "mii" },
	{ N_("ARP"),               "arp" },
	{ NULL, NULL }
};

static void
bond_options_changed (GObject    *object,
                      GParamSpec *pspec,
                      gpointer    user_data)
{
	NMSettingBond *s_bond = NM_SETTING_BOND (object);
	NmtPageBond *bond = NMT_PAGE_BOND (user_data);
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond);
	const char *val;
	char **ips;

	if (priv->updating)
		return;

	priv->updating = TRUE;

	val = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE);
	nmt_newt_popup_set_active_id (priv->mode, val);

	if (!strcmp (val, "active-backup")) {
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->primary), TRUE);
		val = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
		nmt_newt_entry_set_text (priv->primary, val);
	} else
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->primary), FALSE);

	if (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_UNKNOWN) {
		val = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
		if (val && strcmp (val, "0") != 0)
			priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_ARP;
		else
			priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_MII;
	}
	nmt_newt_popup_set_active (priv->monitoring, priv->monitoring_mode);

	if (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_MII) {
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->miimon), TRUE);
		val = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MIIMON);
		nmt_newt_entry_set_text (priv->miimon, val);

		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->updelay), TRUE);
		val = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_UPDELAY);
		nmt_newt_entry_set_text (priv->updelay, val);

		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->downdelay), TRUE);
		val = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY);
		nmt_newt_entry_set_text (priv->downdelay, val);

		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->arp_interval), FALSE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->arp_ip_target), FALSE);
	} else {
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->arp_interval), TRUE);
		val = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
		nmt_newt_entry_set_text (priv->arp_interval, val);

		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->arp_ip_target), TRUE);
		val = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
		ips = g_strsplit (val, ",", -1);
		g_object_set (G_OBJECT (priv->arp_ip_target),
		              "strings", ips,
		              NULL);
		g_strfreev (ips);

		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->miimon), FALSE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->updelay), FALSE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->downdelay), FALSE);
	}

	priv->updating = FALSE;
}

static void
slaves_changed (GObject    *object,
                GParamSpec *pspec,
                gpointer    user_data)
{
	NmtPageBond *bond = NMT_PAGE_BOND (user_data);
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond);
	GPtrArray *slaves;

	g_object_get (object, "connections", &slaves, NULL);
	if (slaves->len == 0) {
		if (priv->slave_type == G_TYPE_NONE)
			return;
		priv->slave_type = G_TYPE_NONE;
	} else {
		NMConnection *slave = slaves->pdata[0];

		if (priv->slave_type != G_TYPE_NONE)
			return;

		if (nm_connection_is_type (slave, NM_SETTING_INFINIBAND_SETTING_NAME))
			priv->slave_type = NM_TYPE_SETTING_INFINIBAND;
		else
			priv->slave_type = NM_TYPE_SETTING_WIRED;
	}

	if (priv->slave_type == NM_TYPE_SETTING_INFINIBAND) {
		nmt_newt_popup_set_active_id (priv->mode, "active-backup");
		nmt_newt_component_set_sensitive (NMT_NEWT_COMPONENT (priv->mode), FALSE);
	} else
		nmt_newt_component_set_sensitive (NMT_NEWT_COMPONENT (priv->mode), TRUE);
}

#define WIDGET_CHANGED_FUNC(widget, func, option) \
static void \
widget ## _widget_changed (GObject    *object, \
                           GParamSpec *pspec, \
                           gpointer    user_data) \
{ \
    NmtPageBond *bond = NMT_PAGE_BOND (user_data); \
    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond); \
    \
    if (priv->updating) \
        return; \
    \
    priv->updating = TRUE; \
    nm_setting_bond_add_option (priv->s_bond, option, func (priv->widget)); \
    priv->updating = FALSE; \
}

WIDGET_CHANGED_FUNC (primary, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_PRIMARY)
WIDGET_CHANGED_FUNC (miimon, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_MIIMON)
WIDGET_CHANGED_FUNC (updelay, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_UPDELAY)
WIDGET_CHANGED_FUNC (downdelay, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_DOWNDELAY)
WIDGET_CHANGED_FUNC (arp_interval, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_ARP_INTERVAL)

static void
mode_widget_changed (GObject    *object,
                     GParamSpec *pspec,
                     gpointer    user_data)
{
	NmtPageBond *bond = NMT_PAGE_BOND (user_data);
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond);
	const char *mode;

	if (priv->updating)
		return;

	mode = nmt_newt_popup_get_active_id (priv->mode);
	priv->updating = TRUE;
	nm_setting_bond_add_option (priv->s_bond, NM_SETTING_BOND_OPTION_MODE, mode);
	priv->updating = FALSE;

	if (!strcmp (mode, "balance-tlb") || !strcmp (mode, "balance-alb")) {
		nmt_newt_popup_set_active (priv->monitoring, NMT_PAGE_BOND_MONITORING_MII);
		nmt_newt_component_set_sensitive (NMT_NEWT_COMPONENT (priv->monitoring), FALSE);
	} else
		nmt_newt_component_set_sensitive (NMT_NEWT_COMPONENT (priv->monitoring), TRUE);

	if (!strcmp (mode, "active-backup")) {
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->primary), TRUE);
		nm_setting_bond_add_option (priv->s_bond, NM_SETTING_BOND_OPTION_PRIMARY,
		                            nmt_newt_entry_get_text (priv->primary));
	} else {
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->primary), FALSE);
		nm_setting_bond_remove_option (priv->s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
	}
}

static void
monitoring_widget_changed (GObject    *object,
                           GParamSpec *pspec,
                           gpointer    user_data)
{
	NmtPageBond *bond = NMT_PAGE_BOND (user_data);
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond);

	if (priv->updating)
		return;

	priv->monitoring_mode = nmt_newt_popup_get_active (priv->monitoring);
	if (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_MII) {
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->miimon), TRUE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->updelay), TRUE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->downdelay), TRUE);

		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->arp_interval), FALSE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->arp_ip_target), FALSE);
	} else {
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->arp_interval), TRUE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->arp_ip_target), TRUE);

		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->miimon), FALSE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->updelay), FALSE);
		nmt_newt_widget_set_visible (NMT_NEWT_WIDGET (priv->downdelay), FALSE);
	}
}

static void
arp_ip_target_widget_changed (GObject    *object,
                              GParamSpec *pspec,
                              gpointer    user_data)
{
	NmtPageBond *bond = NMT_PAGE_BOND (user_data);
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond);
	char **ips, *target;

	if (priv->updating)
		return;

	g_object_get (G_OBJECT (priv->arp_ip_target),
	              "strings", &ips,
	              NULL);
	target = g_strjoinv (",", ips);

	priv->updating = TRUE;
	nm_setting_bond_add_option (priv->s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, target);
	priv->updating = FALSE;

	g_free (target);
	g_strfreev (ips);
}

static gboolean
bond_connection_type_filter (GType    connection_type,
                             gpointer user_data)
{
	NmtPageBond *bond = user_data;
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond);

	if (   priv->slave_type != NM_TYPE_SETTING_WIRED
	    && connection_type == NM_TYPE_SETTING_INFINIBAND)
		return TRUE;
	if (   priv->slave_type != NM_TYPE_SETTING_INFINIBAND
	    && connection_type == NM_TYPE_SETTING_WIRED)
		return TRUE;

	return FALSE;
}

static void
nmt_page_bond_constructed (GObject *object)
{
	NmtPageBond *bond = NMT_PAGE_BOND (object);
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (bond);
	NmtEditorSection *section;
	NmtEditorGrid *grid;
	NMSettingWired *s_wired;
	NMSettingBond *s_bond;
	NmtNewtWidget *widget, *label;
	NMConnection *conn;

	conn = nmt_editor_page_get_connection (NMT_EDITOR_PAGE (bond));
	s_bond = nm_connection_get_setting_bond (conn);
	if (!s_bond) {
		nm_connection_add_setting (conn, nm_setting_bond_new ());
		s_bond = nm_connection_get_setting_bond (conn);
	}
	priv->s_bond = s_bond;

	s_wired = nm_connection_get_setting_wired (conn);
	if (!s_wired) {
		nm_connection_add_setting (conn, nm_setting_wired_new ());
		s_wired = nm_connection_get_setting_wired (conn);
	}

	section = nmt_editor_section_new (_("BOND"), NULL, TRUE);
	grid = nmt_editor_section_get_body (section);

	widget = nmt_newt_separator_new ();
	nmt_editor_grid_append (grid, _("Slaves"), widget, NULL);
	nmt_editor_grid_set_row_flags (grid, widget, NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT);

	widget = nmt_slave_list_new (conn, bond_connection_type_filter, bond);
	g_signal_connect (widget, "notify::connections",
	                  G_CALLBACK (slaves_changed), bond);
	nmt_editor_grid_append (grid, NULL, widget, NULL);
	priv->slaves = NMT_SLAVE_LIST (widget);

	widget = nmt_newt_popup_new (bond_mode);
	g_signal_connect (widget, "notify::active-id",
	                  G_CALLBACK (mode_widget_changed), bond);
	nmt_editor_grid_append (grid, _("Mode"), widget, NULL);
	priv->mode = NMT_NEWT_POPUP (widget);

	widget = nmt_newt_entry_new (40, 0);
	g_signal_connect (widget, "notify::text",
	                  G_CALLBACK (primary_widget_changed), bond);
	nmt_editor_grid_append (grid, _("Primary"), widget, NULL);
	priv->primary = NMT_NEWT_ENTRY (widget);

	widget = nmt_newt_popup_new (bond_monitoring);
	g_signal_connect (widget, "notify::active",
	                  G_CALLBACK (monitoring_widget_changed), bond);
	nmt_editor_grid_append (grid, _("Link monitoring"), widget, NULL);
	priv->monitoring = NMT_NEWT_POPUP (widget);

	widget = nmt_newt_entry_numeric_new (10, 0, G_MAXINT);
	g_signal_connect (widget, "notify::text",
	                  G_CALLBACK (miimon_widget_changed), bond);
	label = nmt_newt_label_new (C_("milliseconds", "ms"));
	nmt_editor_grid_append (grid, _("Monitoring frequency"), widget, label);
	priv->miimon = NMT_NEWT_ENTRY (widget);

	widget = nmt_newt_entry_numeric_new (10, 0, G_MAXINT);
	g_signal_connect (widget, "notify::text",
	                  G_CALLBACK (updelay_widget_changed), bond);
	label = nmt_newt_label_new (C_("milliseconds", "ms"));
	nmt_editor_grid_append (grid, _("Link up delay"), widget, label);
	priv->updelay = NMT_NEWT_ENTRY (widget);

	widget = nmt_newt_entry_numeric_new (10, 0, G_MAXINT);
	g_signal_connect (widget, "notify::text",
	                  G_CALLBACK (downdelay_widget_changed), bond);
	label = nmt_newt_label_new (C_("milliseconds", "ms"));
	nmt_editor_grid_append (grid, _("Link down delay"), widget, label);
	priv->downdelay = NMT_NEWT_ENTRY (widget);

	widget = nmt_newt_entry_numeric_new (10, 0, G_MAXINT);
	g_signal_connect (widget, "notify::text",
	                  G_CALLBACK (arp_interval_widget_changed), bond);
	label = nmt_newt_label_new (C_("milliseconds", "ms"));
	nmt_editor_grid_append (grid, _("Monitoring frequency"), widget, label);
	priv->arp_interval = NMT_NEWT_ENTRY (widget);

	widget = nmt_address_list_new (NMT_ADDRESS_LIST_IP4);
	g_signal_connect (widget, "notify::strings",
	                  G_CALLBACK (arp_ip_target_widget_changed), bond);
	nmt_editor_grid_append (grid, _("ARP targets"), widget, NULL);
	priv->arp_ip_target = NMT_ADDRESS_LIST (widget);

	widget = nmt_mac_entry_new (40, ETH_ALEN, NMT_MAC_ENTRY_TYPE_CLONED);
	g_object_bind_property (s_wired, NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
	                        widget, "mac-address",
	                        G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
	nmt_editor_grid_append (grid, _("Cloned MAC address"), widget, NULL);

	g_signal_connect (s_bond, "notify::" NM_SETTING_BOND_OPTIONS,
	                  G_CALLBACK (bond_options_changed), bond);
	bond_options_changed (G_OBJECT (s_bond), NULL, bond);
	slaves_changed (G_OBJECT (priv->slaves), NULL, bond);

	nmt_editor_page_add_section (NMT_EDITOR_PAGE (bond), section);

	G_OBJECT_CLASS (nmt_page_bond_parent_class)->constructed (object);
}

static void
nmt_page_bond_saved (NmtEditorPage *editor_page)
{
	NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE (editor_page);

	nmt_edit_connection_list_recommit (NMT_EDIT_CONNECTION_LIST (priv->slaves));
}

static void
nmt_page_bond_class_init (NmtPageBondClass *bond_class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (bond_class);
	NmtEditorPageClass *editor_page_class = NMT_EDITOR_PAGE_CLASS (bond_class);

	g_type_class_add_private (bond_class, sizeof (NmtPageBondPrivate));

	object_class->constructed = nmt_page_bond_constructed;
	editor_page_class->saved = nmt_page_bond_saved;
}
