/* bug-buddy bug submitting program
 *
 * Copyright (C) 1999 - 2001 Jacob Berkman
 * Copyright 2000, 2001 Ximian, Inc.
 *
 * Author:  jacob berkman  <jacob@bug-buddy.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include "eds-buddy.h"
#include "gdb-buddy.h"
#include "bugzilla.h"
#include "bug-buddy.h"
#include "distribution.h"
#include "proccess.h"
#include "forbidden-words.h"

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <errno.h>

#include <glib.h>
#include <glib/gstdio.h>
#include <glib/gi18n.h>
#include <gnome.h>
#include <libgnomeui/gnome-window-icon.h>
#include <libgnomevfs/gnome-vfs-utils.h>
#include <gtk/gtkbuilder.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <libgnomecanvas/gnome-canvas-pixbuf.h>
#include <libgnome/libgnometypebuiltins.h>
#include <gdk/gdkx.h>

#include <libxml/tree.h>
#include <libxml/parser.h>

#include <gconf/gconf-client.h>

#ifdef HAVE_NETWORKMANAGER
#include <libnm_glib.h>
#endif

#include <libsoup/soup.h>
#include <libsoup/soup-xmlrpc-message.h>

#include <sys/types.h>
#include <signal.h>

#define d(x)

#define USE_PROXY_KEY 	"/system/http_proxy/use_http_proxy"
#define PROXY_HOST_KEY	"/system/http_proxy/host"
#define PROXY_PORT_KEY	"/system/http_proxy/port"
#define USE_PROXY_AUTH	"/system/http_proxy/use_authentication"
#define PROXY_USER	"/system/http_proxy/authentication_user"
#define PROXY_PASSWORD	"/system/http_proxy/authentication_password"
#define ACCESSIBILITY_KEY "/desktop/gnome/interface/accessibility"
#define GTK_THEME_KEY	"/desktop/gnome/interface/gtk_theme"
#define ICON_THEME_KEY  "/desktop/gnome/interface/icon_theme"
#define DESKTOP_IS_HOME_DIR "/apps/nautilus/preferences/desktop_is_home_dir"

static GOptionData gopt_data;
static int bug_count = 0;
static GHashTable *apps = NULL;

static const GOptionEntry options[] = {
	{ "name",       '\0', 0, G_OPTION_ARG_STRING,   &gopt_data.name,          N_("Name of contact"),                         N_("NAME") },
	{ "email",      '\0', 0, G_OPTION_ARG_STRING,   &gopt_data.email,         N_("Email address of contact"),                N_("EMAIL") },
	{ "package",    '\0', 0, G_OPTION_ARG_STRING,   &gopt_data.package,       N_("Package containing the program"),          N_("PACKAGE") },
	{ "package-ver",'\0', 0, G_OPTION_ARG_STRING,   &gopt_data.package_ver,   N_("Version of the package"),                  N_("VERSION") },
	{ "appname",    '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.app_file,      N_("File name of crashed program"),            N_("FILE") },
	{ "pid",        '\0', 0, G_OPTION_ARG_INT,      &gopt_data.pid,           N_("PID of crashed program"),                  N_("PID") },
	{ "core",       '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.core_file,     N_("Core file from program"),                  N_("FILE") },
	{ "include",    '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.include_file,  N_("Text file to include in the report"),      N_("FILE") },
	{ "minidump",   '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.minidump_file, N_("MiniDump file with info about the crash"), N_("FILE") },
	{ "kill",       '\0', 0, G_OPTION_ARG_INT,      &gopt_data.kill,          N_("PID of the program to kill after the report"), N_("KILL") },
	{ NULL }
};

enum {
	NETWORK_CONNECTED,
	NETWORK_DISCONNECTED,
	NETWORK_UNKNOWN
};

static void fill_stderr_info (GtkBuilder *ui);
static void fill_custom_info (BugzillaApplication *app, GtkBuilder *ui);
static void close_callback   (GtkWidget *widget, gpointer user_data);
static void bug_buddy_quit   (GtkBuilder *ui);


static void
buddy_error (GtkWidget *parent, const char *msg, ...)
{
	GtkWidget *w;
	GtkDialog *d;
	gchar *s;
	va_list args;

	/* No va_list version of dialog_new, construct the string ourselves. */
	va_start (args, msg);
	s = g_strdup_vprintf (msg, args);
	va_end (args);

	w = gtk_message_dialog_new (GTK_WINDOW (parent),
				    0,
				    GTK_MESSAGE_ERROR,
				    GTK_BUTTONS_OK,
				    "%s",
				    s);
	d = GTK_DIALOG (w);
	gtk_dialog_set_default_response (d, GTK_RESPONSE_OK);
	gtk_dialog_run (d);
	gtk_widget_destroy (w);
	g_free (s);
}

static void
lock_text (GtkBuilder *ui)
{
	GtkTextView *text_view;
	GtkTextBuffer *buffer;
	GtkTextIter start;
	GtkTextIter end;
	static GtkTextTag *tag = NULL;
	char *text;

	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_start_iter (buffer, &start);
	gtk_text_buffer_get_end_iter (buffer, &end);
	text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);

	if (!tag) {
		GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (text_view));
		tag = gtk_text_buffer_create_tag (buffer, "lock_tag", 
						  "editable", FALSE,
						  /* I don't like how it looks like dimming also fg
						  "foreground-gdk", &style->fg[GTK_STATE_INSENSITIVE], */
						  "background-gdk", &style->bg[GTK_STATE_INSENSITIVE],
						  NULL);
	}

	if (gtk_text_iter_forward_search (&start, "Backtrace was generated from",
					  GTK_TEXT_SEARCH_TEXT_ONLY,
					  NULL, &end, NULL)) {
		gtk_text_iter_forward_line (&end);
		gtk_text_buffer_apply_tag_by_name (buffer, "lock_tag", &start, &end);
	}
}


static gboolean
search_forbidden_words (GtkBuilder *ui)
{
	GtkTextView *text_view;
	GtkTextBuffer *buffer;
	int i;
	gboolean found = FALSE;
	static GtkTextTag *tag = NULL;

	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	if (!tag) {
		tag = gtk_text_buffer_create_tag (buffer, "forbbiden_tag",
						  "foreground", "white",
						  "background", "blue",
						  NULL);  
	}

	for (i = 0; forbidden_words[i]; i++) {
		GtkTextIter start;
		GtkTextIter end;

		gtk_text_buffer_get_start_iter (buffer, &start);
		while (gtk_text_iter_forward_search (&start, forbidden_words[i],
                                                     GTK_TEXT_SEARCH_TEXT_ONLY,
                                                     &start, &end, NULL)) {
			gtk_text_buffer_apply_tag_by_name (buffer, "forbbiden_tag", &start, &end);
			start = end;
			found = TRUE;
		}
	}

	return found;
}



static void
copy_review (GtkWidget *button, gpointer data)
{
	GtkTextView *text_view;
	GtkTextBuffer *buffer;
	GtkTextIter start;
	GtkTextIter end;
	GtkBuilder *ui = (GtkBuilder*) data;

	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_start_iter (buffer, &start);
	gtk_text_buffer_get_end_iter (buffer, &end);
	gtk_text_buffer_select_range (buffer, &start, &end);
	gtk_text_buffer_copy_clipboard (buffer, gtk_clipboard_get (GDK_NONE));
	
}

static void
edit_review (GtkWidget *button, gpointer data)
{
	GtkTextView *text_view;
	GtkBuilder *ui = (GtkBuilder*) data;
	gboolean editable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));

	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	gtk_text_view_set_editable (text_view, editable);
	if (editable) {
		lock_text (ui);
	}

}

static void
close_review (GtkWidget *button, gpointer data)
{
	GtkWidget *review_dialog = GTK_WIDGET (data);

	gtk_widget_hide (review_dialog);
}

static gboolean
delete_review (GtkWidget *widget, GdkEvent  *event, gpointer user_data)  
{
	gtk_widget_hide (widget);

	return TRUE; /* don't destroy */
}

static void
show_review (GtkWidget *button, gpointer data)
{
	GtkWidget *review_dialog, *main_window;
	GtkWidget *edit, *copy, *close;
	GtkBuilder *ui = (GtkBuilder*) data;
	static gboolean initialized = FALSE;

	if (!initialized) {
		review_dialog = GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog"));
		main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window"));
		copy = GTK_WIDGET (gtk_builder_get_object (ui, "copy-review-button"));
		edit = GTK_WIDGET (gtk_builder_get_object (ui, "edit-review-button"));
		close = GTK_WIDGET (gtk_builder_get_object (ui, "close-review-button"));

		gtk_window_set_transient_for (GTK_WINDOW (review_dialog), GTK_WINDOW (main_window));

		g_signal_connect (G_OBJECT (copy), "clicked", G_CALLBACK (copy_review), ui);
		g_signal_connect (G_OBJECT (edit), "toggled", G_CALLBACK (edit_review), ui);
		g_signal_connect (G_OBJECT (close), "clicked", G_CALLBACK (close_review), review_dialog);
		g_signal_connect (G_OBJECT (review_dialog), "delete-event", G_CALLBACK (delete_review), NULL);
		
		initialized = TRUE;
	}

	lock_text (ui);

	gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog")));
}


static GnomeVersionInfo*
get_gnome_version_info (void)
{
	GnomeVersionInfo *version;
	xmlDoc *doc;
	char *xml_file;
	xmlNode *node;
	guchar *platform, *minor, *micro, *distributor, *date;

	version = g_new0 (GnomeVersionInfo, 1);

	xml_file = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_DATADIR,
					      "gnome-about/gnome-version.xml",
					      TRUE, NULL);
	if (!xml_file)
		return NULL;
	doc = xmlParseFile (xml_file);
	g_free (xml_file);

	if (!doc)
		return NULL;

	platform = minor = micro = distributor = date = NULL;
	
	for (node = xmlDocGetRootElement (doc)->children; node; node = node->next) {
		if (!strcmp ((char *)node->name, "platform"))
			platform = xmlNodeGetContent (node);
		else if (!strcmp ((char *)node->name, "minor"))
			minor = xmlNodeGetContent (node);
		else if (!strcmp ((char *)node->name, "micro"))
			micro = xmlNodeGetContent (node);
		else if (!strcmp ((char *)node->name, "distributor"))
			distributor = xmlNodeGetContent (node);
		else if (!strcmp ((char *)node->name, "date"))
			date = xmlNodeGetContent (node);
	}
	
	if (platform && minor && micro)
		version->gnome_platform = g_strdup_printf ("%s.%s.%s", platform, minor, micro);
  
	if (distributor && *distributor)
		version->gnome_distributor = g_strdup ((char *)distributor);
	
	if (date && *date)
		version->gnome_date = g_strdup ((char *)date);

	xmlFree (platform);
	xmlFree (minor);
	xmlFree (micro);
	xmlFree (distributor);
	xmlFree (date);
	
	xmlFreeDoc (doc);

	return version;
}

static gboolean
update_progress_bar (gpointer data)
{
	GtkProgressBar *pbar = GTK_PROGRESS_BAR (data);

	gtk_progress_bar_pulse (pbar);

	return TRUE;
}

static void
save_email (const char *email)
{
	GConfClient *conf_client;

	conf_client = gconf_client_get_default ();
	gconf_client_set_string (conf_client, "/apps/bug-buddy/email_address", email, NULL);
	g_object_unref (conf_client);
}

static void
link_callback (GtkLinkButton *button, gpointer user_data)
{
	const gchar *link = gtk_link_button_get_uri (button);

	if (gnome_vfs_url_show (link) != GNOME_VFS_OK) {
		char *text;

		text = g_markup_printf_escaped (_("Bug Buddy was unable to view the link \"%s\"\n"), link);
		buddy_error (NULL, text);
		g_free (text);
	}

	return;
}

static void
save_to_file (const gchar *filename, const gchar *text)
{
	GError *error = NULL;

	if (!g_file_set_contents (filename, text, -1, &error)) {
		g_warning ("Unable to save document %s: %s\n", filename, error->message);
		g_error_free (error);
	}
}


static void
network_error (SoupMessage *msg, GtkBuilder *ui)
{

	GtkWidget *dialog;
	int res;
		
	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
		
	dialog = gtk_message_dialog_new_with_markup (NULL,
						      GTK_DIALOG_MODAL,
						      GTK_MESSAGE_QUESTION,
						      GTK_BUTTONS_YES_NO,
						      _("<span weight=\"bold\">Network Connection Error</span>\n"
						      "Maybe no Network Connection available.\n"
						      "Do you want to store this report until "
						      "a Network Connection is available?"));
	res = gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);

	if (res == GTK_RESPONSE_YES) {
		gchar *dirname;
		gchar *filename;
		xmlChar *message_string;
	       
		dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
		if (!g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
			g_mkdir_with_parents (dirname, 0755);
		}

		filename = g_strdup_printf ("%s/%ld", dirname, (long)time (NULL));
					
		message_string = soup_xmlrpc_message_to_string (SOUP_XMLRPC_MESSAGE (msg));

		save_to_file (filename, (const gchar*) message_string);

		xmlFree (message_string);
		g_free (dirname);
		g_free (filename);
	}
		
	bug_buddy_quit (ui);
	return;
}

static void
remove_pending_reports (void)
{
		GDir *dir;
		char *dirname;
		GError *error = NULL;

		dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
		dir = g_dir_open (dirname, 0, &error);
		if (dir) {
			const char *name = g_dir_read_name (dir);
			while (name) {
				char *path = g_strdup_printf ("%s/%s", dirname, name);
				g_remove (path);
				g_free (path);
				name = g_dir_read_name (dir);
			}
			g_dir_close (dir);
		}

		g_remove (dirname);
		g_free (dirname);
}

static void
all_sent (GtkBuilder *ui)
{
	GtkWidget *close_button;

	/* hide the progressbar */
	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));

	close_button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
	gtk_widget_show (close_button);
}

static void
previous_sent (SoupMessage *msg, GtkBuilder *ui)
{
	if (--bug_count == 0) {
		all_sent (ui);
	}
}


static void
bug_sent (SoupMessage *msg, GtkBuilder *ui)
{
	GtkWidget *button;
	GtkWidget *image;
	char *text = NULL;
	char *errmsg = NULL;
	char *str = NULL;
	char *ptr = NULL;
	long bugid;
	char *response;
	char *tmp;
	GtkWidget *urlbutton;
	GtkRequisition requisition;
	GError *err = NULL;

	button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
	gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
	gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
		
	image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON), 
	gtk_button_set_image (GTK_BUTTON (button), image);

	if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
		network_error (msg, ui);
	} else {
		remove_pending_reports ();
	}

	/* parse the XML-RPC response */
	response = bugzilla_parse_response (msg, &err);
	if (response != NULL) {
		bugid = strtol (response, &tmp, 10);
		GtkWidget *main_vbox;
		
		/* we need a reference to the vbox containing the text so that we
	 	* can add a GtkLinkButton to the bug report */
		main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox"));

		if (response == tmp) {
			char *url;
			url = strstr (response, "ViewURL=");
			if (url) 
				url += strlen ("ViewURL=");
			else 
				url = "";

			text = g_strdup (url);
		} else 
			text = g_strdup_printf ("http://bugzilla.gnome.org/show_bug.cgi?id=%ld", bugid);

		/* create a clickable link to the bug report */
		urlbutton = gtk_link_button_new (text);
		g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL);
		gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0);

		gtk_widget_show (urlbutton);
		g_free (text);

		text = g_markup_printf_escaped (_("A bug report detailing your software crash has been sent to GNOME. "
		                                  "This information will allow the developers to understand the cause "
		                                  "of the crash and prepare a solution for it.\n\n"
		                                  "You may be contacted by a GNOME developer if more details are "
		                                  "required about the crash.\n\n"
		                                  "You can view your bug report and follow its progress with this URL:\n")) ;

		gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), text);
		g_free (text);
		save_email (gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry"))));
	} else {
		errmsg = _("Bug Buddy has encountered an error while submitting your report "
		           "to the Bugzilla server.  Details of the error are included below.\n\n");

		if (err != NULL) {
			switch (err->code) {
			case BUGZILLA_ERROR_RECV_BAD_STATUS:
				text = g_strdup_printf (_("Server returned bad state.  This is most likely a server "
				                          "issue and should be reported to bugmaster@gnome.org\n\n%s"), 
				                        err->message);
				break;
			case BUGZILLA_ERROR_RECV_PARSE_FAILED:
				text = g_strdup_printf (_("Failed to parse the xml-rpc response.  Response follows:\n\n%s"), 
				                        err->message);
				break;
			case BUGZILLA_ERROR_RECV_FAULT:
				/* in this case, the error message returned is the faultCode and faultString from
				 * the XML-RPC response, separated by a colon.  We construct our error message
				 * based on the faultString */
				ptr = strstr (err->message, ":");
				if (ptr == NULL) {
					text = g_strdup_printf (_("Bugzilla reported an error when trying to process your "
					                          "request, but was unable to parse the response."));
					break;
				}

				/* skip the colon */
				ptr++;
				
				/* see http://cvs.gnome.org/viewcvs/bugzilla-newer/Bugzilla/RPC.pm?view=markup */
				if (g_str_equal (ptr, "invalid_username")) {
					text = g_strdup_printf (_("The email address you provided is not valid."));
				} else if (g_str_equal (ptr, "account_disabled")) {
					text = g_strdup_printf (_("The account associated with the email address "
				                                  "provided has been disabled."));
				} else if (g_str_equal (ptr, "product_doesnt_exist")) {
					text = g_strdup_printf (_("The product specified doesn't exist or has been "
				                                  "renamed.  Please upgrade to the latest version."));
				} else if (g_str_equal (ptr, "component_not_valid")) {
					text = g_strdup_printf (_("The component specified doesn't exist or has been "
				                                  "renamed.  Please upgrade to the latest version."));
				} else if (g_str_equal (ptr, "require_summary")) {
					text = g_strdup_printf (_("The summary is required in your bug report. "
				                                  "This should not happen with the latest Bug Buddy."));
				} else if (g_str_equal (ptr, "description_required")) {
					text = g_strdup_printf (_("The description is required in your bug report. "
				                                  "This should not happen with the latest Bug Buddy."));
				} else {
					text = g_strdup_printf (_("The fault code returned by Bugzilla is not recognized. "
				                                  "Please report the following information to "
					                          "bugzilla.gnome.org manually:\n\n%s"), err->message);
				}

				break;
			default:
				text = g_strdup_printf (_("An unknown error occurred.  This is most likely a problem with "
				                          "bug-buddy. Please report this problem manually at bugzilla."
			                                  "gnome.org\n\n"));
				break;
			}

			str = g_strconcat (errmsg, text, NULL);
			gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), str);

			g_free (str);
			g_free (text);
			g_error_free (err);
		}
	}
	
	if (--bug_count == 0) {
		all_sent (ui);
	}

	gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui, "main-window")), &requisition);
	gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
			   requisition.width, requisition.height);

	g_free (response);
	
}


static void
set_proxy (SoupSession *session)
{
	GConfClient *gconf_client;
	char *host;
	int port;
	char *proxy_uri;
	SoupUri *uri;
	char *username = NULL;
	char *password = NULL;
	
	
	gconf_client = gconf_client_get_default ();

	if (gconf_client_get_bool (gconf_client, USE_PROXY_KEY, NULL) == FALSE) {
		g_object_unref (gconf_client);
		return;
	}

	host = gconf_client_get_string (gconf_client, PROXY_HOST_KEY, NULL);
	if (host == NULL) {
		g_object_unref (gconf_client);
		return;
	}
	port = gconf_client_get_int (gconf_client, PROXY_PORT_KEY, NULL);
	if (port == 0)
		port = 80;

	if (gconf_client_get_bool (gconf_client, USE_PROXY_AUTH, NULL)) {
		username = gconf_client_get_string (gconf_client, PROXY_USER, NULL);
		password = gconf_client_get_string (gconf_client, PROXY_PASSWORD, NULL);
	}

	if (username && password)
		proxy_uri = g_strdup_printf ("http://%s:%s@%s:%d", username, password, host, port);
	else
		proxy_uri = g_strdup_printf ("http://%s:%d", host, port);

	uri = soup_uri_new (proxy_uri);
	g_object_set (G_OBJECT (session), "proxy-uri", uri, NULL);
	
	g_free (host);
	g_free (username);
	g_free (password);
	g_free (proxy_uri);
	soup_uri_free (uri);
	g_object_unref (gconf_client);

}


static char*
create_report_title (BugzillaApplication *app, int type, const char *description)
{
	char *title;
	long size = 0;
	char *tmp = NULL;

	if (description) {
		tmp = g_malloc0 (256); /* This should be safe enough for 24 UTF-8 chars.
					* anyway, I miss a g_utf8_strndup :) */ 
		size = g_utf8_strlen (description, -1);
		if (size > 24) {
			g_utf8_strncpy (tmp, description, 24);
		} else {
			g_utf8_strncpy (tmp, description, size);
		}
	}

	if (type == BUG_TYPE_CRASH) {
		title = g_strdup_printf ("crash in %s: %s%s", app->cname, 
				         tmp ? tmp : "empty description",
					 (tmp && size > 24) ? "..." : "");
	} else {
		title = g_strdup_printf ("%s: %s%s", app->cname,
					 tmp ? tmp : "empty description",
			       		 (tmp && size > 24) ? "..." : "");
	}

	g_free (tmp);

	return title;
}
static void
send_report (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui)
{
	GtkTextView *text_view;
	GtkTextBuffer *buffer;
	GtkTextIter start;
	GtkTextIter end;
	int type;
	char *gdb_text;
	char *details_text;
	char *title;
	char *final_text;
	const char *email;
	SoupSession *session;
        SoupXmlrpcMessage *message;
	GError *err = NULL;

	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check")));
	
	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_start_iter (buffer, &start);
	gtk_text_buffer_get_end_iter (buffer, &end);
	gdb_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);

	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "details-view"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_start_iter (buffer, &start);
	gtk_text_buffer_get_end_iter (buffer, &end);
	details_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);

	type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(ui), "type"));
	final_text = g_strdup_printf ("%s%s\n\n\n%s", 
				      type == BUG_TYPE_CRASH ? "What were you doing when the application crashed?\n" : "",
				      details_text != NULL ? details_text : "",
				      gdb_text != NULL ? gdb_text : "<empty backtrace>");
	
	email = gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry")));
	title = create_report_title (app, type, details_text);

	message = bugzilla_create_report (app, type, gnome_version, email, title, final_text, ui, gopt_data.minidump_file, &err);
	if (message == NULL) {
		char *text;

		if (err != NULL) {
			text = g_strdup_printf (_("Unable to create the bug report: %s\n"), err->message);
		} else {
			text = g_strdup_printf (_("There was an error creating the bug report\n"));
		}
		
		buddy_error (NULL, text);
		g_free (text);
		g_free (gdb_text);
		g_free (details_text);
		g_free (title);
		g_free (final_text);
		bug_buddy_quit (ui);
		return;
	}

	session = soup_session_async_new ();
	set_proxy (session);


	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ui, "pending-reports-check")))) {
		GDir *dir;
		char *dirname;
		GError *error = NULL;

		dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
		dir = g_dir_open (dirname, 0, &error);
		if (dir) {
			const char *name = g_dir_read_name (dir);
			while (name != NULL) {
				char *path;
				char *contents;
				
				path = g_strdup_printf ("%s/%s", dirname, name);
				if (g_file_get_contents (path, &contents, NULL, NULL)) {
					SoupXmlrpcMessage *msg;
					msg = soup_xmlrpc_message_new ("http://bugzilla.gnome.org/bugbuddy.cgi");
					soup_xmlrpc_message_from_string (msg, contents);
					bug_count++;
					soup_xmlrpc_message_persist (msg);
        				soup_session_queue_message (session, SOUP_MESSAGE (msg),
               		                    			    (SoupMessageCallbackFn)previous_sent, ui);
					g_free (contents);
				}
				g_free (path);
				name = g_dir_read_name (dir);
			}

			g_dir_close (dir);
		}
	}

	bug_count++;

	soup_xmlrpc_message_persist (message);
        soup_session_queue_message (session, SOUP_MESSAGE (message),
                                    (SoupMessageCallbackFn)bug_sent, ui);
	g_free (gdb_text);
	g_free (details_text);
	g_free (title);
	g_free (final_text);

	gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
				   _("Sending..."));

}

/* A local part is valid if it is one or more valid characters. */
static gboolean
email_local_part_is_valid (const char *local_part)
{
	const char *character;

	if (!local_part[0])
		return FALSE;

	for (character = local_part; *character; character++) {
		/* RFC 3696 says *any* printable ASCII character can
		 * appear in local-part, subject to quoting rules. */
		if (g_ascii_isprint (*character))
			continue;

		/* Not valid character, not valid local part. */
		return FALSE;
	}

	return TRUE;
}

/* A domain label is valid if it is one or more valid characters. */
static gboolean
email_domain_label_is_valid (const char *domain_label)
{
	const char *character;
	int i;

	/* Validate each character, whilst measuring length, i. */
	for (i = 0; *(character = domain_label + i); i++) {

		/* If character is alphanumeric it is valid. */
		if (g_ascii_isalnum (*character))
			continue;

		/* If it's a hyphen, it's also valid. */
		if (*character == '-')
			continue;

		/* Anything else is invalid */
		return FALSE;
	}

	/* Labels must be between 1 and 63 characters long */
	if (i < 1 || i > 63) {
		return FALSE;
	}

	return TRUE;
}

/* A domain is valid if it is one or more valid, dot-separated labels. */
static gboolean
email_domain_is_valid (const char *domain)
{
	char **labels;
	char **this_label;
	
	/* If there is no domain, there are no domain labels and the domain is
	 * not valid. */ 
	if (!domain[0])
		return FALSE;

	/* Split the domain on the dot to validate labels. */
	labels = g_strsplit (domain, ".", 0);

	for (this_label = labels; *this_label; this_label++) {
		if (!email_domain_label_is_valid (*this_label)) {
			g_strfreev (labels);
			return FALSE;
		}
	}

	g_strfreev (labels);
	return TRUE;
}

/* Check for *simple* email addresses of the form user@host, with checks
 * in characters used, and sanity checks on the form of host.
 */
/* FIXME: Should we provide a useful error message? */
static gboolean
email_is_valid (const char *address)
{
	char *local_part;
	char *domain;
	char *at_sign;
	gboolean is_valid;

	/* Split on the *last* '@' character: */
	at_sign = strrchr (address, '@');

	if (at_sign == NULL) 
		return FALSE;

	local_part = g_strndup (address, at_sign - address);
	domain = g_strdup (at_sign + 1);

	/* Check each part is valid */
	is_valid = email_local_part_is_valid (local_part)
		&& email_domain_is_valid (domain);

	g_free (local_part);
	g_free (domain);

	return is_valid;
}

static void
check_email (GtkEditable *editable, gpointer data)
{
	const char *email;
	GtkBuilder *ui = (GtkBuilder*) data;

	email = gtk_entry_get_text (GTK_ENTRY (editable));
	gtk_widget_set_sensitive (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")),
				  email_is_valid (email));
}

	
static void
on_send_clicked (GtkWidget *button, gpointer data)
{
	BugzillaApplication *app;
	GnomeVersionInfo *gnome_version;
	GtkRequisition requisition;
	GtkBuilder *ui = (GtkBuilder*) data;

	app = g_object_get_data (G_OBJECT (ui), "app");
	gnome_version = g_object_get_data (G_OBJECT (ui), "gnome-version");

	/* hide the send button immediately so that the user can't click
	 * it more than once (this will create multiple bugs) */
	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));

	gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "review-box")));

	gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui, "main-window")), &requisition);
	gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
			   requisition.width, requisition.height);
			   
	send_report (app, gnome_version, ui);
}


static gboolean
gdb_insert_text (const gchar *stacktrace, GtkBuilder *ui)
{
	GtkTextView *text_view;
	GtkTextIter end;
	GtkTextBuffer *buffer;

	/* FIXME: These strings are gdb specific, we should add here also dbx */
	const char *bt_step1 = "#1";
	const char *bt_step2 = "#2";
	const char *bt_step3 = "#3";

	if (!g_strrstr (stacktrace, bt_step1) &&
            !g_strrstr (stacktrace, bt_step2) &&
            !g_strrstr (stacktrace, bt_step3)) {
		return FALSE;
	}



	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_end_iter (buffer, &end);

	/* add the stacktrace to the GtkTextView */
	gtk_text_buffer_insert (buffer, &end, stacktrace, strlen (stacktrace));

	return TRUE;
}

static void
show_pending_checkbox_if_pending (GtkBuilder *ui)
{
	char *dirname;
	GtkWidget *check;

	dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
	if (g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
		check = GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check"));
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE);
		gtk_widget_show (check);
	}
	g_free (dirname);
}

static GtkWidget*
create_debuginfo_link (void)
{
	GtkWidget *urlbutton;

	/* create a clickable link to the bug report */
	urlbutton = gtk_link_button_new_with_label ("http://live.gnome.org/GettingTraces/DistroSpecificInstructions", 
    	/* Translators: This is the hyperlink which takes to http://live.gnome.org/GettingTraces/DistroSpecificInstructions
         * page. Please also mention that the page is in English */
						    _("Getting useful crash reports"));
	g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL);

	return urlbutton;
}


static void
add_minidump_text_output (const char *minidump_file, GtkBuilder *ui)
{
	GtkTextView *text_view;
	GtkTextIter end;
	GtkTextBuffer *buffer;
	gchar *command_line;
	gchar *standard_output, *standard_error;
	gint exit_status;
	GError *error;


	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_end_iter (buffer, &end);

	command_line = g_strdup_printf ("minidump_dump %s", minidump_file);
	if (g_spawn_command_line_sync (command_line, &standard_output, &standard_error,
				      &exit_status, &error)) {
		gtk_text_buffer_insert (buffer, &end, standard_output, strlen (standard_output));
		g_free (standard_output);
		g_free (standard_error);
	} else {
		g_error_free (error);
	}

}





static void
useless_finished (GtkBuilder *ui)
{

	GtkWidget *button, *image, *main_vbox, *urlbutton;
	BugzillaApplication *app;
	char *label_text;

	app = g_object_get_data (G_OBJECT (ui), "app");

	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));

	label_text = g_markup_printf_escaped (_("The application %s crashed. The bug reporting tool was "
						"unable to collect enough information about the crash to be "
						"useful to the developers.\n\n"
						"In order to submit useful reports, please consider installing "
						"debug packages for your distribution.\n"
						"Click the link below to get information about how to install "
						"these packages:\n"),
			      			app->name);
	gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
		    	label_text);

	main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox"));
	urlbutton = create_debuginfo_link ();
	gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0);

	gtk_widget_show (urlbutton);

	g_free (label_text);

	button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
	gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
	gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
		
	image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON), 
	gtk_button_set_image (GTK_BUTTON (button), image);

	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "email-entry")));
}


static void
known_app_finished (GtkBuilder *ui)
{
	BugzillaApplication *app;
	GtkWidget *email_entry;
	GtkWidget *button;
	char *default_email;
	char *lang_note, *label_text, *s;
	const char *en_lang_note = N_("\n\nPlease write your report in English, if possible.");

	app = g_object_get_data (G_OBJECT (ui), "app");

	fill_custom_info (app, ui);
	fill_stderr_info (ui);

	gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
	gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));

	lang_note = gettext (en_lang_note);

	label_text = g_strconcat (_("Information about the %s application crash has been successfully collected.  "
				    "Please provide some more details about what you were doing when "
				    "the application crashed.\n\n"

				    "A valid email address is required.  This will allow the developers to "
				    "contact you for more information if necessary."),
				  strcmp (lang_note, en_lang_note) ? lang_note : NULL,
				  NULL);

	s = g_markup_printf_escaped (label_text, app->name);
	
	gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
			    s);

	g_free (s);
	g_free (label_text);

	show_pending_checkbox_if_pending (ui);
	
	button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button"));
	g_signal_connect (button, "clicked", 
	                  G_CALLBACK (on_send_clicked), ui);

	email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"));
	g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui);

	default_email = get_default_user_email ();

	if (default_email != NULL) {
		gtk_entry_set_text (GTK_ENTRY (email_entry), default_email);
		g_free (default_email);
	} else {
		gtk_widget_set_sensitive (button, FALSE);
	}

	if (search_forbidden_words (ui)) {
		char *review_text = g_markup_printf_escaped ("<small><i><span weight=\"bold\">%s</span>  %s</i></small>",
							     _("WARNING:"),		
							     _("Some sensitive data is likely present in the crash details.  "
							       "Please review and edit the information if you are concerned "
							       "about transmitting passwords or other sensitive data."));
		gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "review-label")), review_text);
	}
	g_signal_connect (gtk_builder_get_object (ui, "review-button"), "clicked", G_CALLBACK (show_review), ui);
	gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-box")));

	gtk_widget_grab_focus (GTK_WIDGET (gtk_builder_get_object (ui, "details-view")));
}

static void
gdb_finished (const gchar *stacktrace, gpointer data)
{
	GtkBuilder *ui = (GtkBuilder*) data;

	if (gdb_insert_text (stacktrace, ui)) {
		known_app_finished (ui);
	} else {
		useless_finished (ui);
	}
}



static void
on_save_clicked (GtkWidget *button, gpointer user_data)
{
	GtkBuilder *ui = (GtkBuilder *)user_data;
	GtkWidget *dialog;
	const char *desktop;
	char *filename;
	gboolean desktop_is_home_dir, saved;
	GConfClient *gconf_client;
	
	saved = FALSE;

	dialog = gtk_file_chooser_dialog_new (_("Save File"),
	                                      GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
	                                      GTK_FILE_CHOOSER_ACTION_SAVE,
	                                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
	                                      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
	                                      NULL);

	gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);

	gconf_client = gconf_client_get_default ();
	desktop_is_home_dir = gconf_client_get_bool (gconf_client, DESKTOP_IS_HOME_DIR, NULL);
	g_object_unref (gconf_client);

	if (desktop_is_home_dir)
		desktop = g_get_home_dir();
	else
		desktop = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);

	gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), desktop);

	filename = g_strconcat (gopt_data.app_file, _("-bugreport.txt"), NULL);
	gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
	g_free (filename);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
		char *filename;
		GtkTextView   *text_view;
		GtkTextBuffer *buffer;
		GtkTextIter    start;
		GtkTextIter    end;
		gchar         *text;

		text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
		buffer = gtk_text_view_get_buffer (text_view);
		gtk_text_buffer_get_start_iter (buffer, &start);
		gtk_text_buffer_get_end_iter (buffer, &end);
		text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);

		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
		save_to_file (filename, text);
		g_free (filename);
		g_free (text);
		saved = TRUE;
	}

	gtk_widget_destroy (dialog);
	if (saved) {
		bug_buddy_quit (ui);
	}
}

static void 
focus_details (GtkWidget *widget, gpointer data)
{
	gtk_widget_grab_focus (widget);
}

	

static void
unknown_app_finished (GtkBuilder *ui)
{
	GtkWidget *button;
	char *label_text;

	fill_stderr_info (ui);

	if (gopt_data.minidump_file) {
		add_minidump_text_output (gopt_data.minidump_file, ui);
	}
	
	/* don't need user input, so hide these widgets */
	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
	
	/* make the send button into a save button :-) */
	button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button"));
	gtk_button_set_label (GTK_BUTTON (button), _("_Save Bug Report"));
	gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
	g_signal_connect (GTK_BUTTON (button), "clicked", G_CALLBACK (on_save_clicked), ui);
	gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));

	label_text = g_markup_printf_escaped (_("The application %s has crashed.\n"
			      "Information about the crash has been successfully collected.\n\n"
	                      "This application is not known to bug-buddy, therefore the "
	                      "bug report cannot be sent to the GNOME Bugzilla.  Please save the "
	                      "bug to a text file and report it to the appropriate bug tracker "
	                      "for this application."), gopt_data.app_file);
	gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), label_text);

	/* FIXME: If we just grab the focus here to the GtkTextView it will crash on the blink_cb because
	 * the window is nop mapped! Is this a gtk+ bug? are we doing something wrong?
	 * Let's do a funny Workaround: */
	g_signal_connect_after (gtk_builder_get_object (ui, "details-view"), "realize", G_CALLBACK (focus_details), NULL);
	gtk_widget_realize (GTK_WIDGET (gtk_builder_get_object (ui, "details-view")));
}




static void
gdb_finished_unknown_app (const gchar *stacktrace, gpointer data)
{
	GtkBuilder *ui = (GtkBuilder*) data;

	gdb_insert_text (stacktrace, ui);
	unknown_app_finished (ui);
}

static void
bug_buddy_quit (GtkBuilder *ui)
{
	gpointer data;

	g_return_if_fail (ui != NULL);

	data = g_object_get_data (G_OBJECT (ui), "sourceid");

	if (data != NULL) {
		guint source_id = GPOINTER_TO_UINT (data);

		/* removes the context from the main loop and kills any remaining
	 	* gdb process */
		if (source_id > 0) {
			g_source_remove (source_id);
			g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (0));
		}
	}

	g_hash_table_destroy (apps);

	g_object_unref (ui);

	gtk_main_quit ();
}

static void
close_callback (GtkWidget *widget, gpointer user_data)
{
	GtkBuilder *ui = (GtkBuilder *)user_data;

	bug_buddy_quit (ui);
}

static void
help_callback (GtkWidget *widget, gpointer user_data)
{
	GnomeProgram *program = (GnomeProgram *)user_data;
	GError *error = NULL;

	gnome_help_display_desktop (program, 
	                            "user-guide",
	                            "user-guide", 
	                            "feedback-bugs",
	                            &error);

	if (error) {
		GtkWidget *error_dialog = 
		    gtk_message_dialog_new (NULL, 
		                            GTK_DIALOG_MODAL,
		                            GTK_MESSAGE_ERROR, 
		                            GTK_BUTTONS_CLOSE,
		                            _("There was an error displaying help: %s"),
		                            error->message);

		g_signal_connect (G_OBJECT (error_dialog), "response", 
		                  G_CALLBACK (gtk_widget_destroy), NULL);

		gtk_window_set_resizable (GTK_WINDOW (error_dialog), FALSE);

		gtk_widget_show (error_dialog);
		g_error_free (error);
		error = NULL;
	}
}

static gboolean
delete_callback (GtkWidget *widget, GdkEvent *event, gpointer data)
{
	close_callback (NULL, data);
	return TRUE;
}

static void
fill_gnome_info (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui)
{
	char *version_info;
	char *distro;
	GtkTextView *text_view;
	GtkTextIter end;
	GtkTextBuffer *buffer;

	g_return_if_fail (app != NULL);
	g_return_if_fail (gnome_version != NULL);
	g_return_if_fail (ui != NULL);
	
	distro = get_distro_name ();
        version_info = g_strdup_printf ("Distribution: %s\n"
                                        "Gnome Release: %s %s (%s)\n"
                                        "BugBuddy Version: %s\n"
                                        "\n",
                                   	distro,
                                   	gnome_version->gnome_platform, gnome_version->gnome_date,
                                   	gnome_version->gnome_distributor, VERSION);

	g_free (distro);
	
	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_end_iter (buffer, &end);
	gtk_text_buffer_insert (buffer, &end, version_info, strlen (version_info));

	g_free (version_info);
}

static void
fill_custom_info (BugzillaApplication *app, GtkBuilder *ui)
{
	GtkTextView   *text_view;
	GtkTextIter    end;
	GtkTextBuffer *buffer;
	gint   status;
	gchar *output;
	gchar *standard_output = NULL;
	gchar *standard_error = NULL;
	GError *error = NULL;

	g_return_if_fail (app != NULL);
	g_return_if_fail (ui != NULL);

	if (app->extra_info_script == NULL) {
		return;
	}

	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_end_iter (buffer, &end);

	if (!g_spawn_command_line_sync (app->extra_info_script, &standard_output, &standard_error,
				        &status, &error)) {
		gchar *error_string = g_strdup_printf ("There was an error running \"%s\" script:\n"
                                                       "%s", app->extra_info_script, error->message);
		gtk_text_buffer_insert (buffer, &end, error_string, strlen (error_string));

		g_free (error);
		g_free (error_string);
		return;
	}

	output = g_strdup_printf ("Output of custom script \"%s\":\n"
				  "%s\n\n",
				  app->extra_info_script,
				  standard_output ? standard_output : "");

	gtk_text_buffer_insert (buffer, &end, output, strlen (output));

	g_free (output);
	g_free (standard_output);
	g_free (standard_error);
}


static void
fill_proccess_info (pid_t pid, GtkBuilder *ui)
{
	GtkTextView *text_view;
	GtkTextIter end;
	GtkTextBuffer *buffer;
	char *mem;
	char *time;
	char *proccess_info;
	
	mem = proccess_get_mem_state (pid);
	time = proccess_get_time (pid);

        proccess_info = g_strdup_printf ("%s\n"
                                         "%s\n"
                                         "\n",
			 		 mem, time);

	g_free (mem);
	g_free (time);
	
	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_end_iter (buffer, &end);
	gtk_text_buffer_insert (buffer, &end, proccess_info, strlen (proccess_info));

	g_free (proccess_info);

}

static void
fill_include_file (char *filename, GtkBuilder *ui)
{
	GtkTextView *text_view;
	GtkTextIter end;
	GtkTextBuffer *buffer;
	char *text;
	GError *error = NULL;

	if (g_file_get_contents (filename, &text, NULL, &error) != TRUE) {
		buddy_error (NULL, error->message);
		g_error_free (error);
		return;
	}
	
	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_end_iter (buffer, &end);
	gtk_text_buffer_insert (buffer, &end, text, strlen (text));

	g_free (text);

}


static void
fill_system_info (GtkBuilder *ui)
{
	GConfClient *gconf_client;
	GtkTextView *text_view;
	GtkTextIter end;
	GtkTextBuffer *buffer;
	GString *system_info;
	struct utsname uts_buf;
	char *str;
	gboolean has_selinux, enforcing, a11y;
	
	g_return_if_fail (ui != NULL);
	
	system_info = g_string_new ("");

	if (uname (&uts_buf) == 0) {
		g_string_append_printf (system_info, "System: %s %s %s %s\n", uts_buf.sysname, uts_buf.release, uts_buf.version, uts_buf.machine);
	}

	/* X server checks */
	g_string_append_printf (system_info, "X Vendor: %s\n", ServerVendor (gdk_display));
	g_string_append_printf (system_info, "X Vendor Release: %d\n", VendorRelease (gdk_display));
	
	
	/* Selinux checks */
	has_selinux = FALSE;
	if (g_file_get_contents ("/proc/filesystems", &str, NULL, NULL)) {
		has_selinux = strstr (str, "selinuxfs") != NULL;
		g_free (str);
	}
	if (has_selinux) {
		enforcing = TRUE;
		if (g_file_get_contents ("/selinux/enforce", &str, NULL, NULL)) {
			enforcing = strcmp (str, "0") != 0;
			g_free (str);
		}
		g_string_append_printf (system_info, "Selinux: %s\n", enforcing?"Enforcing":"Permissive");
	} else {
		g_string_append_printf (system_info, "Selinux: No\n");
	}

	/* A11y and gtk */
	gconf_client = gconf_client_get_default ();
	a11y = gconf_client_get_bool (gconf_client, ACCESSIBILITY_KEY, NULL);
	g_string_append_printf (system_info, "Accessibility: %s\n", a11y?"Enabled":"Disabled");
	str = gconf_client_get_string (gconf_client, GTK_THEME_KEY, NULL);
	g_string_append_printf (system_info, "GTK+ Theme: %s\n", str);
	g_free (str);
	str = gconf_client_get_string (gconf_client, ICON_THEME_KEY, NULL);
	g_string_append_printf (system_info, "Icon Theme: %s\n", str);
	g_free (str);
	g_object_unref (gconf_client);

	g_string_append (system_info, "\n");
	
	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_end_iter (buffer, &end);
	gtk_text_buffer_insert (buffer, &end, system_info->str, system_info->len);

	g_string_free (system_info, TRUE);
}

static void
fill_stderr_info (GtkBuilder *ui)
{
	GtkTextView *text_view;
	GtkTextIter end;
	GtkTextBuffer *buffer;
	GString *stderr_info;
	char *str, *file;
	gchar **lines;
	int n_lines, i;
	
	g_return_if_fail (ui != NULL);
	
	stderr_info = g_string_new ("");

	/* .xsession-errors: read file */
	file = g_build_filename (g_get_home_dir (), ".xsession-errors", NULL);
	if (g_file_get_contents (file, &str, NULL, NULL)) {
		lines = g_strsplit (str, "\n", -1);
		g_free (str);
		n_lines = 0;
		while (lines[n_lines] != NULL) {
			n_lines++;
		}

		if (n_lines > 0) {
			struct stat buf;
			char *mtime_age = NULL;
			time_t age = 0; 

			
			if (stat (file, &buf) == 0) {
				age = time (NULL) - buf.st_mtime;
				if (age > 5) {
					mtime_age = g_strdup_printf (" (%d sec old)", (int) age);
				}
			}
				
			g_string_append_printf (stderr_info,
						"\n\n----------- .xsession-errors%s ---------------------\n",
						mtime_age?mtime_age:"");
			g_free (mtime_age);
		}
			
		for (i = MAX (0, n_lines-16); i < n_lines; i++) {
			if (lines[i][0] != 0) {
				/* Limit line length to 200 chars to avoid excessive data */
				if (strlen (lines[i]) > 200) {
					lines[i][200] = 0;
				}
				
				g_string_append_printf (stderr_info, "%s\n", lines[i]);
			}
		}
		if (n_lines > 0)
			g_string_append (stderr_info, "--------------------------------------------------\n");
		
		g_strfreev (lines);
	}
	g_free (file);

	text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
	buffer = gtk_text_view_get_buffer (text_view);
	gtk_text_buffer_get_end_iter (buffer, &end);
	gtk_text_buffer_insert (buffer, &end, stderr_info->str, stderr_info->len);
	
	g_string_free (stderr_info, TRUE);
}


static GdkPixbuf*
load_icon (const char *icon)
{
	GdkPixbuf *pixbuf = NULL;

	if (g_path_is_absolute (icon)) {
		pixbuf = gdk_pixbuf_new_from_file_at_size (icon, 48, 48, NULL);
	}
	if (pixbuf == NULL) {
		pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
						   icon, 48, 0, NULL);
	}
	if (pixbuf == NULL && strrchr (icon, '.') != NULL) {
		char *name;
		name = g_strndup (icon, strlen (icon) - strlen (strrchr (icon, '.')));
		pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
						   name, 48, 0, NULL);
		g_free (name);
	}

	if (pixbuf == NULL) {
		char *filename;
		filename = g_strdup_printf (BUDDY_ICONDIR"/pixmaps/%s", icon);
		pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 48, 48, NULL);
		g_free (filename);
	}

	return pixbuf;
}
	

int
main (int argc, char *argv[])
{
	gchar *s;
	BugzillaApplication *app;
	GnomeVersionInfo *gnome_version;
	guint progress;
	GtkWidget *main_window;
	GOptionContext *context;
	GnomeProgram *program;
	guint source_id;
	GError *err = NULL;
	GtkBuilder *ui = NULL;
		
	memset (&gopt_data,  0, sizeof (gopt_data));

	bindtextdomain (PACKAGE, GNOMELOCALEDIR);
	bind_textdomain_codeset (PACKAGE, "UTF-8");
	textdomain (PACKAGE);

	context = g_option_context_new (N_("\n\nBug Buddy is a utility that helps report debugging\n"
	                                  "information to the GNOME Bugzilla when a program crashes."));

	g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);

	g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);

	program = gnome_program_init (PACKAGE, VERSION,
	                              LIBGNOMEUI_MODULE,
	                              argc, argv,
	                              GNOME_PARAM_GOPTION_CONTEXT, context,
	                              GNOME_PARAM_APP_DATADIR, BUDDY_DATADIR,
 				      GNOME_CLIENT_PARAM_SM_CONNECT, FALSE,
	                              NULL);

	g_set_application_name (_("Bug Buddy"));
	gtk_window_set_default_icon_name ("bug-buddy");


	s = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_APP_DATADIR,
				       "bug-buddy.gtkbuilder", TRUE, NULL);
	if (s) {
		ui = gtk_builder_new ();
		gtk_builder_add_from_file (ui, s, &err);
		gtk_builder_set_translation_domain (ui, GETTEXT_PACKAGE);
	}

	if (!ui) {
		buddy_error (NULL, 
			     _("Bug Buddy could not load its user interface file.\n"
			       "Please make sure Bug Buddy was installed correctly."));
		g_object_unref (program);
		return 0;
	}
	g_free (s);	

	main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window"));
	g_signal_connect (main_window, "delete-event", G_CALLBACK (delete_callback), ui);

	gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));

	progress = g_timeout_add (100, update_progress_bar,
				  gtk_builder_get_object (ui, "progressbar"));
	gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
				   _("Collecting information from your system..."));

	if (gopt_data.app_file == NULL && gopt_data.package == NULL) {
		buddy_error (NULL, _("Either --appname or --package arguments are required.\n"));	
		g_object_unref (program);
		return 0;
	}	

	if (gopt_data.app_file && gopt_data.pid == 0 && 
            gopt_data.include_file == NULL && 
            gopt_data.minidump_file == NULL) {
		buddy_error (NULL, _("Either --pid , --include or --minidump arguments are required.\n"));	
		g_object_unref (program);
		return 0;
	}	
	
	/* get some information about the gnome version */
	gnome_version = get_gnome_version_info ();
	if (gnome_version == NULL) {
		buddy_error (NULL, _("Bug Buddy was unable to retrieve information regarding "
		                     "the version of GNOME you are running.  This is most likely "
		                     "due to a missing installation of gnome-desktop.\n"));
		g_object_unref (program);
		return 0;
	}
	
	g_object_set_data (G_OBJECT (ui), "gnome-version", gnome_version);

	/* connect the signal handler for the help button */
	g_signal_connect (gtk_builder_get_object (ui, "help-button"), "clicked", 
	                  G_CALLBACK (help_callback), program);

	gtk_widget_show (main_window);

	apps = load_applications ();
	
	/* If we have a binary file it is a crash */
	if (gopt_data.app_file) {
		app = g_hash_table_lookup (apps, gopt_data.app_file);

		/* we handle an unknown application (no .desktop file) differently */
		if (app != NULL) {
			s = g_markup_printf_escaped (_("The %s application has crashed.  "
		                               		"We are collecting information about the crash to send to the "
                                               		"developers in order to fix the problem."), app->name);	
			gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s);
			g_free (s);
		
			g_object_set_data (G_OBJECT (ui), "app", app);
	
			if (app->icon) {
				GdkPixbuf *pixbuf;
				pixbuf = load_icon (app->icon);
				if (pixbuf) {
					gtk_image_set_from_pixbuf (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")),
			                           	   	   pixbuf);
					g_object_unref(pixbuf);
				}
			}
			fill_gnome_info (app, gnome_version, ui);
		}

		gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
					   _("Collecting information from the crash..."));

		fill_system_info (ui);
		
		fill_proccess_info (gopt_data.pid, ui);

		if (gopt_data.pid > 0) {
			/* again, if this is an unknown application, we connect a different callback that
	 		* will allow the user to save the trace rather than sending it to the GNOME Bugzilla */
			if (app == NULL) {
				source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui, 
		                           		   gdb_finished_unknown_app, &err);
			} else {
				source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui, gdb_finished, &err);
			}

			if (source_id == 0) {
				buddy_error (NULL, _("Bug Buddy encountered the following error when trying "
		                     		     "to retrieve debugging information: %s\n"), err->message);
				g_error_free (err);
				g_object_unref (program);
				return 0;
			}

			/* connect the close button callback so that we can remove the source from 
	 		* the main loop (and kill gdb) if the user wants to quit before gdb is finished */
			g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (source_id));
		} else {
			if (app == NULL) {
				unknown_app_finished (ui);
			} else {
				known_app_finished (ui);
			}
		}
		g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_CRASH));
	} else {
		/* No binary file, so this is a non-crashing bug. Look the application from the --package arg */
		GtkWidget *email_entry;
		char *default_email;
 		char *msg, *lang_note;
                const char *en_lang_note = N_("\n\nPlease write your report in English, if possible.");
		
		app = g_hash_table_find (apps, (GHRFunc)bugzilla_search_for_package, gopt_data.package);
		if (app == NULL) {
			/* Fallback to binary name */
			app = g_hash_table_lookup (apps, gopt_data.package);
		}
		if (app == NULL) {
			buddy_error (NULL, _("Bug Buddy doesn't know how to send a suggestion for the application %s.\n"),
		                     	     gopt_data.package);
			return 0;
		}
		
		g_object_set_data (G_OBJECT (ui), "app", app);
	
		if (app->icon) {
			GdkPixbuf *pixbuf;
			
			pixbuf = load_icon (app->icon);
			if (pixbuf) {
				gtk_image_set_from_pixbuf (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")),
							   pixbuf);
				g_object_unref (pixbuf);
			}
		}
		fill_gnome_info (app, gnome_version, ui);
		fill_custom_info (app, ui);
	
		gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
		gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
		gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));

		lang_note = gettext (en_lang_note);
		msg = g_strconcat (_("Thank you for helping us improving our software.\n"
				     "Please fill your suggestions/error information for %s application.\n\n"
				     "A valid email address is required.  This will allow developers to "
				     "contact you for more information if necessary."),
				   strcmp (lang_note, en_lang_note)? lang_note: NULL,
				   NULL);
		s = g_markup_printf_escaped (msg, app->name);
		gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s);
		g_free (s);
		g_free (msg);
		
		s = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>",
                                             _("Suggestion / Error description:"));
		gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "main-label")), s);
		g_free (s);
		
		show_pending_checkbox_if_pending (ui);
		g_signal_connect (gtk_builder_get_object (ui, "send-button"), "clicked", 
	                  	  G_CALLBACK (on_send_clicked), ui);

		email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"));
		g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui);

		default_email = get_default_user_email ();
		
		if (default_email != NULL) {
			gtk_entry_set_text (GTK_ENTRY (email_entry), default_email);
			g_free (default_email);
		}
		g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_REQUEST));
	}
	
	if (gopt_data.include_file != NULL) {
		fill_include_file (gopt_data.include_file, ui);
	}

	
	g_signal_connect (gtk_builder_get_object (ui, "close-button"), "clicked", 
	                  G_CALLBACK (close_callback), ui);
	
	gtk_main ();

	g_object_unref (program);

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1