/* This file is part of GQ
 *
 * AUTHORS
 *     Sven Herzberg  <herzi@gnome-de.org>
 *
 * Copyright (C) 2006  Sven Herzberg <herzi@gnome-de.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include "gq-server-dialog.h"

#include <stdlib.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include "gq-keyring.h"
#include "gq-utilities.h"

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

struct GqServerDialogPrivate {
	GqServer* server;
	GqServer* backup;

	/* the dialog buttons */
	GtkWidget* button_close;
	GtkWidget* button_cancel;

	/* the server properties */
	GtkWidget* entry_name;
	GtkWidget* entry_host;
	GtkWidget* entry_port;
	GtkWidget* checkbutton_tls;
	GtkWidget* checkbutton_hide_internals;
	GtkWidget* entry_search_attribute;

	/* connection properties */
	GtkWidget* entry_bind_dn;
	GtkWidget* entry_bind_pw;
	GtkWidget* combobox_bind_type;
	GtkWidget* togglebutton_show_pw;
	GtkWidget* button_clear_pw;
	GtkWidget* checkbutton_ask_pw;
	GtkWidget* checkbutton_cache_connection;

	/* the label for messages */
	GtkWidget* label_warning;
};

#define P(i) (G_TYPE_INSTANCE_GET_PRIVATE((i), GQ_TYPE_SERVER_DIALOG, struct GqServerDialogPrivate))

GtkWidget*
gq_server_dialog_new(GqServer * server,
		     GtkWindow* window)
{
	GtkWidget* dialog;
	g_return_val_if_fail(GQ_IS_SERVER(server), NULL);
	g_return_val_if_fail(GTK_IS_WINDOW(window), NULL);

	dialog = g_object_new(GQ_TYPE_SERVER_DIALOG, "server", server, NULL);
	gtk_window_set_transient_for(GTK_WINDOW(dialog),
				     window);
	return dialog;
}

GqServer*
gq_server_dialog_get_server(GqServerDialog const* self)
{
	g_return_val_if_fail(GQ_IS_SERVER_DIALOG(self), NULL);

	return P(self)->server;
}

static gboolean
server_dialog_select_bind_type(GtkTreeModel* model,
			       GtkTreePath * path G_GNUC_UNUSED,
			       GtkTreeIter * iter,
			       gpointer      self)
{
	GqBindType type;
	gtk_tree_model_get(model, iter, 0, &type, -1);
	if(type == gq_server_get_bind_type(P(self)->server)) {
		gtk_combo_box_set_active_iter(GTK_COMBO_BOX(P(self)->combobox_bind_type), iter);
		return TRUE; // stop
	}
	return FALSE; // go on
}

static void
server_dialog_connect(GqServerDialog* self)
{
	gchar const* value;
	gchar      * text;

	gtk_entry_set_text(GTK_ENTRY(P(self)->entry_name), gq_server_get_name(P(self)->server));
	gtk_entry_set_text(GTK_ENTRY(P(self)->entry_host), gq_server_get_host(P(self)->server));

	text = g_strdup_printf("%d", gq_server_get_port(P(self)->server));
	gtk_entry_set_text(GTK_ENTRY(P(self)->entry_port), text);
	g_free(text);
	text = NULL;

	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(P(self)->checkbutton_tls),
				     gq_server_get_use_tls(P(self)->server));
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(P(self)->checkbutton_hide_internals),
				     gq_server_get_hide_internals(P(self)->server));

	value = gq_server_get_search_attribute(P(self)->server);
	if(value) {
		gtk_entry_set_text(GTK_ENTRY(P(self)->entry_search_attribute),
				   value);
	}

	value = gq_server_get_bind_dn(P(self)->server);
	if(value) {
		gtk_entry_set_text(GTK_ENTRY(P(self)->entry_bind_dn),
				   value);
	}

	text = gq_keyring_get_password(P(self)->server);
	if(text) {
		gtk_entry_set_text(GTK_ENTRY(P(self)->entry_bind_pw),
				   text);
	}
	gq_keyring_free_password(text);

	gtk_tree_model_foreach(gtk_combo_box_get_model(GTK_COMBO_BOX(P(self)->combobox_bind_type)),
			       server_dialog_select_bind_type,
			       self);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(P(self)->checkbutton_ask_pw),
				     gq_server_get_ask_pw(P(self)->server));
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(P(self)->checkbutton_cache_connection),
				     gq_server_get_cache_connection(P(self)->server));
}

static void
server_dialog_set_server(GqServerDialog* self,
			 GqServer      * server)
{
	g_return_if_fail(!P(self)->server);
	g_return_if_fail(GQ_IS_SERVER(server));

	P(self)->server = g_object_ref(server);
	gq_server_copy(P(self)->server, P(self)->backup);

	if(P(self)->entry_name) {
		server_dialog_connect(self);
	}

	g_object_notify(G_OBJECT(self), "server");
}

/* GType */
G_DEFINE_TYPE(GqServerDialog, gq_server_dialog, HERZI_TYPE_GLADE_DIALOG);

enum {
	PROP_0,
	PROP_SERVER
};

static void
gq_server_dialog_init(GqServerDialog* self)
{
	P(self)->backup = gq_server_new("backup server"); // will be overwritten when copying
}

static void
server_dialog_dispose(GObject* object)
{
	GqServerDialog* self = GQ_SERVER_DIALOG(object);

	if(P(self)->backup) {
		g_object_unref(P(self)->backup);
		P(self)->backup = NULL;
	}

	if(P(self)->server) {
		g_object_unref(P(self)->server);
		P(self)->server = NULL;
	}

	G_OBJECT_CLASS(gq_server_dialog_parent_class)->dispose(object);
}

static void
server_dialog_get_property(GObject   * object,
			   guint       prop_id,
			   GValue    * value,
			   GParamSpec* pspec)
{
	switch(prop_id) {
	case PROP_SERVER:
		g_value_set_object(value,
				   gq_server_dialog_get_server(GQ_SERVER_DIALOG(object)));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
server_dialog_set_property(GObject     * object,
			   guint         prop_id,
			   GValue const* value,
			   GParamSpec  * pspec)
{
	switch(prop_id) {
	case PROP_SERVER:
		server_dialog_set_server(GQ_SERVER_DIALOG(object), g_value_get_object(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
server_dialog_response(GtkDialog* dialog, gint response)
{
	if(response != GTK_RESPONSE_CLOSE) {
		// Cancel, Escape or closed the window
		gq_server_copy(P(dialog)->backup,
			       P(dialog)->server);
	} else {
		gchar const* new_pw;
		gchar      * old_pw;
		gq_server_set_name(P(dialog)->server,
				   gtk_entry_get_text(GTK_ENTRY(P(dialog)->entry_name)));
		old_pw = gq_keyring_get_password(P(dialog)->backup);
		new_pw = gtk_entry_get_text(GTK_ENTRY(P(dialog)->entry_bind_pw));
		if(!old_pw || (new_pw && strcmp(old_pw, new_pw))) {
			if(old_pw) {
				gq_keyring_forget_password(P(dialog)->backup);
			}

			if(new_pw && *new_pw) {
				gq_keyring_save_password(P(dialog)->server, new_pw);
			}
		}
		gq_keyring_free_password(old_pw);
#warning "FIXME: move the keyring password from the old server to the new one if hostname/bind_dn changed"
	}

	if(GTK_DIALOG_CLASS(gq_server_dialog_parent_class)->response) {
		GTK_DIALOG_CLASS(gq_server_dialog_parent_class)->response(dialog, response);
	}
}

static void
server_dialog_update_host(GqServerDialog* self)
{
	gq_server_set_host(P(self)->server,
			   gtk_entry_get_text(GTK_ENTRY(P(self)->entry_host)));
}

static void
server_dialog_update_name(GqServerDialog* self)
{
	gchar const* name = gtk_entry_get_text(GTK_ENTRY(P(self)->entry_name));
	// if the name is empty, display a warning

	gtk_widget_set_sensitive(P(self)->button_close, name && *name);
}

static void
server_dialog_update_port(GqServerDialog* self)
{
	// if not a number, display warning
	gq_server_set_port(P(self)->server,
			   atoi(gtk_entry_get_text(GTK_ENTRY(P(self)->entry_port))));
}

static void
server_dialog_update_tls(GqServerDialog* self)
{
	gq_server_set_use_tls(P(self)->server,
			      gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(P(self)->checkbutton_tls)));
}

static void
server_dialog_update_hide_internals(GqServerDialog* self)
{
	gq_server_set_hide_internals(P(self)->server,
				     gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(P(self)->checkbutton_hide_internals)));
}

static void
server_dialog_update_search_attribute(GqServerDialog* self)
{
	gchar const* sa = gtk_entry_get_text(GTK_ENTRY(P(self)->entry_search_attribute));
#warning "FIXME: make sure the attribute name is valid (if possible)"
	if(sa && !*sa) {
		// FIXME: GTK+ Bug http://bugzilla.gnome.org/show_bug.cgi?id=64998
		return;
	}
	gq_server_set_search_attribute(P(self)->server, sa);
}

static void
server_dialog_update_bind_dn(GqServerDialog* self)
{
#warning "FIXME: try to provide a list to choose from in a GtkComboBoxEntry"
	gq_server_set_bind_dn(P(self)->server,
			      gtk_entry_get_text(GTK_ENTRY(P(self)->entry_bind_dn)));
}

static void
server_dialog_update_show_pw(GqServerDialog* self)
{
	GtkToggleButton* toggle = GTK_TOGGLE_BUTTON(P(self)->togglebutton_show_pw);
	gboolean visible = gtk_toggle_button_get_active(toggle);

	if(!visible || question_popup(GTK_WINDOW(self),
				      _("Display Password"),
				      _("The password that you entered will be displayed in\n"
				        "the entry. This can be dangerous if someone is watching\n"
				        "at the screen you're currently working on (that includes\n"
				        "both people behind you and people that may watch your\n"
				        "desktop via VNC).\n\n"
				        "Do you still want to see the password?")))
	{
#warning "FIXME: let the user set a 'don't display again' switch"
		gtk_entry_set_visibility(GTK_ENTRY(P(self)->entry_bind_pw), visible);
	} else if(visible) {
		// un-toggle the button if the user was not sure about
		// displaying the password
		gtk_toggle_button_set_active(toggle, FALSE);
	}
}

static void
server_dialog_clear_pw(GqServerDialog* self)
{
	gtk_entry_set_text(GTK_ENTRY(P(self)->entry_bind_pw), "");
}

static void
server_dialog_update_bind_type(GqServerDialog* self)
{
	GtkComboBox* combo;
	GtkTreeIter  iter;
	GqBindType   type;

	combo = GTK_COMBO_BOX(P(self)->combobox_bind_type);
	gtk_combo_box_get_active_iter(combo, &iter);
	gtk_tree_model_get(gtk_combo_box_get_model(combo), &iter,
			   0, &type,
			   -1);

	gq_server_set_bind_type(P(self)->server, type);
}

static void
server_dialog_update_ask_pw(GqServerDialog* self)
{
	gboolean sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(P(self)->checkbutton_ask_pw));
	gq_server_set_ask_pw(P(self)->server, sensitive);
	gtk_widget_set_sensitive(P(self)->entry_bind_pw, !sensitive);
	gtk_widget_set_sensitive(P(self)->togglebutton_show_pw, !sensitive);
	gtk_widget_set_sensitive(P(self)->button_clear_pw, !sensitive);
}

static void
server_dialog_update_cache_connection(GqServerDialog* self)
{
	gq_server_set_cache_connection(P(self)->server,
				       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(P(self)->checkbutton_cache_connection)));
}

static void
server_dialog_hide_pw(GqServerDialog* self)
{
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(P(self)->togglebutton_show_pw), FALSE);
}

static void
server_dialog_connect_widgets(HerziGladeDialog* dialog,
			      GladeXML        * xml)
{
	struct GqServerDialogPrivate* p = P(dialog);
	GtkCellRenderer* renderer;
	GtkListStore* store;
	gint i;

	p->button_close  = glade_xml_get_widget(xml, "server_button_close");
	p->button_cancel = glade_xml_get_widget(xml, "server_button_revert");

	p->entry_name    = glade_xml_get_widget(xml, "server_entry_name");
	g_signal_connect_swapped(p->entry_name, "notify::text",
				 G_CALLBACK(server_dialog_update_name), dialog);

	p->entry_host    = glade_xml_get_widget(xml, "server_entry_host");
	g_signal_connect_swapped(p->entry_host, "notify::text",
				 G_CALLBACK(server_dialog_update_host), dialog);
	p->entry_port    = glade_xml_get_widget(xml, "server_entry_port");
	g_signal_connect_swapped(p->entry_port, "notify::text",
				 G_CALLBACK(server_dialog_update_port), dialog);
	p->checkbutton_tls = glade_xml_get_widget(xml, "server_checkbutton_tls");
	g_signal_connect_swapped(p->checkbutton_tls, "notify::active",
				 G_CALLBACK(server_dialog_update_tls), dialog);
	p->checkbutton_hide_internals = glade_xml_get_widget(xml, "server_checkbutton_hide_internals");
	g_signal_connect_swapped(p->checkbutton_hide_internals, "notify::active",
				 G_CALLBACK(server_dialog_update_hide_internals), dialog);
	p->entry_search_attribute = glade_xml_get_widget(xml, "server_entry_search_attribute");
	g_signal_connect_swapped(p->entry_search_attribute, "notify::text",
				 G_CALLBACK(server_dialog_update_search_attribute), dialog);

	/* connection widgets */
	p->entry_bind_dn = glade_xml_get_widget(xml, "server_entry_bind_dn");
	g_signal_connect_swapped(p->entry_bind_dn, "notify::text",
				 G_CALLBACK(server_dialog_update_bind_dn), dialog);
	p->entry_bind_pw = glade_xml_get_widget(xml, "server_entry_bind_pw");
	// the password gets updated on clicking OK
	p->togglebutton_show_pw = glade_xml_get_widget(xml, "server_togglebutton_show_pw");
	g_signal_connect_swapped(p->togglebutton_show_pw, "notify::active",
				 G_CALLBACK(server_dialog_update_show_pw), dialog);
	p->button_clear_pw = glade_xml_get_widget(xml, "server_button_clear_pw");
	g_signal_connect_swapped(p->button_clear_pw, "clicked",
				 G_CALLBACK(server_dialog_clear_pw), dialog);
	p->combobox_bind_type = glade_xml_get_widget(xml, "server_combobox_bind_type");
	store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
	g_signal_connect_swapped(p->combobox_bind_type, "changed",
				 G_CALLBACK(server_dialog_update_bind_type), dialog);
	renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(p->combobox_bind_type), renderer, TRUE);
	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(p->combobox_bind_type), renderer,
				       "text", 1,
				       NULL);
	gtk_combo_box_set_model(GTK_COMBO_BOX(p->combobox_bind_type), GTK_TREE_MODEL(store));
	g_object_unref(store);
	for(i = 0; i <= GQ_BIND_SASL; i++) {
		GtkTreeIter  iter;
		gchar const* text;

		switch(i) {
		case GQ_BIND_SIMPLE:
			text = _("Simple");
			break;
		case GQ_BIND_KERBEROS:
			text = _("Kerberos");
			break;
		case GQ_BIND_SASL:
			text = _("SASL");
			break;
		default:
			g_assert_not_reached();
			return;
		}

		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter,
				   0, i,
				   1, text,
				   -1);
		if(!i) {
			gtk_combo_box_set_active_iter(GTK_COMBO_BOX(p->combobox_bind_type), &iter);
		}
	}

	p->checkbutton_ask_pw = glade_xml_get_widget(xml, "server_checkbutton_ask_pw");
	g_signal_connect_swapped(p->checkbutton_ask_pw, "notify::active",
				 G_CALLBACK(server_dialog_update_ask_pw), dialog);
	p->checkbutton_cache_connection = glade_xml_get_widget(xml, "server_checkbutton_cache_connection");
	g_signal_connect_swapped(p->checkbutton_cache_connection, "notify::active",
				 G_CALLBACK(server_dialog_update_cache_connection), dialog);

	p->label_warning = glade_xml_get_widget(xml, "server_label_warning");

	g_signal_connect_swapped(glade_xml_get_widget(xml, "server_notebook"), "switch-page",
				 G_CALLBACK(server_dialog_hide_pw), dialog);

	if(p->server) {
		server_dialog_connect(GQ_SERVER_DIALOG(dialog));
	}
}

static void
gq_server_dialog_class_init(GqServerDialogClass* self_class)
{
	GObjectClass*          object_class = G_OBJECT_CLASS(self_class);
	GtkDialogClass*        dialog_class = GTK_DIALOG_CLASS(self_class);
	HerziGladeDialogClass* glade_dialog_class = HERZI_GLADE_DIALOG_CLASS(self_class);

	/* GObjectClass */
	object_class->dispose      = server_dialog_dispose;
	object_class->get_property = server_dialog_get_property;
	object_class->set_property = server_dialog_set_property;

	g_object_class_install_property(object_class,
					PROP_SERVER,
					g_param_spec_object("server",
							    _("Server"),
							    _("The LDAP-Server edited by this dialog"),
							    GQ_TYPE_SERVER,
							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	/* GtkDialog */
	dialog_class->response = server_dialog_response;

	/* HerziGladeDialog */
	glade_dialog_class->connect_widgets = server_dialog_connect_widgets;
	glade_dialog_class->filename        = PACKAGE_PREFIX "/share/gq/gq.glade";
	glade_dialog_class->root_widget     = "server_dialog";

	/* GqServerDialogClass */
	g_type_class_add_private(self_class, sizeof(struct GqServerDialogPrivate));
}

