/*
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 * Authors:
 *	Dan Vratil <dvratil@redhat.com>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "e-port-entry.h"

#include <stddef.h>
#include <string.h>

struct _EPortEntryPrivate {
	guint port;
	gboolean is_valid;
	CamelNetworkSecurityMethod method;
};

enum {
	PORT_NUM_COLUMN,
	PORT_DESC_COLUMN,
	PORT_IS_SSL_COLUMN
};

enum {
	PROP_0,
	PROP_IS_VALID,
	PROP_PORT,
	PROP_SECURITY_METHOD
};

G_DEFINE_TYPE (
	EPortEntry,
	e_port_entry,
	GTK_TYPE_COMBO_BOX)

static void
port_entry_set_is_valid (EPortEntry *port_entry,
                         gboolean is_valid)
{
	g_return_if_fail (E_IS_PORT_ENTRY (port_entry));

	port_entry->priv->is_valid = is_valid;

	g_object_notify (G_OBJECT (port_entry), "is-valid");
}

/**
 * Returns number of port currently selected in the widget, no matter
 * what value is in the PORT property
 */
static gint
port_entry_get_model_active_port (EPortEntry *port_entry)
{
	const gchar *port;

	port = gtk_combo_box_get_active_id (GTK_COMBO_BOX (port_entry));

	if (!port) {
		GtkWidget *entry = gtk_bin_get_child (GTK_BIN (port_entry));
		port = gtk_entry_get_text (GTK_ENTRY (entry));
	}

	return atoi (port);
}

static void
port_entry_port_changed (EPortEntry *port_entry)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	const gchar *port;
	const gchar *tooltip;

	g_return_if_fail (E_IS_PORT_ENTRY (port_entry));

	model = gtk_combo_box_get_model (GTK_COMBO_BOX (port_entry));
	g_return_if_fail (model);

	if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (port_entry), &iter)) {
		GtkWidget *entry = gtk_bin_get_child (GTK_BIN (port_entry));
		port = gtk_entry_get_text (GTK_ENTRY (entry));

		/* Try if user just haven't happened to enter a default port */
		gtk_combo_box_set_active_id (GTK_COMBO_BOX (port_entry), port);
	} else {
		gtk_tree_model_get (model, &iter, PORT_NUM_COLUMN, &port, -1);
	}

	if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (port_entry), &iter)) {
		gtk_tree_model_get (model, &iter, PORT_DESC_COLUMN, &tooltip, -1);
		gtk_widget_set_tooltip_text (GTK_WIDGET (port_entry), tooltip);
	} else {
		gtk_widget_set_has_tooltip (GTK_WIDGET (port_entry), FALSE);
	}

	if (port == NULL || *port == '\0') {
		port_entry->priv->port = 0;
		port_entry_set_is_valid (port_entry, FALSE);
	} else {
		port_entry->priv->port = atoi (port);
		if ((port_entry->priv->port <= 0) ||
		    (port_entry->priv->port > G_MAXUINT16)) {
			port_entry->priv->port = 0;
			port_entry_set_is_valid (port_entry, FALSE);
		} else {
			port_entry_set_is_valid (port_entry, TRUE);
		}
	}

	g_object_notify (G_OBJECT (port_entry), "port");
}

static void
port_entry_method_changed (EPortEntry *port_entry)
{
	CamelNetworkSecurityMethod method;

	method = e_port_entry_get_security_method (port_entry);

	switch (method) {
		case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
			e_port_entry_activate_secured_port (port_entry, 0);
			break;
		default:
			e_port_entry_activate_nonsecured_port (port_entry, 0);
			break;
	}
}

static void
port_entry_set_property (GObject *object,
                         guint property_id,
                         const GValue *value,
                         GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_IS_VALID:
			port_entry_set_is_valid (
				E_PORT_ENTRY (object),
				g_value_get_boolean (value));
			return;

		case PROP_PORT:
			e_port_entry_set_port (
				E_PORT_ENTRY (object),
				g_value_get_uint (value));
			return;

		case PROP_SECURITY_METHOD:
			e_port_entry_set_security_method (
				E_PORT_ENTRY (object),
				g_value_get_enum (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
port_entry_get_property (GObject *object,
                         guint property_id,
                         GValue *value,
                         GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_IS_VALID:
			g_value_set_boolean (
				value, e_port_entry_is_valid (
				E_PORT_ENTRY (object)));
			return;

		case PROP_PORT:
			g_value_set_uint (
				value, e_port_entry_get_port (
				E_PORT_ENTRY (object)));
			return;

		case PROP_SECURITY_METHOD:
			g_value_set_enum (
				value, e_port_entry_get_security_method (
				E_PORT_ENTRY (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
port_entry_get_preferred_width (GtkWidget *widget,
                                gint *minimum_size,
                                gint *natural_size)
{
	PangoContext *context;
	PangoFontMetrics *metrics;
	PangoFontDescription *font_desc;
	GtkStyleContext	*style_context;
	GtkStateFlags state;
	gint digit_width;
	gint parent_entry_width_min;
	gint parent_width_min;
	GtkWidget *entry;

	style_context = gtk_widget_get_style_context (widget);
	state = gtk_widget_get_state_flags (widget);
	gtk_style_context_get (
		style_context, state, "font", &font_desc, NULL);
	context = gtk_widget_get_pango_context (GTK_WIDGET (widget));
	metrics = pango_context_get_metrics (
		context, font_desc, pango_context_get_language (context));

	digit_width = PANGO_PIXELS (
		pango_font_metrics_get_approximate_digit_width (metrics));

	/* Preferred width of the entry */
	entry = gtk_bin_get_child (GTK_BIN (widget));
	gtk_widget_get_preferred_width (entry, NULL, &parent_entry_width_min);

	/* Preferred width of a standard combobox */
	GTK_WIDGET_CLASS (e_port_entry_parent_class)->
		get_preferred_width (widget, &parent_width_min, NULL);

	/* 6 * digit_width - port number has max 5
	 * digits + extra free space for better look */
	if (minimum_size != NULL)
		*minimum_size =
			parent_width_min - parent_entry_width_min +
			6 * digit_width;

	if (natural_size != NULL)
		*natural_size =
			parent_width_min - parent_entry_width_min +
			6 * digit_width;

	pango_font_metrics_unref (metrics);
	pango_font_description_free (font_desc);
}

static void
e_port_entry_class_init (EPortEntryClass *class)
{
	GObjectClass *object_class;
	GtkWidgetClass *widget_class;

	g_type_class_add_private (class, sizeof (EPortEntryPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = port_entry_set_property;
	object_class->get_property = port_entry_get_property;

	widget_class = GTK_WIDGET_CLASS (class);
	widget_class->get_preferred_width = port_entry_get_preferred_width;

	g_object_class_install_property (
		object_class,
		PROP_IS_VALID,
		g_param_spec_boolean (
			"is-valid",
			NULL,
			NULL,
			FALSE,
			G_PARAM_READABLE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_PORT,
		g_param_spec_uint (
			"port",
			NULL,
			NULL,
			0,		/* Min port, 0 = invalid port */
			G_MAXUINT16,	/* Max port */
			0,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_SECURITY_METHOD,
		g_param_spec_enum (
			"security-method",
			"Security Method",
			"Method used to establish a network connection",
			CAMEL_TYPE_NETWORK_SECURITY_METHOD,
			CAMEL_NETWORK_SECURITY_METHOD_NONE,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));
}

static void
e_port_entry_init (EPortEntry *port_entry)
{
	GtkCellRenderer *renderer;
	GtkListStore *store;

	port_entry->priv = G_TYPE_INSTANCE_GET_PRIVATE (
		port_entry, E_TYPE_PORT_ENTRY, EPortEntryPrivate);
	port_entry->priv->port = 0;
	port_entry->priv->is_valid = FALSE;

	store = gtk_list_store_new (
		3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);

	gtk_combo_box_set_model (
		GTK_COMBO_BOX (port_entry), GTK_TREE_MODEL (store));
	gtk_combo_box_set_entry_text_column (
		GTK_COMBO_BOX (port_entry), PORT_NUM_COLUMN);
	gtk_combo_box_set_id_column (
		GTK_COMBO_BOX (port_entry), PORT_NUM_COLUMN);

	renderer = gtk_cell_renderer_text_new ();
	gtk_cell_renderer_set_sensitive (renderer, TRUE);
	gtk_cell_layout_pack_start (
		GTK_CELL_LAYOUT (port_entry), renderer, FALSE);
	gtk_cell_layout_add_attribute (
		GTK_CELL_LAYOUT (port_entry),
		renderer, "text", PORT_NUM_COLUMN);

	renderer = gtk_cell_renderer_text_new ();
	gtk_cell_renderer_set_sensitive (renderer, FALSE);
	gtk_cell_layout_pack_start (
		GTK_CELL_LAYOUT (port_entry), renderer, TRUE);
	gtk_cell_layout_add_attribute (
		GTK_CELL_LAYOUT (port_entry),
		renderer, "text", PORT_DESC_COLUMN);

	/* Update the port property when port is changed */
	g_signal_connect (
		port_entry, "changed",
		G_CALLBACK (port_entry_port_changed), NULL);

	g_signal_connect (
		port_entry, "notify::security-method",
		G_CALLBACK (port_entry_method_changed), NULL);
}

GtkWidget *
e_port_entry_new (void)
{
	return g_object_new (
		E_TYPE_PORT_ENTRY, "has-entry", TRUE, NULL);
}

void
e_port_entry_set_camel_entries (EPortEntry *port_entry,
                                CamelProviderPortEntry *entries)
{
	GtkTreeIter iter;
	GtkTreeModel *model;
	GtkListStore *store;
	gint i = 0;

	g_return_if_fail (E_IS_PORT_ENTRY (port_entry));
	g_return_if_fail (entries);

	model = gtk_combo_box_get_model (GTK_COMBO_BOX (port_entry));
	store = GTK_LIST_STORE (model);

	gtk_list_store_clear (store);

	while (entries[i].port > 0) {
		gchar *port_string;

		port_string = g_strdup_printf ("%i", entries[i].port);

		gtk_list_store_append (store, &iter);
		gtk_list_store_set (
			store, &iter,
			PORT_NUM_COLUMN, port_string,
			PORT_DESC_COLUMN, entries[i].desc,
			PORT_IS_SSL_COLUMN, entries[i].is_ssl,
			-1);
		i++;

		g_free (port_string);
	}

	/* Activate the first port */
	if (i > 0)
		e_port_entry_set_port (port_entry, entries[0].port);
}

gint
e_port_entry_get_port (EPortEntry *port_entry)
{
	g_return_val_if_fail (E_IS_PORT_ENTRY (port_entry), 0);

	return port_entry->priv->port;
}

void
e_port_entry_set_port (EPortEntry *port_entry,
                       gint port)
{
	g_return_if_fail (E_IS_PORT_ENTRY (port_entry));

	port_entry->priv->port = port;
	if ((port <= 0) || (port > G_MAXUINT16))
		port_entry_set_is_valid (port_entry, FALSE);
	else {
		gchar *port_string;

		port_string = g_strdup_printf ("%i", port);

		gtk_combo_box_set_active_id (
			GTK_COMBO_BOX (port_entry), port_string);

		if (port_entry_get_model_active_port (port_entry) != port) {
			GtkWidget *entry;

			entry = gtk_bin_get_child (GTK_BIN (port_entry));
			gtk_entry_set_text (GTK_ENTRY (entry), port_string);
		}

		port_entry_set_is_valid (port_entry, TRUE);

		g_free (port_string);
	}

	g_object_notify (G_OBJECT (port_entry), "port");
}

gboolean
e_port_entry_is_valid (EPortEntry *port_entry)
{
	g_return_val_if_fail (E_IS_PORT_ENTRY (port_entry), FALSE);

	return port_entry->priv->is_valid;
}

CamelNetworkSecurityMethod
e_port_entry_get_security_method (EPortEntry *port_entry)
{
	g_return_val_if_fail (
		E_IS_PORT_ENTRY (port_entry),
		CAMEL_NETWORK_SECURITY_METHOD_NONE);

	return port_entry->priv->method;
}

void
e_port_entry_set_security_method (EPortEntry *port_entry,
                                  CamelNetworkSecurityMethod method)
{
	g_return_if_fail (E_IS_PORT_ENTRY (port_entry));

	port_entry->priv->method = method;

	g_object_notify (G_OBJECT (port_entry), "security-method");
}

/**
 * If there are more then one secured port in the model, you can specify
 * which of the secured ports should be activated by specifying the index.
 * The index counts only for secured ports, so if you have 5 ports of which
 * ports 1, 3 and 5 are secured, the association is 0=>1, 1=>3, 2=>5
 */
void
e_port_entry_activate_secured_port (EPortEntry *port_entry,
                                    gint index)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gboolean is_ssl;
	gint iters = 0;

	g_return_if_fail (E_IS_PORT_ENTRY (port_entry));

	model = gtk_combo_box_get_model (GTK_COMBO_BOX (port_entry));

	if (!gtk_tree_model_get_iter_first (model, &iter))
		return;

	do {
		gtk_tree_model_get (
			model, &iter, PORT_IS_SSL_COLUMN, &is_ssl, -1);
		if (is_ssl && (iters == index)) {
			gtk_combo_box_set_active_iter (
				GTK_COMBO_BOX (port_entry), &iter);
			return;
		}

		if (is_ssl)
			iters++;

	} while (gtk_tree_model_iter_next (model, &iter));
}

/**
 * If there are more then one unsecured port in the model, you can specify
 * which of the unsecured ports should be activated by specifiying the index.
 * The index counts only for unsecured ports, so if you have 5 ports, of which
 * ports 2 and 4 are unsecured, the associtation is 0=>2, 1=>4
 */
void
e_port_entry_activate_nonsecured_port (EPortEntry *port_entry,
                                       gint index)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gboolean is_ssl;
	gint iters = 0;

	g_return_if_fail (E_IS_PORT_ENTRY (port_entry));

	model = gtk_combo_box_get_model (GTK_COMBO_BOX (port_entry));

	if (!gtk_tree_model_get_iter_first (model, &iter))
		return;

	do {
		gtk_tree_model_get (model, &iter, PORT_IS_SSL_COLUMN, &is_ssl, -1);
		if (!is_ssl && (iters == index)) {
			gtk_combo_box_set_active_iter (
				GTK_COMBO_BOX (port_entry), &iter);
			return;
		}

		if (!is_ssl)
			iters++;

	} while (gtk_tree_model_iter_next (model, &iter));
}