/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* e-summary-url.c
 *
 * Authors: Iain Holmes <iain@ximian.com>
 *
 * Copyright (C) 2000  Ximian, Inc.
 *
 * 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.
 */

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

#include <bonobo/bonobo-property-control.h>
#include <bonobo/bonobo-event-source.h>
#include <bonobo/bonobo-widget.h>

#include <libgnome/gnome-i18n.h>
#include <libgnome/gnome-url.h>
#include <libgnome/gnome-exec.h>
#include <libgnomeui/gnome-propertybox.h>

#include <stdlib.h>

#include <gtkhtml/gtkhtml.h>
#include <gtkhtml/gtkhtml-stream.h>
#include <gal/util/e-util.h>
#include <gal/widgets/e-unicode.h>

#include <liboaf/liboaf.h>

#include <libgnomevfs/gnome-vfs.h>
#include "e-summary.h"
#include "e-summary-url.h"
#include "e-summary-util.h"

#include "Composer.h"

typedef enum _ESummaryProtocol {
	PROTOCOL_NONE,
	PROTOCOL_HTTP,
	PROTOCOL_MAILTO,
	PROTOCOL_VIEW,
	PROTOCOL_EXEC,
	PROTOCOL_FILE,
	PROTOCOL_CLOSE,
	PROTOCOL_LEFT,
	PROTOCOL_RIGHT,
	PROTOCOL_UP,
	PROTOCOL_DOWN,
	PROTOCOL_CONFIGURE,
	PROTOCOL_OTHER
} ESummaryProtocol;

static char *descriptions[] = {
	N_("Open %s with the default GNOME application"),
	N_("Open %s with the default GNOME web browser"),
	N_("Send an email to %s"),
	N_("Change the view to %s"),
	N_("Run %s"),
	N_("Open %s with the default GNOME application"),
	N_("Close %s"),
	N_("Move %s to the left"),
	N_("Move %s to the right"),
	N_("Move %s into the previous row"),
	N_("Move %s into the next row"),
	N_("Configure %s"),
	N_("Open %s with the default GNOME application")
};

typedef struct _PropertyDialog {
	BonoboListener *listener;
	int listener_id;

	Bonobo_EventSource eventsource;
	GtkWidget *dialog;
} PropertyDialog;
#define COMPOSER_IID "OAFIID:GNOME_Evolution_Mail_Composer"

#if HAVECACHE
static ESummaryCache *image_cache = NULL;
#endif

gboolean e_summary_url_mail_compose (ESummary *esummary,
				     const char *url);
gboolean e_summary_url_exec (const char *exec);

struct _DownloadInfo {
	GtkHTMLStream *stream;
	char *uri;
	char *buffer;

	gboolean error;
};
typedef struct _DownloadInfo DownloadInfo;

static void
close_callback (GnomeVFSAsyncHandle *handle,
		GnomeVFSResult result,
		gpointer data)
{
	DownloadInfo *info = data;
	if (info->error) {
		gtk_html_stream_close (info->stream, GTK_HTML_STREAM_ERROR);
	} else {
		gtk_html_stream_close (info->stream, GTK_HTML_STREAM_OK);
	}

	g_free (info->uri);
	g_free (info->buffer);
	g_free (info);
}

static void
read_callback (GnomeVFSAsyncHandle *handle,
	       GnomeVFSResult result,
	       gpointer buffer,
	       GnomeVFSFileSize bytes_requested,
	       GnomeVFSFileSize bytes_read,
	       gpointer data)
{
	DownloadInfo *info = data;
	if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) {
		g_warning ("Read error");
		info->error = TRUE;
		gnome_vfs_async_close (handle, close_callback, info);
	}
	
	if (bytes_read == 0) {
		info->error = FALSE;
		gnome_vfs_async_close (handle, close_callback, info);
	} else {
		gtk_html_stream_write (info->stream, buffer, bytes_read);
		gnome_vfs_async_read (handle, buffer, 4095, read_callback,
				      info);
	}
}

static void
open_callback (GnomeVFSAsyncHandle *handle,
	       GnomeVFSResult result,
	       DownloadInfo *info)
{
	if (result != GNOME_VFS_OK) {
		gtk_html_stream_close (info->stream, GTK_HTML_STREAM_ERROR);
		g_free (info->uri);
		g_free (info);
		return;
	} 

	info->buffer = g_new (char, 4096);
	gnome_vfs_async_read (handle, info->buffer, 4095, read_callback, info);
}

void
e_summary_url_request (GtkHTML *html,
		       const gchar *url,
		       GtkHTMLStream *stream)
{
	char *filename;
	GnomeVFSAsyncHandle *handle;
	DownloadInfo *info;

	g_print ("url: %s\n", url);
	if (strncasecmp (url, "file:", 5) == 0) {
		url += 5;
		filename = e_pixmap_file (url);
	} else if (strchr (url, ':') >= strchr (url, '/')) {
		filename = e_pixmap_file (url);
	} else
		filename = g_strdup (url);

	if (filename == NULL) {
		gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
		return;
	}

	g_print ("Filename: %s\n", filename);

	info = g_new (DownloadInfo, 1);
	info->stream = stream;
	info->uri = filename;
	info->error = FALSE;

	gnome_vfs_async_open (&handle, filename, GNOME_VFS_OPEN_READ,
			      (GnomeVFSAsyncOpenCallback) open_callback, info);
}

static char *
parse_uri (const char *uri,
	   ESummaryProtocol protocol,
	   ESummary *esummary)
{
	char *parsed;
	char *p;
	int address;
	ESummaryWindow *window;

	switch (protocol) {
		
	case PROTOCOL_HTTP:
		/* "http://" == 7 */
		parsed = g_strdup (uri + 7);
		break;

	case PROTOCOL_EXEC:
		/* "exec://" == 7 */
		parsed = g_strdup (uri + 7);
		break;

	case PROTOCOL_VIEW:
		/* "view://" == 7 */
		parsed = g_strdup (uri + 7);
		break;

	case PROTOCOL_MAILTO:
		/* Fun. Mailto's might be "mailto:" or "mailto://" */
		if (strstr (uri, "mailto://") == NULL) {
			parsed = (char *) (uri + 7);
		} else {
			parsed = (char *) (uri + 9);
		}

		/* Now strip anything after a question mark, 
		   as it is a parameter (that we ignore for the time being) */
		if ( (p = strchr (parsed, '?')) != NULL) {
			parsed = g_strndup (parsed, p - parsed);
		} else {
			parsed = g_strdup (parsed);
		}

		break;

	case PROTOCOL_CLOSE:
		address = atoi (uri + 8);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		parsed = g_strdup (window->title);
		break;

	case PROTOCOL_LEFT:
		address = atoi (uri + 7);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		parsed = g_strdup (window->title);
		break;

	case PROTOCOL_RIGHT:
		address = atoi (uri + 8);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		parsed = g_strdup (window->title);
		break;

	case PROTOCOL_UP:
		address = atoi (uri + 5);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		parsed = g_strdup (window->title);
		break;

	case PROTOCOL_DOWN:
		address = atoi (uri + 7);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		parsed = g_strdup (window->title);
		break;

	case PROTOCOL_CONFIGURE:
		address = atoi (uri + 12);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		parsed = g_strdup (window->title);
		break;

	case PROTOCOL_NONE:
	case PROTOCOL_OTHER:
	default:
		/* Just return the uneditted uri. */
		parsed = g_strdup (uri);
		break;
	}

	return parsed;
}

static ESummaryProtocol
get_protocol (const char *url)
{
	char *lowerurl;
	ESummaryProtocol protocol = PROTOCOL_OTHER;
	
	lowerurl = g_strdup (url);
	g_strdown (lowerurl);
	
	/* Check for no :/ */
	if (strstr (lowerurl, "://") == NULL) {
		
		/* Annoying alternative for mailto URLs */
		if (strncmp (lowerurl, "mailto:", 6) != 0) {
			g_free (lowerurl);
			return PROTOCOL_NONE;
		} else {
			g_free (lowerurl);
			return PROTOCOL_MAILTO;
		}
	}
	
	switch (lowerurl[0]) {
	case 'c':
		switch (lowerurl[1]) {
		case 'l':
			if (strncmp (lowerurl + 2, "ose", 3) == 0)
				protocol = PROTOCOL_CLOSE;
			break;
		case 'o':
			if (strncmp (lowerurl + 2, "nfigure", 7) == 0)
				protocol = PROTOCOL_CONFIGURE;
			break;
		}

	case 'd':
		if (strncmp (lowerurl + 1, "own", 3) == 0)
			protocol = PROTOCOL_DOWN;
		break;

	case 'e':
		if (strncmp (lowerurl + 1, "xec", 3) == 0)
			protocol = PROTOCOL_EXEC;
		break;
		
	case 'f':
		if (strncmp (lowerurl + 1, "ile", 3) == 0)
			protocol = PROTOCOL_FILE;
		break;

	case 'h':
		if (strncmp (lowerurl + 1, "ttp", 3) == 0)
			protocol = PROTOCOL_HTTP;
		break;
		
	case 'l':
		if (strncmp (lowerurl + 1, "eft", 3) == 0)
			protocol = PROTOCOL_LEFT;
		break;

	case 'm':
		if (strncmp (lowerurl + 1, "ailto", 5) == 0)
			protocol = PROTOCOL_MAILTO;
		break;
		
	case 'r':
		if (strncmp (lowerurl + 1, "ight", 4) == 0)
			protocol = PROTOCOL_RIGHT;
		break;

	case 'u':
		if (lowerurl[1] == 'p')
			protocol = PROTOCOL_UP;
		break;
		
	case 'v':
		if (strncmp (lowerurl + 1, "iew", 3) == 0)
			protocol = PROTOCOL_VIEW;
		break;
		
	default:
		break;
	}
	
	g_free (lowerurl);
	
	return protocol;
}

static void
property_apply (GnomePropertyBox *propertybox,
		gint page_num,
		Bonobo_PropertyControl control)
{
	CORBA_Environment ev;

	g_print ("page_num: %d\n", page_num);

	CORBA_exception_init (&ev);
	Bonobo_PropertyControl_notifyAction (control, page_num, Bonobo_PropertyControl_APPLY, &ev);
	CORBA_exception_free (&ev);
}

static void
property_help (GnomePropertyBox *propertybox,
	       gint page_num,
	       Bonobo_PropertyControl control)
{
	CORBA_Environment ev;

	CORBA_exception_init (&ev);
	Bonobo_PropertyControl_notifyAction (control, page_num, Bonobo_PropertyControl_HELP, &ev);
	CORBA_exception_free (&ev);
}

static void
property_event (BonoboListener *listener,
		char *event_name,
		CORBA_any *any,
		CORBA_Environment *ev,
		gpointer user_data)
{
	PropertyDialog *data = (PropertyDialog *) user_data;
	if (strcmp (event_name, BONOBO_PROPERTY_CONTROL_CHANGED) == 0) {
		gnome_property_box_changed (GNOME_PROPERTY_BOX (data->dialog));
		return;
	}
}

static void
dialog_destroyed (GtkObject *object,
		  PropertyDialog *dialog)
{
	CORBA_Environment ev;

	CORBA_exception_init (&ev);
	Bonobo_EventSource_removeListener (dialog->eventsource,
					   dialog->listener_id, &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		g_warning ("Error: %s", CORBA_exception_id (&ev));
	}

	bonobo_object_unref (BONOBO_OBJECT (dialog->listener));
	CORBA_exception_free (&ev);
	g_free (dialog);
}

struct _idle_data {
	ESummary *esummary;
	ESummaryWindow *window;
};

static gboolean
idle_remove_window (gpointer data)
{
	struct _idle_data *id = data;

	e_summary_remove_window (id->esummary, id->window);
	e_summary_queue_rebuild (id->esummary);
	g_free (id);

	return FALSE;
}

void
e_summary_url_click (GtkWidget *widget,
		     const char *url,
		     ESummary *esummary)
{
	ESummaryProtocol protocol;
	char *parsed;
	int address;
	ESummaryWindow *window;
	struct _idle_data *id;
	Bonobo_Control control;
	Bonobo_Listener corba_listener;
	GtkWidget *prefsbox, *control_widget;
	CORBA_Environment ev;
	PropertyDialog *data;
	int num_pages, i;

	protocol = get_protocol (url);

	parsed = parse_uri (url, protocol, esummary);

	switch (protocol) {
	case PROTOCOL_MAILTO:
		/* Open a composer window */
		e_summary_url_mail_compose (esummary, parsed);
		break;
		
	case PROTOCOL_VIEW:
		/* Change the EShellView's current uri */
		e_summary_change_current_view (esummary, parsed);
		break;
		
	case PROTOCOL_EXEC:
		/* Execute the rest of the url */
		e_summary_url_exec (parsed);
		break;

	case PROTOCOL_CLOSE:
		/* Close the window. */
		address = atoi (url + 8);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		if (window->iid == NULL)
			break;

		id = g_new (struct _idle_data, 1);
		id->window = window;
		id->esummary = esummary;

		/* Close the window on an idle to work around a bug in
		   gnome-vfs which locks the e_summary_remove_window function 
		   and as gtkhtml has a pointer grab on, this locks the whole 
		   display. GAH! */
		g_idle_add (idle_remove_window, id);
		break;

	case PROTOCOL_CONFIGURE:
		/* Configure the window. . . */
		address = atoi (url + 12);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		if (window->iid == NULL)
			break;
	
		data = g_new (PropertyDialog, 1);
		/* Create the property box */
		prefsbox = gnome_property_box_new ();
		data->dialog = prefsbox;

		CORBA_exception_init (&ev);
		data->eventsource = window->event_source;
		data->listener = bonobo_listener_new (property_event, data);
		corba_listener = bonobo_object_corba_objref (BONOBO_OBJECT (data->listener));
		data->listener_id = Bonobo_EventSource_addListener (data->eventsource,
								    corba_listener, &ev);

		gtk_signal_connect (GTK_OBJECT (prefsbox), "apply",
				    GTK_SIGNAL_FUNC (property_apply), 
				    window->propertycontrol);
		gtk_signal_connect (GTK_OBJECT (prefsbox), "help",
				    GTK_SIGNAL_FUNC (property_help),
				    window->propertycontrol);
		gtk_signal_connect (GTK_OBJECT (prefsbox), "destroy",
				    GTK_SIGNAL_FUNC (dialog_destroyed), data);

		num_pages = Bonobo_PropertyControl__get_pageCount (window->propertycontrol, &ev);
		for (i = 0; i < num_pages; i++) {
			control = Bonobo_PropertyControl_getControl (window->propertycontrol, i, &ev);
			if (ev._major != CORBA_NO_EXCEPTION) {
				g_warning ("Unable to get property control.");
				CORBA_exception_free (&ev);
				break;
			}
			control_widget = bonobo_widget_new_control_from_objref (control,
										CORBA_OBJECT_NIL);
			gnome_property_box_append_page (GNOME_PROPERTY_BOX (prefsbox),
							control_widget,
							gtk_label_new (_("page")));
		}

		gtk_widget_show_all (prefsbox);

		break;

	case PROTOCOL_LEFT:
		/* Window left */
		address = atoi (url + 7);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		if (window->iid == NULL)
			break;

		e_summary_window_move_left (esummary, window);
		e_summary_queue_rebuild (esummary);
		break;

	case PROTOCOL_RIGHT:
		address = atoi (url + 8);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		if (window->iid == NULL)
			break;

		e_summary_window_move_right (esummary, window);
		e_summary_queue_rebuild (esummary);
		break;

	case PROTOCOL_UP:
		address = atoi (url + 5);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		if (window->iid == NULL)
			break;

		e_summary_window_move_up (esummary, window);
		e_summary_queue_rebuild (esummary);
		break;

	case PROTOCOL_DOWN:
		address = atoi (url + 7);
		window = (ESummaryWindow *) GINT_TO_POINTER (address);
		if (window->iid == NULL)
			break;

		e_summary_window_move_down (esummary, window);
		e_summary_queue_rebuild (esummary);
		break;

	case PROTOCOL_OTHER:
	case PROTOCOL_NONE:
	case PROTOCOL_HTTP:
	case PROTOCOL_FILE:
	default:
		/* Let browser handle it */
		gnome_url_show (url);
		break;
		
	}      

	g_free (parsed);
}

static void
parse_mail_url (char *url,
		GList **cc,
		GList **bcc,
		char **subject)
{
	char **options;
	int i = 0;

	options = g_strsplit (url, "&", 0);
	while (options[i] != NULL) {
		char **params;

		params = g_strsplit (options[i], "=", 2);
		if (strcmp (params[0], "subject") == 0) {
			*subject = g_strdup (params[1]);
		} else if (strcmp (params[0], "cc") == 0) {
			*cc = g_list_prepend (*cc, g_strdup (params[1]));
		} else if (strcmp (params[1], "bcc") == 0) {
			*bcc = g_list_prepend (*bcc, g_strdup (params[1]));
		}

		g_strfreev (params);
		i++;
	}
	
	g_strfreev (options);
	/* Reverse the list so it's in the correct order */
	*cc = g_list_reverse (*cc);
	*bcc = g_list_reverse (*bcc);
}

static void
recipients_from_list (GNOME_Evolution_Composer_RecipientList *recipients,
		      GList *list)
{
	GList *t;
	int i;

	for (i = 0, t = list; t; i++, t = t->next) {
		GNOME_Evolution_Composer_Recipient *recipient;
		char *address = (char *)t->data;

		recipient = recipients->_buffer + i;
		recipient->name = CORBA_string_dup ("");
		recipient->address = CORBA_string_dup (address ? address : "");
	}
}

static void
free_list (GList *list)
{
	for (; list; list = list->next) {
		g_free (list->data);
	}
}

gboolean
e_summary_url_mail_compose (ESummary *esummary,
			    const char *url)
{
	CORBA_Object composer;
	CORBA_Environment ev;
	char *full_address, *address, *proto, *q;
	GNOME_Evolution_Composer_RecipientList *to, *cc, *bcc;
	GNOME_Evolution_Composer_Recipient *recipient;
	CORBA_char *subject;
	GList *gcc = NULL, *gbcc = NULL;
	char *gsubject = NULL;
	
	CORBA_exception_init (&ev);
	
	/* FIXME: Query for IIDs? */
	composer = oaf_activate_from_id ((char *)COMPOSER_IID, 0, NULL, &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		CORBA_exception_free (&ev);
		g_warning ("Unable to start composer component!");
		return FALSE;
	}

	if ( (proto = strstr (url, "://")) != NULL){
		full_address = proto + 3;
	} else {
		if (strncmp (url, "mailto:", 7) == 0)
			full_address = (char *) (url + 7);
		else
			full_address = (char *) url;
	}

	q = strchr (full_address, '?');
	if (q != NULL) {
		address = g_strndup (full_address, q - full_address);
		parse_mail_url (q + 1, &gcc, &gbcc, &gsubject);
	} else {
		address = g_strdup (full_address);
	}

	to = GNOME_Evolution_Composer_RecipientList__alloc ();
	to->_length = 1;
	to->_maximum = 1;
	to->_buffer = CORBA_sequence_GNOME_Evolution_Composer_Recipient_allocbuf (to->_maximum);
	
	recipient = to->_buffer;
	recipient->name = CORBA_string_dup ("");
	recipient->address = CORBA_string_dup (address?address:"");
	g_free (address);

	/* FIXME: Get these out of the URL */
	cc = GNOME_Evolution_Composer_RecipientList__alloc ();
	cc->_length = g_list_length (gcc);
	cc->_maximum = cc->_length;
	cc->_buffer = CORBA_sequence_GNOME_Evolution_Composer_Recipient_allocbuf (cc->_maximum);
	
	recipients_from_list (cc, gcc);
	free_list (gcc);
	g_list_free (gcc);

	bcc = GNOME_Evolution_Composer_RecipientList__alloc ();
	bcc->_length = g_list_length (gbcc);
	bcc->_maximum = bcc->_length;
	bcc->_buffer = CORBA_sequence_GNOME_Evolution_Composer_Recipient_allocbuf (bcc->_maximum);
	
	recipients_from_list (bcc, gbcc);
	free_list (gbcc);
	g_list_free (gbcc);

	subject = CORBA_string_dup (gsubject ? gsubject : "");
	g_free (gsubject);

	CORBA_exception_init (&ev);
	GNOME_Evolution_Composer_setHeaders (composer, to, cc, bcc, subject, &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		CORBA_exception_free (&ev);
		CORBA_free (to);
		CORBA_free (cc);
		CORBA_free (bcc);
		CORBA_free (subject);

		g_warning ("%s(%d): Error setting headers", __FUNCTION__, __LINE__);
		return FALSE;
	}
	
	CORBA_free (to);
	CORBA_free (cc);
	CORBA_free (bcc);
	CORBA_free (subject);

	CORBA_exception_init (&ev);  
	GNOME_Evolution_Composer_show (composer, &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		CORBA_exception_free (&ev);
		g_warning ("%s(%d): Error showing composer", __FUNCTION__, __LINE__);
		return FALSE;
	}
	
	CORBA_exception_free (&ev);
	
	/* FIXME: Free the composer? */
	
	return TRUE;
}

gboolean
e_summary_url_exec (const char *exec)
{
	gchar **exec_array;
	int argc;
	
	exec_array = g_strsplit (exec, " ", 0);
	
	argc = 0;
	while (exec_array[argc] != NULL) {
		argc++;
	}
	
	gnome_execute_async (NULL, argc, exec_array);
	
	g_strfreev (exec_array);
	return TRUE;
}

static char *
e_summary_url_describe (const char *uri, 
			ESummary *esummary)
{
	ESummaryProtocol protocol;
	char *contents, *description, *tmp;

	protocol = get_protocol (uri);
	contents = parse_uri (uri, protocol, esummary);
	tmp = e_utf8_to_locale_string (contents);

	description = g_strdup_printf (_(descriptions[protocol]), tmp);
	g_free (tmp);
	g_free (contents);

	return description;
}

void
e_summary_url_over (GtkHTML *html,
		    const char *uri,
		    ESummary *esummary)
{
	char *description;

	if (uri != NULL) {
		description = e_summary_url_describe (uri, esummary);
		e_summary_set_message (esummary, description, FALSE);
		g_free (description);
	} else {
		e_summary_unset_message (esummary);
	}
}

/* Cache stuff */
#if HAVECACHE
void
e_summary_url_init_cache (void)
{
	if (image_cache != NULL)
		return;

	image_cache = e_summary_cache_new ();
}

void
e_summary_url_cache_destroy (void)
{
	if (image_cache == NULL)
		return;

	gtk_object_unref (GTK_OBJECT (image_cache));

	image_cache = NULL;
}
#endif