/*
 *  Copyright © 2005, 2006 Christian Persch
 *
 *  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, 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.
 *
 *  $Id$
 */

#include <xpcom-config.h>
#include "config.h"

#include <glib.h>
#include <glib-object.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include <nsStringGlue.h>

#include <nsCOMPtr.h>
#include <nsIDOMWindow.h>
#include <nsServiceManagerUtils.h>

#include "gecko-embed.h"
#include "gecko-embed-single.h"

#include "ephy-embed-shell.h"

#include "AutoJSContextStack.h"
#include "AutoWindowModalState.h"
#include "GeckoUtils.h"

#include "GeckoPromptService.h"

#define TIMEOUT			1000 /* ms */
#define TIMEOUT_DATA_KEY	"timeout"

#define MAX_MESSAGE_LENGTH	512
#define MAX_TITLE_LENGTH	256
#define MAX_BUTTON_TEXT_LENGTH	128

enum
{
	RESPONSE_ABORT_SCRIPT = 42
};

class Prompter
{
public:
	Prompter (const char*, nsIDOMWindow*, const PRUnichar*, const PRUnichar*);
	~Prompter();

	void AddStockButton (const char*, int);
	void AddButtonWithFlags (PRInt32, PRUint32, const PRUnichar*, PRUint32);
	void AddButtonsWithFlags (PRUint32, const PRUnichar*, const PRUnichar*, const PRUnichar*);
	void AddCheckbox (const PRUnichar*, PRBool*);
	void GetCheckboxState (PRBool *);
	void AddEntry (const char *, const PRUnichar *, PRBool);
	void GetText (PRUint32, PRUnichar **);
	void AddSelect (PRUint32, const PRUnichar **, PRInt32);
	void GetSelected (PRInt32*);

	PRInt32 Run (PRBool * = nsnull);
	void Show ();

	PRBool IsCalledFromScript ();
	void PerformScriptAbortion ();

	char *ConvertAndTruncateString (const PRUnichar *, PRInt32 = -1);
	char* ConvertAndEscapeButtonText (const PRUnichar *, PRInt32 = -1);

private:
	nsCOMPtr<nsIDOMWindow> mWindow;
	GtkDialog *mDialog;
	GtkWidget *mVBox;
	GtkWidget *mCheck;
	GtkSizeGroup *mSizeGroup;
	GtkWidget *mEntries[2];
	GtkWidget *mCombo;
	PRInt32 mNumButtons;
	PRInt32 mNumEntries;
	PRInt32 mDefaultResponse;
	PRInt32 mUnaffirmativeResponse;
	PRInt32 mResponse;
	PRBool mSuccess;
	PRBool mDelay;
};

Prompter::Prompter (const char *aStock,
		    nsIDOMWindow *aParent,
		    const PRUnichar *aTitle,
		    const PRUnichar *aText)
	: mWindow (aParent)
	, mDialog(nsnull)
	, mVBox(nsnull)
	, mCheck(nsnull)
	, mSizeGroup(nsnull)
	, mCombo(nsnull)
	, mNumButtons(0)
	, mNumEntries(0)
	, mDefaultResponse(GTK_RESPONSE_ACCEPT)
	, mUnaffirmativeResponse(0)
	, mResponse(GTK_RESPONSE_CANCEL)
	, mSuccess(PR_FALSE)
	, mDelay(PR_FALSE)
{
	GtkWidget *parent, *hbox, *label, *image;

	g_object_ref (ephy_embed_shell_get_default ());

	mEntries[0] = mEntries[1] = nsnull;

	mDialog = GTK_DIALOG (gtk_dialog_new ());
	g_object_ref_sink (mDialog);

	char *title = NULL;
	if (aTitle)
	{
		title = ConvertAndTruncateString (aTitle, MAX_TITLE_LENGTH);
	}

	gtk_window_set_title (GTK_WINDOW (mDialog), title ? title : "");
	g_free (title);

	gtk_window_set_modal (GTK_WINDOW (mDialog), TRUE);

	parent = GeckoUtils::GetGtkWindowForDOMWindow (aParent);
	if (GTK_IS_WINDOW (parent))
	{
		gtk_window_set_transient_for (GTK_WINDOW (mDialog),
					      GTK_WINDOW (parent));
	}

	gtk_dialog_set_has_separator (mDialog, FALSE);
	gtk_window_set_resizable (GTK_WINDOW (mDialog), FALSE);
	gtk_container_set_border_width (GTK_CONTAINER (mDialog), 5);
	gtk_box_set_spacing (GTK_BOX (mDialog->vbox), 14); /* 2 * 5 + 14 = 24 */
	
	hbox = gtk_hbox_new (FALSE, 12);
	gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (mDialog)->vbox), hbox);

	image = gtk_image_new_from_stock (aStock, GTK_ICON_SIZE_DIALOG);
	gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
	gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);

	mVBox = gtk_vbox_new (FALSE, 12);
	gtk_box_pack_start (GTK_BOX (hbox), mVBox, TRUE, TRUE, 0);

	char *text = NULL;
	if (aText)
	{
		text = ConvertAndTruncateString (aText, MAX_MESSAGE_LENGTH);
	}

	label = gtk_label_new (text);
	g_free (text);

	gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
	/* Guard against overlong nonbreakable text (exploit) */
	gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR);
	gtk_label_set_selectable (GTK_LABEL (label), TRUE);
	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);

	gtk_box_pack_start (GTK_BOX (mVBox), label, FALSE, FALSE, 0);
	gtk_widget_show (label);

	if (IsCalledFromScript ())
	{
		gtk_dialog_add_button (GTK_DIALOG (mDialog),
				       _("_Abort Script"),
				       RESPONSE_ABORT_SCRIPT);
	}

	gtk_widget_show (image);
	gtk_widget_show (mVBox);
	gtk_widget_show (hbox);
}

Prompter::~Prompter ()
{
	if (mSizeGroup)
	{
		g_object_unref (mSizeGroup);
	}

	gtk_widget_destroy (GTK_WIDGET (mDialog));
	g_object_unref (mDialog);

	g_object_unref (ephy_embed_shell_get_default ());
}

void
Prompter::AddStockButton (const char *aStock,
			  int aResponse)
{
	gtk_dialog_add_button (GTK_DIALOG (mDialog),
			       aStock, aResponse);
	++mNumButtons;
}

void
Prompter::AddButtonWithFlags (PRInt32 aNum,
			      PRUint32 aFlags,
			      const PRUnichar *aText,
			      PRUint32 aDefault)
{
	if (aFlags == 0) return;

	const char *label = NULL;
	char *freeme = NULL;
	gboolean isAffirmative = FALSE;
	switch (aFlags)
	{
		case nsIPromptService::BUTTON_TITLE_OK:
			label = GTK_STOCK_OK;
			isAffirmative = TRUE;
			break;

		case nsIPromptService::BUTTON_TITLE_CANCEL:
			label = GTK_STOCK_CANCEL;
			break;

		case nsIPromptService::BUTTON_TITLE_YES:
			label = GTK_STOCK_YES;
			isAffirmative = TRUE;
			break;

		case nsIPromptService::BUTTON_TITLE_NO:
			label = GTK_STOCK_NO;
			break;

		case nsIPromptService::BUTTON_TITLE_SAVE:
			label = GTK_STOCK_SAVE;
			isAffirmative = TRUE;
			break;

		case nsIPromptService::BUTTON_TITLE_DONT_SAVE:
			label = _("Don't Save");
			break;

		case nsIPromptService::BUTTON_TITLE_REVERT:
			label = GTK_STOCK_REVERT_TO_SAVED;
			break;

		case nsIPromptService::BUTTON_TITLE_IS_STRING:
		default:
			label = freeme = ConvertAndEscapeButtonText (aText, MAX_BUTTON_TEXT_LENGTH);
			/* We can't tell, so assume it's affirmative */
			isAffirmative = TRUE;
			break;
	}

	if (label == NULL) return;

	gtk_dialog_add_button (mDialog, label, aNum);
	++mNumButtons;

	if (isAffirmative && mDelay)
	{
		gtk_dialog_set_response_sensitive (mDialog, aNum, FALSE);
	}

	if (!isAffirmative)
	{
		mUnaffirmativeResponse = aNum;
	}

	if (aDefault)
	{
		mDefaultResponse = aNum;
	}

	g_free (freeme);
}

void
Prompter::AddButtonsWithFlags (PRUint32 aFlags,
			       const PRUnichar *aText0,
			       const PRUnichar *aText1,
			       const PRUnichar *aText2)
{
	mDelay = (aFlags & nsIPromptService::BUTTON_DELAY_ENABLE) != 0;
	mDefaultResponse = -1;

	/* Reverse the order, on the assumption that what we passed is the
	 * 'windows' button order, and we want HIG order.
	 */
	AddButtonWithFlags (2, ((aFlags / nsIPromptService::BUTTON_POS_2) & 0xff), aText2,
			    aFlags & nsIPromptService::BUTTON_POS_2_DEFAULT);
	AddButtonWithFlags (1, ((aFlags / nsIPromptService::BUTTON_POS_1) & 0xff), aText1,
			    aFlags & nsIPromptService::BUTTON_POS_1_DEFAULT);
	AddButtonWithFlags (0, ((aFlags / nsIPromptService::BUTTON_POS_0) & 0xff), aText0,
			    aFlags & nsIPromptService::BUTTON_POS_0_DEFAULT);

	/* If no default was set, use the 'rightmost' unaffirmative response.
	 * This happens with the suite's password manager prompt.
	 */
	if (mDefaultResponse == -1)
	{
		mDefaultResponse = mUnaffirmativeResponse;
	}
}

void
Prompter::AddCheckbox (const PRUnichar *aText,
		       PRBool *aState)
{
	if (!aState || !aText) return;

	char *label = ConvertAndEscapeButtonText (aText, 2 * MAX_BUTTON_TEXT_LENGTH);
	mCheck = gtk_check_button_new_with_mnemonic (label);
	g_free (label);

	gtk_label_set_line_wrap (GTK_LABEL (GTK_BIN (mCheck)->child), TRUE);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (mCheck), *aState);
	gtk_box_pack_start (GTK_BOX (mVBox), mCheck, FALSE, FALSE, 0);
	gtk_widget_show (mCheck);
}

void
Prompter::GetCheckboxState (PRBool *aState)
{
	if (!aState || !mCheck) return;
		
	*aState = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (mCheck));
}

void
Prompter::AddEntry (const char *aLabel,
		    const PRUnichar *aValue,
		    PRBool aIsPassword)
{
	if (!mSizeGroup)
	{
		mSizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
	}

	GtkWidget *hbox = gtk_hbox_new (FALSE, 12);
	gtk_box_pack_start (GTK_BOX (mVBox), hbox, FALSE, FALSE, 0);

	GtkWidget *label = nsnull;
	if (aLabel)
	{
		label = gtk_label_new_with_mnemonic (aLabel);
		gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
		gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
		gtk_size_group_add_widget (mSizeGroup, label);
	}
       
	GtkWidget *entry = mEntries[mNumEntries++] = gtk_entry_new ();
	gtk_entry_set_visibility (GTK_ENTRY (entry), !aIsPassword);
	gtk_entry_set_activates_default(GTK_ENTRY (entry), TRUE);

	if (aValue)
	{
		nsCString cValue;
		NS_UTF16ToCString (nsDependentString(aValue),
				   NS_CSTRING_ENCODING_UTF8, cValue);

		gtk_entry_set_text (GTK_ENTRY (entry), cValue.get());
	}

	if (label)
	{
		gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
	}

	gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
	gtk_widget_show_all (hbox);
}

void
Prompter::GetText (PRUint32 aNum,
		   PRUnichar **aValue)
{
	if (!aValue || !mEntries[aNum]) return;

	const char *text = gtk_entry_get_text (GTK_ENTRY (mEntries[aNum]));
	if (!text) return;

	nsString value;
	NS_CStringToUTF16 (nsDependentCString (text),
			   NS_CSTRING_ENCODING_UTF8, value);

	*aValue = NS_StringCloneData (value);
}

void
Prompter::AddSelect (PRUint32 aCount,
		     const PRUnichar **aList,
		     PRInt32 aDefault)
{
	mCombo = gtk_combo_box_new_text ();

	for (PRUint32 i = 0; i < aCount; i++)
	{
		/* FIXME: use "" instead in this case? */
		if (!aList[i] || !aList[i][0]) continue;

		nsCString cData;
		NS_UTF16ToCString (nsDependentString(aList[i]), NS_CSTRING_ENCODING_UTF8, cData);

		gtk_combo_box_append_text (GTK_COMBO_BOX (mCombo), cData.get());
	}

	gtk_combo_box_set_active (GTK_COMBO_BOX (mCombo), aDefault);

	gtk_box_pack_start (GTK_BOX (mVBox), mCombo, FALSE, FALSE, 0);
	gtk_widget_show (mCombo);
}

void
Prompter::GetSelected (PRInt32 *aSelected)
{
	if (!aSelected || !mCombo) return;

	*aSelected = gtk_combo_box_get_active (GTK_COMBO_BOX (mCombo));
}

static gboolean
EnableResponse (GtkDialog *aDialog)
{
	g_object_steal_data (G_OBJECT (aDialog), TIMEOUT_DATA_KEY);

	gtk_dialog_set_response_sensitive (aDialog, 0, TRUE);
	gtk_dialog_set_response_sensitive (aDialog, 1, TRUE);
	gtk_dialog_set_response_sensitive (aDialog, 2, TRUE);

	return FALSE;
}

static void
RemoveTimeout (gpointer idptr)
{
	guint timeout = GPOINTER_TO_UINT (idptr);

	g_return_if_fail (timeout != 0);

	g_source_remove (timeout);
}

PRInt32
Prompter::Run (PRBool *aSuccess)
{
#if 0
	AutoEventQueue queue;
	if (NS_FAILED (queue.Init()))
	{
		if (aSuccess)
		{
			*aSuccess = PR_FALSE;
		}
		mSuccess = PR_FALSE;

		return GTK_RESPONSE_CANCEL;
	}
#endif

	nsresult rv;
	AutoJSContextStack stack;
	rv = stack.Init ();
	if (NS_FAILED (rv)) return rv;

	AutoWindowModalState modalState (mWindow);

	if (mDelay)
	{
		guint timeout = g_timeout_add (TIMEOUT,
					       (GSourceFunc) EnableResponse,
					       mDialog);
		g_object_set_data_full (G_OBJECT (mDialog), TIMEOUT_DATA_KEY,
					GUINT_TO_POINTER (timeout),
					(GDestroyNotify) RemoveTimeout);
	}

	gtk_dialog_set_default_response (GTK_DIALOG (mDialog), mDefaultResponse);

	GtkWidget *widget = GTK_WIDGET (mDialog);
	gtk_widget_show (widget);
	mResponse = gtk_dialog_run (mDialog);
	gtk_widget_hide (widget);

	g_object_set_data (G_OBJECT (mDialog), TIMEOUT_DATA_KEY, NULL);

	mSuccess = (GTK_RESPONSE_ACCEPT == mResponse);
	if (aSuccess)
	{
		*aSuccess = mSuccess;
	}

	if (mResponse == RESPONSE_ABORT_SCRIPT)
	{
		PerformScriptAbortion ();
	}

	return mResponse;
}

static void
DeletePrompter (gpointer aPromptPtr,
	        GObject *aZombie)
{
	Prompter *prompt = static_cast<Prompter*>(aPromptPtr);

	delete prompt;
}

void
Prompter::Show ()
{
	/* We don't need it anymore */
	mWindow = nsnull;

	gtk_window_set_modal (GTK_WINDOW (mDialog), FALSE);

	g_signal_connect (mDialog, "response",
			  G_CALLBACK (gtk_widget_destroy), NULL);
	g_object_weak_ref (G_OBJECT (mDialog),
			   (GWeakNotify) DeletePrompter,
			   static_cast<gpointer>(this));

	gtk_widget_show (GTK_WIDGET (mDialog));
}

PRBool
Prompter::IsCalledFromScript()
{
#if 0
	nsCOMPtr<nsIXPConnect> xpconnect (do_GetService (nsIXPConnect::GetCID()));
	NS_ENSURE_TRUE (xpconnect, PR_FALSE);

	nsresult rv;
	nsCOMPtr<nsIXPCNativeCallContext> ncc;
	rv = xpconnect->GetCurrentNativeCallContext (getter_AddRefs (ncc));
	NS_ENSURE_SUCCESS (rv, PR_FALSE);

	if (!ncc) return PR_FALSE;

	JSContext *cx = nsnull;
	rv = ncc->GetJSContext (&cx);
	g_print ("GetJSContext rv=%x, cx=%p\n", rv, cx);

	NS_ENSURE_SUCCESS (rv, PR_FALSE);

	return cx != nsnull;
#endif
	return PR_FALSE;
}

void
Prompter::PerformScriptAbortion()
{
#if 0
	/* FIXME: can we only stop the calling script, not all scripts in the context? */

	nsCOMPtr<nsIXPConnect> xpconnect (do_GetService (nsIXPConnect::GetCID()));
	NS_ENSURE_TRUE (xpconnect, );

	nsresult rv;
	nsCOMPtr<nsIXPCNativeCallContext> ncc;
	rv = xpconnect->GetCurrentNativeCallContext (getter_AddRefs (ncc));
	NS_ENSURE_SUCCESS (rv, );
	NS_ENSURE_TRUE (ncc, );

	JSContext *cx = nsnull;
	rv = ncc->GetJSContext (&cx);
	g_print ("GetJSContext rv=%x, cx=%p\n", rv, cx);
	NS_ENSURE_SUCCESS (rv, );
	NS_ENSURE_TRUE (cx, );

	g_print ("Would now disable scripts\n");
//	MozillaPrivate::SetScriptsEnabled (cx, PR_FALSE, PR_FALSE);
#endif
}

char *
Prompter::ConvertAndTruncateString (const PRUnichar *aText,
				    PRInt32 aMaxLength)
{
	if (aText == nsnull) return NULL;

	/* This depends on the assumption that 
	 * typeof(PRUnichar) == typeof (gunichar2) == uint16,
	 * which should be pretty safe.
	 */
	glong n_read = 0, n_written = 0;
	char *converted = g_utf16_to_utf8 ((gunichar2*) aText, aMaxLength,
					    &n_read, &n_written, NULL);
	/* FIXME loop from the end while !g_unichar_isspace (char)? */

	return converted;
}

char *
Prompter::ConvertAndEscapeButtonText(const PRUnichar *aText,
			   	     PRInt32 aMaxLength)
{
	char *converted = ConvertAndTruncateString (aText, aMaxLength);
	if (converted == NULL) return NULL;

	char *escaped = (char*) g_malloc (strlen (converted) + 1);
	char *q = escaped;
	for (const char *p = converted; *p; ++p, ++q)
	{
		if (*p == '&')
		{
			if (*(p+1) == '&')
			{
				*q = '&';
				++p;
			}
			else
			{
				*q = '_';
			}
		}
		else
		{
			*q = *p;
		}
	}

	/* Null termination */
	*q = '\0';

	g_free (converted);

	return escaped;
}

NS_IMPL_ISUPPORTS2 (GeckoPromptService,
		    nsIPromptService,
		    nsINonBlockingAlertService)

GeckoPromptService::GeckoPromptService()
{
}

GeckoPromptService::~GeckoPromptService()
{
}

/* nsIPromptService implementation */

/* void alert (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText); */
NS_IMETHODIMP
GeckoPromptService::Alert (nsIDOMWindow *aParent,
			  const PRUnichar *aDialogTitle,
			  const PRUnichar *aText)
{
	Prompter prompt (GTK_STOCK_DIALOG_INFO, aParent, aDialogTitle, aText);
	prompt.AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt.Run ();

	return NS_OK;
}

/* void alertCheck (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText, in wstring aCheckMsg, inout boolean aCheckState); */
NS_IMETHODIMP
GeckoPromptService::AlertCheck (nsIDOMWindow *aParent,
			       const PRUnichar *aDialogTitle,
			       const PRUnichar *aText,
			       const PRUnichar *aCheckMsg,
			       PRBool *aCheckState)
{
	Prompter prompt (GTK_STOCK_DIALOG_INFO, aParent, aDialogTitle, aText);
	prompt.AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt.AddCheckbox (aCheckMsg, aCheckState);

	prompt.Run ();
	prompt.GetCheckboxState (aCheckState);

	return NS_OK;
}

/* boolean confirm (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText); */
NS_IMETHODIMP
GeckoPromptService::Confirm (nsIDOMWindow *aParent,
			    const PRUnichar *aDialogTitle,
			    const PRUnichar *aText,
			    PRBool *_retval)
{
	NS_ENSURE_ARG_POINTER (_retval);

	Prompter prompt (GTK_STOCK_DIALOG_QUESTION, aParent, aDialogTitle, aText);
	prompt.AddStockButton (GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	prompt.AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt.Run (_retval);

	return NS_OK;
}

/* boolean confirmCheck (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText, in wstring aCheckMsg, inout boolean aCheckState); */
NS_IMETHODIMP
GeckoPromptService::ConfirmCheck (nsIDOMWindow *aParent,
				 const PRUnichar *aDialogTitle,
				 const PRUnichar *aText,
				 const PRUnichar *aCheckMsg,
				 PRBool *aCheckState,
				 PRBool *_retval)
{
	NS_ENSURE_ARG_POINTER (_retval);

	Prompter prompt (GTK_STOCK_DIALOG_QUESTION, aParent, aDialogTitle, aText);
	prompt.AddStockButton (GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	prompt.AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt.AddCheckbox (aCheckMsg, aCheckState);

	prompt.Run (_retval);
	prompt.GetCheckboxState (aCheckState);

	return NS_OK;
}

/* PRInt32 confirmEx (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText, in unsigned long aButtonFlags, in wstring aButton0Title, in wstring aButton1Title, in wstring aButton2Title, in wstring aCheckMsg, inout boolean aCheckState); */
NS_IMETHODIMP
GeckoPromptService::ConfirmEx (nsIDOMWindow *aParent,
			      const PRUnichar *aDialogTitle,
			      const PRUnichar *aText,
			      PRUint32 aButtonFlags,
			      const PRUnichar *aButton0Title,
			      const PRUnichar *aButton1Title,
			      const PRUnichar *aButton2Title,
			      const PRUnichar *aCheckMsg,
			      PRBool *aCheckState,
			      PRInt32 *_retval)
{
	NS_ENSURE_ARG_POINTER (_retval);

	Prompter prompt (GTK_STOCK_DIALOG_QUESTION, aParent, aDialogTitle, aText);
	prompt.AddButtonsWithFlags (aButtonFlags, aButton0Title,
				    aButton1Title, aButton2Title);
	prompt.AddCheckbox (aCheckMsg, aCheckState);

	*_retval = prompt.Run (nsnull);
	prompt.GetCheckboxState (aCheckState);

	return NS_OK;
}

/* boolean prompt (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText, inout wstring aValue, in wstring aCheckMsg, inout boolean aCheckState); */
NS_IMETHODIMP
GeckoPromptService::Prompt (nsIDOMWindow *aParent,
			   const PRUnichar *aDialogTitle,
			   const PRUnichar *aText,
			   PRUnichar **aValue,
			   const PRUnichar *aCheckMsg,
			   PRBool *aCheckState,
			   PRBool *_retval)
{
	NS_ENSURE_ARG_POINTER (_retval);
	NS_ENSURE_ARG_POINTER (aValue);

	Prompter prompt (GTK_STOCK_DIALOG_QUESTION, aParent, aDialogTitle, aText);
	prompt.AddStockButton (GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	prompt.AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt.AddEntry (nsnull, *aValue, PR_FALSE);
	prompt.AddCheckbox (aCheckMsg, aCheckState);

	prompt.Run (_retval);
	prompt.GetText (0, aValue);
	prompt.GetCheckboxState (aCheckState);

	return NS_OK;
}

/* boolean promptUsernameAndPassword (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText, inout wstring aUsername, inout wstring aPassword, in wstring aCheckMsg, inout boolean aCheckState); */
NS_IMETHODIMP
GeckoPromptService::PromptUsernameAndPassword (nsIDOMWindow *aParent,
					      const PRUnichar *aDialogTitle,
					      const PRUnichar *aText,
					      PRUnichar **aUsername,
					      PRUnichar **aPassword,
					      const PRUnichar *aCheckMsg,
					      PRBool *aCheckState,
					      PRBool *_retval)
{
	NS_ENSURE_ARG_POINTER (_retval);
	NS_ENSURE_ARG_POINTER (aUsername);
	NS_ENSURE_ARG_POINTER (aPassword);

	Prompter prompt (GTK_STOCK_DIALOG_AUTHENTICATION, aParent, aDialogTitle, aText);
	prompt.AddStockButton (GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	prompt.AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt.AddEntry (_("_Username:"), *aUsername, PR_FALSE);
	prompt.AddEntry (_("_Password:"), *aPassword, PR_TRUE);
	prompt.AddCheckbox (aCheckMsg, aCheckState);

	prompt.Run (_retval);
	prompt.GetText (0, aUsername);
	prompt.GetText (1, aPassword);
	prompt.GetCheckboxState (aCheckState);

	return NS_OK;
}

/* boolean promptPassword (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText, inout wstring aPassword, in wstring aCheckMsg, inout boolean aCheckState); */
NS_IMETHODIMP
GeckoPromptService::PromptPassword (nsIDOMWindow *aParent,
				   const PRUnichar *aDialogTitle,
				   const PRUnichar *aText,
				   PRUnichar **aPassword,
				   const PRUnichar *aCheckMsg,
				   PRBool *aCheckState,
				   PRBool *_retval)
{
	NS_ENSURE_ARG_POINTER (_retval);
	NS_ENSURE_ARG_POINTER (aPassword);

	Prompter prompt (GTK_STOCK_DIALOG_AUTHENTICATION, aParent, aDialogTitle, aText);
	prompt.AddStockButton (GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	prompt.AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt.AddEntry (_("_Password:"), *aPassword, PR_TRUE);
	prompt.AddCheckbox (aCheckMsg, aCheckState);

	// FIXME: Add a CAPSLOCK indicator?

	prompt.Run (_retval);
	prompt.GetText (0, aPassword);
	prompt.GetCheckboxState (aCheckState);

	return NS_OK;
}

/* boolean select (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText, in PRUint32 aCount, [array, size_is (aCount)] in wstring aSelectList, out long aOutSelection); */
NS_IMETHODIMP
GeckoPromptService::Select (nsIDOMWindow *aParent,
			   const PRUnichar *aDialogTitle,
			   const PRUnichar *aText,
			   PRUint32 aCount,
			   const PRUnichar **aSelectList,
			   PRInt32 *aOutSelection,
			   PRBool *_retval)
{
	NS_ENSURE_ARG_POINTER (_retval);
	NS_ENSURE_ARG_POINTER (aOutSelection);

	Prompter prompt (GTK_STOCK_DIALOG_QUESTION, aParent, aDialogTitle, aText);
	prompt.AddStockButton (GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	prompt.AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt.AddSelect (aCount, aSelectList, *aOutSelection);

	prompt.Run (_retval);
	prompt.GetSelected (aOutSelection);

	return NS_OK;
}

/* nsIPromptService2 */

/* boolean promptAuth (in nsIDOMWindow aParent, in nsIChannel aChannel, in PRUint32 level, in nsIAuthInformation authInfo, in wstring checkboxLabel, inout boolean checkValue); */
NS_IMETHODIMP
GeckoPromptService::PromptAuth (nsIDOMWindow *aParent,
                                nsIChannel *aChannel,
                                PRUint32 level,
                                nsIAuthInformation *authInfo,
                                const PRUnichar *checkboxLabel,
                                PRBool *checkValue,
                                PRBool *_retval)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

/* nsICancelable asyncPromptAuth (in nsIDOMWindow aParent, in nsIChannel aChannel, in nsIAuthPromptCallback aCallback, in nsISupports aContext, in PRUint32 level, in nsIAuthInformation authInfo, in wstring checkboxLabel, inout boolean checkValue); */
NS_IMETHODIMP
GeckoPromptService::AsyncPromptAuth (nsIDOMWindow *aParent,
                                     nsIChannel *aChannel,
                                     nsIAuthPromptCallback *aCallback,
                                     nsISupports *aContext,
                                     PRUint32 level,
                                     nsIAuthInformation *authInfo,
                                     const PRUnichar *checkboxLabel,
                                     PRBool *checkValue,
                                     nsICancelable **_retval)
{
  /* FIXME implement me */
  return NS_ERROR_NOT_IMPLEMENTED;
}

/* nsINonBlockingAlertService */

/* showNonBlockingAlert (in nsIDOMWindow aParent, in wstring aDialogTitle, in wstring aText); */
NS_IMETHODIMP
GeckoPromptService::ShowNonBlockingAlert (nsIDOMWindow *aParent,
					 const PRUnichar *aDialogTitle,
					 const PRUnichar *aText)
{
	Prompter *prompt = new Prompter (GTK_STOCK_DIALOG_INFO, aParent, aDialogTitle, aText);
	if (!prompt) return NS_ERROR_OUT_OF_MEMORY;

	prompt->AddStockButton (GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
	prompt->Show ();

	return NS_OK;
}