/*
 *  Copyright (C) 2001 Philip Langdale
 *
 *  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 2, 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* Things to be aware of:
 *
 * This filepicker, like the mozilla one, does not make an attempt
 * to verify the validity of the initial directory you pass it.
 * It does check that the user doesn't give it a garbage path
 * during use, but it is the caller's responsibility to give a
 * sensible initial path.
 *
 * At the current moment, we instantiate the filepicker directly
 * in our contenthandler where there is path verification code
 * and else where through our C wrapper, which also does verification.
 * If, at a future date, you need to instantiate filepicker without
 * using the C wrapper, please verify the initial path. See
 * ContentHandler for a way to do this.
 */

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

#include "ephy-string.h"
#include "ephy-gui.h"

#include <glib/gconvert.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtkmenuitem.h>
#include <gtk/gtkcheckbutton.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtkfilesel.h>
#include <gtk/gtkhbbox.h>
#include <gtk/gtkoptionmenu.h>
#include <gtk/gtkmessagedialog.h>
#include <libgnome/gnome-i18n.h>

#include "nsIFilePicker.h"

#include "nsCRT.h"
#include "nsCOMPtr.h"
#include "nsISupportsArray.h"
#include "nsIServiceManager.h"

#include "nsString.h"
#include "nsXPIDLString.h"
#include "nsIPrefService.h"
#include "nsIURI.h"
#include "nsIFileURL.h"
#include "nsIChannel.h"
#include "nsIFileChannel.h"
#include "nsNetCID.h"
#include "nsILocalFile.h"
#include "nsIPromptService.h"
#include "nsReadableUtils.h"

#include <libgnome/gnome-util.h>

#include "FilePicker.h"
#include "MozillaPrivate.h"

/* Implementation file */
NS_IMPL_ISUPPORTS1(GFilePicker, nsIFilePicker)

GFilePicker::GFilePicker(FileFormat *aFileFormats) :
			mFileFormats(aFileFormats)
{
	NS_INIT_ISUPPORTS();

	/* member initializers and constructor code */
	mFile = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID);
	mDisplayDirectory = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID);
	mDisplayDirectory->InitWithNativePath(nsDependentCString(g_get_home_dir()));
}

GFilePicker::~GFilePicker()
{
	/* destructor code */
}

////////////////////////////////////////////////////////////////////////////////
// begin nsIFilePicker impl
////////////////////////////////////////////////////////////////////////////////

/* void init (in nsIDOMWindowInternal parent, in wstring title, in short mode); */
NS_IMETHODIMP GFilePicker::Init(nsIDOMWindowInternal *aParent, 
				const PRUnichar *aTitle, PRInt16 aMode)
{
	mParent = do_QueryInterface(aParent);
	mParentWidget = MozillaFindGtkParent(mParent);
	mTitle = NS_ConvertUCS2toUTF8(aTitle);
	mMode = aMode;
	
	return NS_OK;
}

/* void appendFilters (in long filterMask); */
NS_IMETHODIMP GFilePicker::AppendFilters(PRInt32 aFilterMask)
{
	//This function cannot be implemented due to the crippled
	//nature of GtkFileSelection, but NS_ERROR_NOT_IMPLEMENTED
	//is interpreted as a terminal error by some callers.
	return NS_OK;
}

/* void appendFilter (in wstring title, in wstring filter); */
NS_IMETHODIMP GFilePicker::AppendFilter(const PRUnichar *aTitle,
					const PRUnichar *aFilter)
{
	//GtkFileSelection is crippled, so we can't provide a short-list
	//of filters to choose from. We provide minimal functionality
	//by using the most recent AppendFilter call as the active filter.
	mFilter = NS_ConvertUCS2toUTF8(aFilter);
	return NS_OK;
}

/* attribute long filterIndex; */
NS_IMETHODIMP GFilePicker::GetFilterIndex(PRInt32 *aFilterIndex)
{
	return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP GFilePicker::SetFilterIndex(PRInt32 aFilterIndex)
{
	return NS_OK;
}

/* attribute wstring defaultString; */
NS_IMETHODIMP GFilePicker::GetDefaultString(PRUnichar * *aDefaultString)
{
	gsize bytesWritten;
	gchar *utf8DefaultString = g_filename_to_utf8(mDefaultString.get(), -1,
						      NULL,
						      &bytesWritten, NULL);

	*aDefaultString = ToNewUnicode(NS_ConvertUTF8toUCS2(utf8DefaultString));
	g_free(utf8DefaultString);

	return NS_OK;
}
NS_IMETHODIMP GFilePicker::SetDefaultString(const PRUnichar *aDefaultString)
{
	if (aDefaultString)
	{
		gsize bytesWritten;
		gchar *localeDefaultString =
			g_filename_from_utf8(NS_ConvertUCS2toUTF8(aDefaultString).get(),
					     -1, NULL,
					     &bytesWritten, NULL);
		mDefaultString = localeDefaultString;					     
		g_free(localeDefaultString);
	}
	else
		mDefaultString = "";
	return NS_OK;
}

/* attribute wstring defaultExtension; */
// Again, due to the crippled file selector, we can't really
// do anything here.
NS_IMETHODIMP GFilePicker::GetDefaultExtension(PRUnichar * *aDefaultExtension)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP GFilePicker::SetDefaultExtension(const PRUnichar *aDefaultExtension)
{
    return NS_OK;
}

/* attribute nsILocalFile displayDirectory; */
NS_IMETHODIMP GFilePicker::GetDisplayDirectory(nsILocalFile * *aDisplayDirectory)
{
	NS_IF_ADDREF(*aDisplayDirectory = mDisplayDirectory);
	return NS_OK;
}
NS_IMETHODIMP GFilePicker::SetDisplayDirectory(nsILocalFile * aDisplayDirectory)
{
	mDisplayDirectory = aDisplayDirectory;
	return NS_OK;
}

/* readonly attribute nsILocalFile file; */
NS_IMETHODIMP GFilePicker::GetFile(nsILocalFile * *aFile)
{
	NS_IF_ADDREF(*aFile = mFile);
	return NS_OK;
}

/* readonly attribute nsIFileURL fileURL; */
NS_IMETHODIMP GFilePicker::GetFileURL(nsIFileURL * *aFileURL)
{
	nsCOMPtr<nsIFileURL> fileURL = 
		do_CreateInstance(NS_STANDARDURL_CONTRACTID);
	fileURL->SetFile(mFile);
	NS_IF_ADDREF(*aFileURL = fileURL);
	return NS_OK;
}

/* readonly attribute nsISimpleEnumerator files; */
NS_IMETHODIMP GFilePicker::GetFiles(nsISimpleEnumerator * *aFiles)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* short show (); */
NS_IMETHODIMP GFilePicker::Show(PRInt16 *_retval)
{
	mFileSelector = gtk_file_selection_new(mTitle.get());

	nsCAutoString cFileName;
	if(mMode == nsIFilePicker::modeGetFolder)
		cFileName.Assign("");
	else
		cFileName = mDefaultString;

	nsCAutoString cDirName;
	mDisplayDirectory->GetNativePath(cDirName);

	nsCAutoString cFullPath;
	cFullPath.Assign(cDirName + NS_LITERAL_CSTRING("/") + cFileName);
	gtk_file_selection_set_filename(GTK_FILE_SELECTION(mFileSelector),
				 	cFullPath.get());

	if (!mFilter.IsEmpty())
	{
		gtk_file_selection_complete(GTK_FILE_SELECTION(mFileSelector),
					    mFilter.get());
	}

	if (mParentWidget)
		gtk_window_set_transient_for(GTK_WINDOW(mFileSelector),
					     GTK_WINDOW(mParentWidget));

	if (mFileFormats)
	{
		mFormatChooser = gtk_option_menu_new();
		GtkMenu *options = GTK_MENU(gtk_menu_new());

		FileFormat *current = mFileFormats;
		while (current->description != NULL)
		{
			/* FIXME: the label should include the extensions too */
			gchar *label = current->description;
			GtkWidget *item = gtk_menu_item_new_with_label(label);
			gtk_widget_show(item);
			gtk_menu_shell_append(GTK_MENU_SHELL(options), item);
			current++;
		}
		gtk_option_menu_set_menu(GTK_OPTION_MENU(mFormatChooser),
					 GTK_WIDGET(options));
		gtk_widget_show(mFormatChooser);
		gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION (mFileSelector)->action_area),
				   mFormatChooser,
				   FALSE, TRUE, 0);
	}
	else
	{
		mFormatChooser = NULL;
	}

	if (mMode == nsIFilePicker::modeGetFolder)
	{
		gtk_widget_set_sensitive(GTK_FILE_SELECTION(mFileSelector)
					 ->file_list, FALSE);
	}

	gtk_window_set_modal(GTK_WINDOW(mFileSelector), TRUE);

	gint retVal = gtk_dialog_run(GTK_DIALOG(mFileSelector));
	
	HandleFilePickerResult();

	if (retVal != GTK_RESPONSE_OK)
	{
		*_retval = returnCancel;
	}
	else
	{	
		ValidateFilePickerResult(_retval);
	}

	gtk_widget_destroy(mFileSelector);

	return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////
// begin local public methods impl
////////////////////////////////////////////////////////////////////////////////

NS_METHOD GFilePicker::InitWithGtkWidget (GtkWidget *aParentWidget, 
					  const char *aTitle, PRInt16 aMode)
{
	mParentWidget = aParentWidget;

	mTitle = nsDependentCString(aTitle);

	mMode = aMode;

	mFile = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID);

	return NS_OK;
}

NS_METHOD GFilePicker::SanityCheck (PRBool *retIsSane)
{
	*retIsSane = PR_TRUE;
	
	nsresult rv;
	PRBool dirExists, fileExists = PR_TRUE;

	if (mDisplayDirectory)
	{
		rv = mDisplayDirectory->Exists (&dirExists);
		g_return_val_if_fail (NS_SUCCEEDED(rv), rv);
	}
	else
	{
		dirExists = PR_FALSE;
	}

	if (mMode != nsIFilePicker::modeGetFolder)
	{
		rv = mFile->Exists (&fileExists);
		g_return_val_if_fail (NS_SUCCEEDED(rv), rv);
	}

	if (mMode == nsIFilePicker::modeSave && !fileExists)
	{
		return NS_OK;
	}
	
	if (!dirExists || !fileExists)
	{
		GtkWidget *errorDialog = gtk_message_dialog_new (
				NULL,
				GTK_DIALOG_MODAL,
				GTK_MESSAGE_ERROR,
				GTK_BUTTONS_OK,
				_("The specified path does not exist."));

		if (mParentWidget)
			gtk_window_set_transient_for(GTK_WINDOW(errorDialog),
						     GTK_WINDOW(mFileSelector));

		gtk_window_set_modal (GTK_WINDOW(errorDialog), TRUE);
		gtk_dialog_run (GTK_DIALOG(errorDialog));
		gtk_widget_destroy (errorDialog);
		*retIsSane = PR_FALSE;
		return NS_OK;
	}

	PRBool correctType;
	char *errorText;
	if (mMode == nsIFilePicker::modeGetFolder)
	{
		rv = mDisplayDirectory->IsDirectory (&correctType);
		g_return_val_if_fail (NS_SUCCEEDED(rv), rv);
		errorText = g_strdup (_("A file was selected when a "
					"folder was expected."));
	}
	else
	{
		rv = mFile->IsFile (&correctType);
		g_return_val_if_fail (NS_SUCCEEDED(rv), rv);
		errorText = g_strdup (_("A folder was selected when a "
				        "file was expected."));
	}
	
	if(!correctType)
	{
		GtkWidget *errorDialog = gtk_message_dialog_new (
				NULL,
				GTK_DIALOG_MODAL,
				GTK_MESSAGE_ERROR,
				GTK_BUTTONS_OK,
				errorText);

		if (mParentWidget)
			gtk_window_set_transient_for(GTK_WINDOW(errorDialog),
						     GTK_WINDOW(mFileSelector));

		gtk_window_set_modal (GTK_WINDOW(errorDialog), TRUE);
		gtk_dialog_run (GTK_DIALOG(errorDialog));
		gtk_widget_destroy (errorDialog);
		*retIsSane = PR_FALSE;
	}
	g_free (errorText);

	return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////
// begin local private methods impl
////////////////////////////////////////////////////////////////////////////////

NS_METHOD GFilePicker::HandleFilePickerResult()
{
	const char *fileName = gtk_file_selection_get_filename(GTK_FILE_SELECTION(mFileSelector));

	if (!fileName || strlen(fileName) == 0) return NS_ERROR_FAILURE;

	const nsACString &cFileName = nsDependentCString(fileName);
	mFile->InitWithNativePath(cFileName);

	if (mMode == nsIFilePicker::modeGetFolder)
	{
		mDisplayDirectory->InitWithNativePath(cFileName);
		mDefaultString = "";
	}
	else
	{
		nsCOMPtr<nsIFile> directory;
		mFile->GetParent(getter_AddRefs(directory));
		mDisplayDirectory = do_QueryInterface(directory);
		mFile->GetNativeLeafName(mDefaultString);
	}

	if (mFormatChooser)
	{
		GtkWidget *menu = gtk_option_menu_get_menu 
			(GTK_OPTION_MENU(mFormatChooser));
		GtkWidget *selected = gtk_menu_get_active (GTK_MENU(menu));

		gint i(0);
		for (GList *iterator = GTK_MENU_SHELL(menu)->children ;
		     iterator ; iterator = iterator->next, i++)
		{
			if (iterator->data == selected) 
			{
				mSelectedFileFormat = i;
				break;
			}
		}
	}

	return NS_OK;
}

NS_METHOD GFilePicker::ValidateFilePickerResult(PRInt16 *retval)
{
	nsresult rv;
	const char *fileName = gtk_file_selection_get_filename(GTK_FILE_SELECTION(mFileSelector));

	*retval = returnCancel;

	PRBool passesSanityCheck;
	rv = SanityCheck(&passesSanityCheck);
	if (NS_SUCCEEDED(rv) && !passesSanityCheck) return NS_ERROR_FAILURE;

	if (mMode == nsIFilePicker::modeSave)
	{
		if (!ephy_gui_confirm_overwrite_file (mFileSelector,
					              fileName))
		{
			return NS_OK;
		}
	}

	*retval = returnOK;

	return NS_OK;
}