/*
 * Copyright (C) 2012 - Juan Ferrer Toribio
 *
 * 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 3 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 <http://www.gnu.org/licenses/>.
 */

#include "vn-login.h"

#define LOGIN_UI	_GUI_DIR"/login.glade"

#define IS_DEFINED(string)		(string && g_strcmp0 (string, ""))
#define BUILDER_GET(obj, name)  ((gpointer) gtk_builder_get_object (obj, name))

typedef struct
{
	VnLogin * obj;
	gchar * user;
	gchar * pass;
	DbConn * conn;
	GThread * thread;
	gboolean connected;
	GError * error;
}
ConnectData;

void	vn_login_cb_gui_logout	(VnGui * gui, VnLogin * obj);
void	vn_login_cb_gui_exit	(VnGui * gui, VnLogin * obj);

G_DEFINE_TYPE (VnLogin, vn_login, G_TYPE_OBJECT);

/**
 * vn_login_new:
 * 
 * Creates a new Gui object.
 *
 * Return value: the created #VnLogin
 **/
VnLogin * vn_login_new (const gchar * application_id)
{
	return g_object_new (VN_TYPE_LOGIN, "application-id", application_id, NULL);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Private

/*
 * Frees the #ConnectData struct
 */
void connect_data_free (ConnectData * connect_data)
{
	if (connect_data->error)
		g_error_free (connect_data->error);

	g_free (connect_data->user);
	g_free (connect_data->pass);
	g_object_unref (connect_data->conn);
	g_object_unref (connect_data->obj);
	g_thread_unref (connect_data->thread);
	g_free (connect_data);	
}

/*
 * Shows the login window to the user.
 */
static void vn_login_show (VnLogin * obj)
{
	gboolean autologin = FALSE;

	if (!obj->window)
	{
		gchar * user;
		gchar * pass;
		GtkBuilder * builder = gtk_builder_new ();

		if (gtk_builder_add_from_file (builder, LOGIN_UI, NULL))
		{
			obj->window = BUILDER_GET (builder, "window");
			obj->user = BUILDER_GET (builder, "user");
			obj->pass = BUILDER_GET (builder, "password");
			obj->remember = BUILDER_GET (builder, "remember");
			obj->connect = BUILDER_GET (builder, "connect");
			obj->stop = BUILDER_GET (builder, "stop");
			obj->settings_button = BUILDER_GET (builder, "settings");
			obj->spinner = BUILDER_GET (builder, "spinner");
			obj->settings_dialog = BUILDER_GET (builder, "settings-dialog");
			obj->plugin = BUILDER_GET (builder, "plugin");
			obj->host = BUILDER_GET (builder, "host");
			obj->schema = BUILDER_GET (builder, "schema");
			obj->ssl_ca = BUILDER_GET (builder, "ssl-ca");
			gtk_builder_connect_signals (builder, obj);

			user = g_settings_get_string (obj->settings, "user");
			pass = g_settings_get_string (obj->settings, "pass"); 

			if (user && g_strcmp0 (user, ""))
				gtk_entry_set_text (obj->user, user);

			if (pass && g_strcmp0 (pass, ""))
			{
				gchar * aux = pass;
				pass = gvn_decode (aux); 
				gtk_entry_set_text (obj->pass, pass);
				gtk_toggle_button_set_active (obj->remember, TRUE);
				g_free (aux);
				autologin = TRUE;
			}

			g_free (user);
			g_free (pass);
		}

		g_object_unref (builder);
	}

	gtk_widget_show_all (GTK_WIDGET (obj->window));
	gtk_widget_grab_focus (GTK_WIDGET (obj->user));
	gtk_widget_hide (obj->spinner);
	gtk_widget_hide (obj->stop);
	
	if (autologin)
		gtk_button_clicked (obj->connect);
}

/*
 * Enables/disables the #GtkSpinner at login dialog.
 */
static void vn_login_set_loading (VnLogin * obj, gboolean loading)
{
	gtk_widget_set_sensitive (GTK_WIDGET (obj->connect), !loading);
	gtk_widget_set_sensitive (GTK_WIDGET (obj->settings_button), !loading);
	gtk_widget_set_sensitive (GTK_WIDGET (obj->user), !loading);
	gtk_widget_set_sensitive (GTK_WIDGET (obj->pass), !loading);
	gtk_widget_set_sensitive (GTK_WIDGET (obj->remember), !loading);

	if (loading)
	{
		gtk_widget_show (obj->spinner);
		gtk_widget_show (obj->stop);
		gtk_widget_hide (GTK_WIDGET (obj->connect));
	}
	else
	{
		gtk_widget_hide (obj->spinner);
		gtk_widget_hide (obj->stop);
		gtk_widget_show (GTK_WIDGET (obj->connect));
	}
}

/*
 * Frees the GUI object.
 */
static void vn_login_free_gui (VnLogin * obj)
{
	if (obj->gui)
	{
		g_object_disconnect (obj->gui
			,"any_signal", vn_login_cb_gui_exit, obj
			,"any_signal", vn_login_cb_gui_logout, obj
			,NULL
		);
		g_clear_object (&obj->gui);
	}
}

/*
 * Shows the settings dialog. 
 */
void vn_login_cb_settings_clicked (GtkButton * button, VnLogin * obj)
{
	gchar * plugin = g_settings_get_string (obj->settings, "plugin");
	gchar * host = g_settings_get_string (obj->settings, "host");
	gchar * schema = g_settings_get_string (obj->settings, "schema");
	gchar * ssl_ca = g_settings_get_string (obj->settings, "ssl-ca");

	gtk_entry_set_text (obj->plugin, plugin);
	gtk_entry_set_text (obj->host, host);
	gtk_entry_set_text (obj->schema, schema);
	gtk_entry_set_text (obj->ssl_ca, ssl_ca);
	gtk_dialog_run (obj->settings_dialog);
	
	g_free (plugin);
	g_free (host);
	g_free (schema);
	g_free (ssl_ca);
}

/*
 * Hides the settings dialog. 
 */
void vn_login_cb_settings_cancel_clicked (GtkButton * button, VnLogin * obj)
{
	gtk_widget_hide (GTK_WIDGET (obj->settings_dialog));
}

/*
 * Hides the settings dialog when escape is pressed. 
 */
void vn_login_settings_on_delete_event (GtkWidget * settings_dialog)
{
	gtk_widget_hide (settings_dialog);
}

/*
 * Applies the changes made on settings dialog. 
 */
void vn_login_cb_settings_apply_clicked (GtkButton * button, VnLogin * obj)
{
	const gchar * plugin = gtk_entry_get_text (obj->plugin);
	const gchar * host = gtk_entry_get_text (obj->host);
	const gchar * schema = gtk_entry_get_text (obj->schema);
	const gchar * ssl_ca = gtk_entry_get_text (obj->ssl_ca);
	
	g_settings_set_string (obj->settings, "plugin", plugin);
	g_settings_set_string (obj->settings, "host", host);
	g_settings_set_string (obj->settings, "schema", schema);
	g_settings_set_string (obj->settings, "ssl-ca", ssl_ca);
	gtk_widget_hide (GTK_WIDGET (obj->settings_dialog));
}

/*
 * Shows the login dialog when user logout from GUI.
 */
void vn_login_cb_gui_logout (VnGui * gui, VnLogin * obj)
{
	g_settings_set_string (obj->settings, "pass", "");
	vn_login_free_gui (obj);
	vn_login_show (obj);
}

/*
 * Destroys the login dialog when user exits from GUI.
 */
void vn_login_cb_gui_exit (VnGui * gui, VnLogin * obj)
{
	vn_login_free_gui (obj);
	gtk_widget_destroy (GTK_WIDGET (obj->window));
}

/*
 * Saves the login information and opens the main GUI. 
 */
static gboolean vn_login_done (ConnectData * connect_data)
{
	VnLogin * obj = connect_data->obj;

	if (connect_data->connected)
	{
		const gchar * user;
	
		user = gtk_entry_get_text (obj->user);
		g_settings_set_string (obj->settings, "user", user);

		if (gtk_toggle_button_get_active (obj->remember))
		{
			const gchar * pass = gtk_entry_get_text (obj->pass);
			gchar * aux = gvn_encode (pass);

			gtk_toggle_button_set_active (obj->remember, FALSE);
			g_settings_set_string (obj->settings, "pass", aux);
			g_free (aux);
		}
	
		gtk_entry_set_text (obj->pass, "");
		gtk_widget_hide (GTK_WIDGET (obj->window));
		obj->gui = vn_gui_new (obj->app, connect_data->conn);
		g_object_connect (obj->gui
			,"signal::logout", vn_login_cb_gui_logout, obj
			,"signal::exit", vn_login_cb_gui_exit, obj
			,NULL
		);
		vn_gui_open (obj->gui);
	}
	else
	{	
		GtkWidget * dialog;

		dialog = gtk_message_dialog_new (obj->window
			,GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT
			,GTK_MESSAGE_ERROR
			,GTK_BUTTONS_OK
			,_("Login error")
		);
		gtk_window_set_title (GTK_WINDOW (dialog), _("Login error"));
		
		if (connect_data->error)
		{
			gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
				"%s", connect_data->error->message);

			if (connect_data->error->code == DB_CONN_ERROR_BAD_LOGIN)
				gtk_entry_set_text (obj->pass, "");
		}

		gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (GTK_WIDGET (dialog));
		vn_login_show (obj);
	}

	vn_login_set_loading (obj, FALSE);
	return FALSE;
}

/*
 * Thread function that tries to open the connection asynchronously.
 */
static void vn_login_thread (ConnectData * connect_data)
{
	gchar * plugin;
	gchar * host;
	gchar * schema;
	gchar * ssl_ca;
	VnLogin * obj = connect_data->obj;

	plugin = g_settings_get_string (obj->settings, "plugin");
	host = g_settings_get_string (obj->settings, "host");
	schema = g_settings_get_string (obj->settings, "schema");
	ssl_ca = g_settings_get_string (obj->settings, "ssl-ca");
	
	if (IS_DEFINED (plugin) && IS_DEFINED (host) && IS_DEFINED (schema))
	{
		if (db_conn_load_plugin (connect_data->conn, plugin, &connect_data->error))
		{
			if (IS_DEFINED (ssl_ca))
				db_conn_set_ssl (connect_data->conn, ssl_ca);

			connect_data->connected = db_conn_open (connect_data->conn
				,host
				,schema
				,connect_data->user
				,connect_data->pass
				,&connect_data->error
			);
		}
	}
	else
		connect_data->error = g_error_new (
			 VN_LOGIN_LOG_DOMAIN
			,VN_LOGIN_ERR_BAD_SETTINGS
			,_("Bad connection settings, please check it.")
		);

	g_free (plugin);
	g_free (host);
	g_free (schema);
	g_free (ssl_ca);
	
	g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
		(GSourceFunc) vn_login_done, connect_data, (GDestroyNotify) connect_data_free);
}

/*
 * Opens the connection.
 */
void vn_login_cb_connect_clicked (GtkButton * button, VnLogin * obj)
{
	ConnectData * connect_data;

	vn_login_set_loading (obj, TRUE);

	connect_data = g_new (ConnectData, 1);
	connect_data->obj = g_object_ref (obj);
	connect_data->user = g_strdup (gtk_entry_get_text (obj->user));
	connect_data->pass = g_strdup (gtk_entry_get_text (obj->pass));
	connect_data->conn = db_conn_new ();
	connect_data->connected = FALSE;
	connect_data->error = NULL;
	connect_data->thread = g_thread_new ("vn-login",
		(GThreadFunc) vn_login_thread, connect_data);
}

/*
 * Stops trying to connect.
 */
void vn_login_cb_stop_clicked (GtkButton * button, VnLogin * obj)
{
	
}

/*
 * Closes the application when login window is destroyed.
 */
void vn_login_cb_destroyed (GtkWidget * window, VnLogin * obj)
{
	obj->window = NULL;
	gtk_main_quit ();
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Public

/**
 * vn_login_run:
 * @obj: a #VnLogin
 * 
 * Starts the main gui application.
 **/
void vn_login_run (VnLogin * obj)
{
	g_return_if_fail (VN_IS_LOGIN (obj));

	if (g_application_register (G_APPLICATION (obj->app), NULL, NULL))
	{
		g_object_set (gtk_settings_get_default (),
			"gtk-button-images", TRUE, NULL
		);
		vn_login_show (obj);	
		gtk_main ();
	}
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties

enum
{
	  PROP_APPLICATION_ID = 1
};

static void vn_login_set_property (VnLogin * obj, guint id,
	const GValue * value, GParamSpec * pspec)
{
	switch (id)
	{
		case PROP_APPLICATION_ID:
		{
			const gchar * id = g_value_get_string (value);
			obj->app = gtk_application_new (id, G_APPLICATION_FLAGS_NONE);
			obj->settings = g_settings_new (id);
			break;
		}
		default:		
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
	}
}

static void vn_login_get_property (VnLogin * obj, guint id,
	GValue * value, GParamSpec * pspec)
{	
	switch (id)
	{
		default:		
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
	}
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class

static void vn_login_init (VnLogin * obj)
{
	obj->gui = NULL;
	obj->window = NULL;
	obj->settings = NULL;
	obj->app = NULL;
}

static void vn_login_finalize (VnLogin * obj)
{
	vn_login_free_gui (obj);
	g_clear_object (&obj->settings);
	g_clear_object (&obj->app);
	G_OBJECT_CLASS (vn_login_parent_class)->finalize (G_OBJECT (obj));
}

static void vn_login_class_init (VnLoginClass * k)
{
	GObjectClass * klass = G_OBJECT_CLASS (k);
	klass->finalize = (GObjectFinalizeFunc) vn_login_finalize;
	klass->set_property = (GObjectSetPropertyFunc) vn_login_set_property;
	klass->get_property = (GObjectGetPropertyFunc) vn_login_get_property;

	g_object_class_install_property (klass, PROP_APPLICATION_ID,
		g_param_spec_string ("application-id"
			,_("Application id")
			,_("The application identifier")
			,"app.default"
			,G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY
	));
}