/*
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
 * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Claws Mail Team
 * This file Copyright (C) 2006 iSteve <isteve@bofh.cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <unistd.h>
#include <stdio.h>

#include <glib.h>
#include "gettext.h"
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <libgtkhtml/gtkhtml.h>
#include <libgtkhtml/view/htmlselection.h>
#include <libgtkhtml/layout/htmlbox.h>
#include <libgtkhtml/layout/htmlboxtext.h>
#include "common/claws.h"
#include "common/version.h"
#include "main.h"
#include "plugin.h"
#include "utils.h"
#include "mimeview.h"
#include "messageview.h"
#include "prefs_common.h"
#include "gtkhtml2_prefs.h"
#include "log.h"
#include "codeconv.h"
#include "plugin.h"
#include "menu.h"
#include "defs.h"

#ifdef USE_PTHREAD
#include <pthread.h>
#endif

#ifdef HAVE_LIBCURL
#include <curl/curl.h>
#include <curl/curlver.h>
#endif

typedef struct _GtkHtml2Viewer GtkHtml2Viewer;

struct _GtkHtml2Viewer
{
	MimeViewer	 mimeviewer;
	GtkWidget	*html_view;
	GtkWidget	*scrollwin;
	HtmlDocument	*html_doc;
	gchar		*filename;
	gchar		*base;
	MimeInfo	*mimeinfo;
	MimeInfo	*to_load;
	gboolean	 force_image_loading;
	gint		 tag;
	gint		 loading;
	gint		 stop_previous;
	GMutex		*mutex;
	GtkWidget 	*link_popupmenu;
	GtkItemFactory 	*link_popupfactory;
	gint		 last_search_match;
};

static MimeViewerFactory gtkhtml2_viewer_factory;
static gchar *gtkhtml2_viewer_tmpdir = NULL;

static void gtkhtml_open_uri_cb			(GtkHtml2Viewer *viewer,
						 guint		 action,
						 void		*data);
static void gtkhtml_copy_uri_cb			(GtkHtml2Viewer *viewer,
						 guint		 action,
						 void		*data);

static GtkItemFactoryEntry gtkhtml_link_popup_entries[] = 
{
	{N_("/_Open with Web browser"),	NULL, gtkhtml_open_uri_cb, 0, NULL},
	{N_("/Copy this _link"),	NULL, gtkhtml_copy_uri_cb, 0, NULL},
};

static void scrolled_cb (GtkAdjustment *adj, GtkHtml2Viewer *viewer)
{
}


static GtkWidget *gtkhtml2_get_widget(MimeViewer *_viewer)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *) _viewer;

	debug_print("gtkhtml2_get_widget: %p\n", viewer->scrollwin);

	return GTK_WIDGET(viewer->scrollwin);
}

static gint gtkhtml2_show_mimepart_real(MimeViewer *_viewer)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *) _viewer;
	FILE *fp;
	gchar buf[4096];
	gint loaded = 0;
	const gchar *charset = NULL;
	MessageView *messageview = ((MimeViewer *)viewer)->mimeview 
					? ((MimeViewer *)viewer)->mimeview->messageview 
					: NULL;
	MimeInfo *partinfo = viewer->to_load;

	memset(buf, 0, sizeof(buf));

	viewer->loading = 1;
	messageview->updating = TRUE;
	debug_print("gtkhtml2_show_mimepart\n");

	if (viewer->filename != NULL) {
		g_unlink(viewer->filename);
		g_free(viewer->filename);
		viewer->filename = NULL;
	}
	g_free(viewer->base);

	viewer->base = NULL;
	viewer->mimeinfo = NULL;
	if (messageview) {
		NoticeView *noticeview = messageview->noticeview;
		noticeview_hide(noticeview);
	}

	if (partinfo)
		viewer->filename = procmime_get_tmp_file_name(partinfo);
	html_document_clear(viewer->html_doc);

	html_view_zoom_reset(HTML_VIEW(viewer->html_view));
	if (partinfo && !(procmime_get_part(viewer->filename, partinfo) < 0)) {

		if (_viewer && _viewer->mimeview &&
		    _viewer->mimeview->messageview->forced_charset)
			charset = _viewer->mimeview->messageview->forced_charset;
		else
			charset = procmime_mimeinfo_get_parameter(partinfo, "charset");
		if (charset == NULL)
			charset = conv_get_locale_charset_str();

		debug_print("using charset %s\n", charset);
		
		if (html_document_open_stream(viewer->html_doc, "text/html")) {
			gboolean got_charset = FALSE;
			fp = fopen(viewer->filename, "r");

			if (fp == NULL) {
				html_document_close_stream(viewer->html_doc);
				goto out;
			}

			while ((loaded = fread(buf, 1, 4096, fp)) > 0) {
				if (strcasestr(buf, "<meta") &&
				    strcasestr(buf, "http-equiv") &&
				    strcasestr(buf, "charset"))
					got_charset = TRUE; /* hack */
				
				g_mutex_lock(viewer->mutex);
				if (!viewer->stop_previous && !claws_is_exiting() && got_charset == FALSE) {
					if (strcasestr(buf, "</head>")) {
						gchar *meta_charset = g_strdup_printf(
							"<meta http-equiv=Content-Type content=\"text/html; charset=%s\">",
							charset);
						html_document_write_stream(
							viewer->html_doc, meta_charset, strlen(meta_charset));
						debug_print("injected %s\n", meta_charset);
						g_free(meta_charset);
						got_charset = TRUE;
					} else if (strcasestr(buf, "<body>")) {
						gchar *meta_charset = g_strdup_printf(
							"<head><meta http-equiv=Content-Type content=\"text/html; charset=%s\"></head>",
							charset);
						html_document_write_stream(
							viewer->html_doc, meta_charset, strlen(meta_charset));
						debug_print("injected %s and head\n", meta_charset);
						g_free(meta_charset);
						got_charset = TRUE;
					} 
				}
				if (!viewer->stop_previous && !claws_is_exiting())
					html_document_write_stream(viewer->html_doc, buf, loaded);
				else {
					g_mutex_unlock(viewer->mutex);
					break;
				}
				g_mutex_unlock(viewer->mutex);
				if (gtk_events_pending())
					gtk_main_iteration();
			}
			fclose(fp);
			html_document_close_stream(viewer->html_doc);
			viewer->mimeinfo = partinfo;
		}
	}
	g_mutex_lock(viewer->mutex);
out:
	viewer->tag = -1;
	viewer->loading = 0;
	viewer->stop_previous = FALSE;
	viewer->force_image_loading = FALSE;	
	g_mutex_unlock(viewer->mutex);
	messageview->updating = FALSE;
	return FALSE;
}

static gint gtkhtml2_show_mimepart_prepare(MimeViewer *_viewer)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *) _viewer;

	if (!g_mutex_trylock(viewer->mutex)) {
		if (viewer->loading) {
			viewer->stop_previous = TRUE;
			main_window_cursor_normal(mainwindow_get_mainwindow());
		}
		return TRUE;
	}

	if (viewer->tag > 0) {
		gtk_timeout_remove(viewer->tag);
		viewer->tag = -1;
		if (viewer->loading) {
			viewer->stop_previous = TRUE;
			main_window_cursor_normal(mainwindow_get_mainwindow());
		}
	}
	if (viewer->stop_previous) {
		g_mutex_unlock(viewer->mutex);
		return TRUE;
	}
	viewer->tag = gtk_timeout_add(5, (GtkFunction)gtkhtml2_show_mimepart_real, viewer);
	g_mutex_unlock(viewer->mutex);
	return FALSE;
}

static void gtkhtml2_show_mimepart(MimeViewer *_viewer,
				const gchar *infile,
				MimeInfo *partinfo)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *) _viewer;
	viewer->to_load = partinfo;
	viewer->last_search_match = -1;
	gtk_timeout_add(5, (GtkFunction)gtkhtml2_show_mimepart_prepare, viewer);
}

#ifdef HAVE_LIBCURL
static void reload_with_img(NoticeView *noticeview, GtkHtml2Viewer *viewer)
{
	viewer->force_image_loading = TRUE;
	gtkhtml2_show_mimepart((MimeViewer *)viewer, NULL, viewer->mimeinfo);
}
#endif

static void gtkhtml2_clear_viewer(MimeViewer *_viewer)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *) _viewer;
	GtkAdjustment *vadj;
	
	debug_print("gtkhtml2_clear_viewer\n");
	viewer->to_load = NULL;
	if (!viewer->loading)
		html_document_clear(viewer->html_doc);
	viewer->last_search_match = -1;
	vadj = gtk_scrolled_window_get_vadjustment(
		GTK_SCROLLED_WINDOW(viewer->scrollwin));
	vadj->value = 0.0;
	g_signal_emit_by_name(G_OBJECT(vadj), "value-changed", 0);
}

static void gtkhtml2_destroy_viewer(MimeViewer *_viewer)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *) _viewer;

	debug_print("gtkhtml2_destroy_viewer\n");

	gtk_widget_unref(GTK_WIDGET(viewer->html_view));
	gtk_widget_unref(GTK_WIDGET(viewer->scrollwin));
	g_unlink(viewer->filename);
	g_free(viewer->filename);
    	g_free(viewer);
}

static gchar *make_url(const gchar *url, const gchar *base)
{
	if (url == NULL) {
		return NULL;
	} else if (strstr(url, "http://") ||
	    strstr(url, "https://") ||
	    strstr(url, "ftp://") ||
	    strstr(url, "sftp://") ||
	    strstr(url, "mailto:")) {
		return g_strdup(url);
	} else if (base == NULL || !strstr(base, "://")) {
		return g_strdup(url);
	} else {
		gchar *prefix = g_strdup(base);
		gchar *real_base = g_strdup(strstr(base, "://")+3);
		gchar *result = NULL;
		gboolean insert_sep = FALSE;
		*(strstr(prefix, "://")+3) = '\0';
		if (url[0] == '/') { /* absolute */
			if (strchr(real_base, '/'))
				*strchr(real_base, '/') = '\0';
		} else if (url[0] != '#') { /* relative and not anchor */
			if (strrchr(real_base, '/'))
				*(strrchr(real_base, '/')+1) = '\0';
			else
				insert_sep = TRUE;
		} else if (url[0] == '#') {
			insert_sep = FALSE;
		}
		result = g_strdup_printf("%s%s%s%s", prefix, real_base, insert_sep?"/":"", url);
		g_free(prefix);
		g_free(real_base);
		return result;
	}
}

static void gtkhtml_open_uri_cb (GtkHtml2Viewer *viewer, guint action, void *data)
{
	gchar *uri = g_object_get_data(G_OBJECT(viewer->link_popupmenu),
					   "uri");
	g_object_set_data(G_OBJECT(viewer->link_popupmenu), "uri", NULL);
	open_uri(uri, prefs_common.uri_cmd);
	g_free(uri);
}

static void gtkhtml_copy_uri_cb (GtkHtml2Viewer *viewer, guint action, void *data)
{
	gchar *uri = g_object_get_data(G_OBJECT(viewer->link_popupmenu),
					   "uri");
	g_object_set_data(G_OBJECT(viewer->link_popupmenu), "uri", NULL);
	gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri, -1);
	gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri, -1);
	g_free(uri);
}

/* DESCRIPTION:
 * This callback gets executed if a user clicks a link, that is, if he
 * apparently attempts to move onto another document. Commonly handle
 * this by loading a document in same fashion as gtkhtml2_show_mimepart,
 * or just ignore it. */
void link_clicked(HtmlDocument *doc, const gchar *url, GtkHtml2Viewer *viewer) {
	gchar *real_url = make_url(url, viewer->base);
	GdkEvent *event = gtk_get_current_event();
	GdkEventButton *bevent = NULL;
	gint button = 1;
	gchar *tmp = g_object_get_data(G_OBJECT(viewer->link_popupmenu),
					   "uri");
	if (tmp)
		g_free(tmp);
	g_object_set_data(G_OBJECT(viewer->link_popupmenu), "uri", NULL);

	if (event && event->type == GDK_BUTTON_RELEASE) {
		bevent = (GdkEventButton *)event;
		button = bevent->button;
	}
	gdk_event_free(event);

	if (button == 1 || button == 2) {
		if (real_url)
			open_uri(real_url, prefs_common.uri_cmd);
		else
			open_uri(url, prefs_common.uri_cmd);
	} else if (button == 3) {
		g_object_set_data(G_OBJECT(viewer->link_popupmenu), "uri",
			  real_url ? g_strdup(real_url):g_strdup(url));
		gtk_menu_popup(GTK_MENU(viewer->link_popupmenu), 
			       NULL, NULL, NULL, NULL, 
			       bevent->button, bevent->time);
	}
	g_free(real_url);
}


/* DESCRIPTION:
 * This callback informs whenever user has a pointer over any URL. Probably
 * only useful for debugging and for statusbar update.
 */
static void on_url(GtkWidget *widget, const gchar *url, gpointer data)
{
	gchar *trimmed_uri = NULL;
	MimeViewer *viewer = (MimeViewer *)data;
	MessageView *messageview = viewer->mimeview ? viewer->mimeview->messageview : NULL;

	g_return_if_fail(messageview != NULL);

	if (url != NULL) {
		gchar *real_url = make_url(url, ((GtkHtml2Viewer *)data)->base);
		trimmed_uri = trim_string(real_url?real_url:url, 60);
		if (messageview->statusbar)
			gtk_statusbar_push(GTK_STATUSBAR(messageview->statusbar),
				   messageview->statusbar_cid, trimmed_uri);
		g_free(real_url);
		g_free(trimmed_uri);
	} else {
		if (messageview->statusbar)
			gtk_statusbar_pop(GTK_STATUSBAR(messageview->statusbar),
				   messageview->statusbar_cid);
	}
}

#ifdef HAVE_LIBCURL
struct _GtkHtmlThreadCtx {
	gchar *url;
	gboolean ready;
};

typedef struct _GtkHtmlThreadCtx GtkHtmlThreadCtx;

static void *gtkhtml_fetch_feed_threaded(void *arg)
{
	GtkHtmlThreadCtx *ctx = (GtkHtmlThreadCtx *)arg;
	CURL *eh = NULL;
	CURLcode res;
	gchar *template = get_tmp_file();
	FILE *f = NULL;

#ifdef USE_PTHREAD
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
#endif

	if (template != NULL)
		f = fopen(template, "wb");
	if (f == NULL) {
		perror("fdopen");
		ctx->ready = TRUE;
		g_unlink(template);
		g_free(template);
		return NULL;
	}

	eh = curl_easy_init();

	if (eh == NULL) {
		g_warning("can't init curl");
		ctx->ready = TRUE;
		g_unlink(template);
		g_free(template);
		return NULL;
	}

	curl_easy_setopt(eh, CURLOPT_URL, ctx->url);
	curl_easy_setopt(eh, CURLOPT_NOPROGRESS, 1);
	curl_easy_setopt(eh, CURLOPT_WRITEDATA, f);
	curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1);
	curl_easy_setopt(eh, CURLOPT_MAXREDIRS, 3);
#ifndef USE_PTHREAD
	curl_easy_setopt(eh, CURLOPT_TIMEOUT, prefs_common.io_timeout_secs);
#endif
#if LIBCURL_VERSION_NUM >= 0x070a00
	curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0);
	curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 0);
#endif
	curl_easy_setopt(eh, CURLOPT_USERAGENT,
		"Claws Mail GtkHtml2 plugin "PLUGINVERSION
		" (" PLUGINS_URI ")");

	res = curl_easy_perform(eh);

	curl_easy_cleanup(eh);

	fclose(f);

	ctx->ready = TRUE;

	return template;
}
#endif

static void set_base(HtmlDocument *doc, const gchar *url, gpointer data)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *)data;
	g_free(viewer->base);
	viewer->base = g_strdup(url);
	
}

static void requested_url(HtmlDocument *doc, const gchar *url, HtmlStream *stream, gpointer data)
{
	void *tmpfile = NULL;
#ifdef HAVE_LIBCURL
#ifdef USE_PTHREAD
	pthread_t pt;
#endif
	GtkHtmlThreadCtx *ctx = NULL;
	time_t start_time = time(NULL);
	gboolean killed = FALSE;
	gboolean remote_not_loaded = FALSE;
#endif
	gint loaded = 0;
	char buffer[4096];
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *)data;

	main_window_cursor_wait(mainwindow_get_mainwindow());
	if (!url)
		goto fail;
        if (strncmp(url, "cid:", 4) == 0) {
                MimeInfo *mimeinfo = ((MimeViewer *)data)->mimeview->mimeinfo;
                gchar *image = g_strconcat("<", url + 4, ">", NULL);

		debug_print("looking for %s in Content-ID\n", image);

                if (url + 4 == '\0') {
			g_free(image);
                        goto fail;
		}

                while ((mimeinfo = procmime_mimeinfo_next(mimeinfo)) != NULL) {
                        if (mimeinfo->id != NULL && strcmp(mimeinfo->id, image) == 0)
                                break;
                }
                g_free(image);

                if (mimeinfo == NULL) {
                        goto fail;
                }
		debug_print("found %s in mimeinfo's Content-ID\n", mimeinfo->id);

                tmpfile = procmime_get_tmp_file_name(mimeinfo);
                if (tmpfile == NULL)
                        goto fail;

                if (procmime_get_part(tmpfile, mimeinfo) < 0) {
                        g_free(tmpfile);
			tmpfile = NULL;
                        goto fail;
                }
        }
        else
        {
                MimeInfo *mimeinfo = ((MimeViewer *)data)->mimeview->mimeinfo;
#ifdef HAVE_LIBCURL
		gchar *real_url = NULL;
		gchar *cache_file = NULL;
#endif
		debug_print("looking for %s in Content-Location\n", url);
                if (url == '\0')
                        goto fail;

                while ((mimeinfo = procmime_mimeinfo_next(mimeinfo)) != NULL) {
                        if (mimeinfo->location != NULL && strcmp(mimeinfo->location, url) == 0)
                                break;
                }

                if (mimeinfo == NULL) {
                        goto not_found_local;
                }
		debug_print("found %s in mimeinfo's Content-Location\n", mimeinfo->location);
                tmpfile = procmime_get_tmp_file_name(mimeinfo);
                if (tmpfile == NULL)
                        goto not_found_local;

                if (procmime_get_part(tmpfile, mimeinfo) < 0) {
                        g_free(tmpfile);
			tmpfile = NULL;
                        goto not_found_local;
                }
		goto found_local;

not_found_local:
#ifdef HAVE_LIBCURL
		real_url = NULL;
		cache_file = NULL;
                if (!viewer->force_image_loading && gtkhtml_prefs.local) {
			remote_not_loaded = TRUE;
                        goto fail;
		}

	        debug_print("looking for %s online\n", url);
		debug_print("using %s base\n", viewer->base);
		real_url = make_url(url, ((GtkHtml2Viewer *)data)->base);
		if (gtkhtml_prefs.cache_images) {
			cache_file = g_strconcat(gtkhtml2_viewer_tmpdir, G_DIR_SEPARATOR_S,
							itos(g_str_hash(real_url)), NULL);
			if (is_file_exist(cache_file)) {
				debug_print("cache file found (%s)\n", cache_file);
				tmpfile = get_tmp_file();
				if (link(cache_file, tmpfile) == 0) {
					g_free(cache_file);
					g_free(real_url);
					goto found_local;
				}
			} 
			debug_print("cache file not found (%s)\n", cache_file);
			if (!viewer->force_image_loading && prefs_common.work_offline) {
				remote_not_loaded = TRUE;
				goto fail;
			}
		}
	        ctx = g_new0(GtkHtmlThreadCtx, 1);
	        ctx->url = real_url;
	        ctx->ready = FALSE;
		debug_print("final URL: %s\n", ctx->url);
#ifdef USE_PTHREAD
	        if( pthread_create(&pt, PTHREAD_CREATE_JOINABLE, gtkhtml_fetch_feed_threaded,
				        (void *)ctx) != 0 ) {
		        /* Bummer, couldn't create thread. Continue non-threaded */
		        tmpfile = gtkhtml_fetch_feed_threaded(ctx);
	        } else {
		        /* Thread created, let's wait until it finishes */
		        debug_print("gtkhtml: waiting for thread to finish\n");
		        while( !ctx->ready ) {
			        claws_do_idle();
				if (time(NULL) - start_time > prefs_common.io_timeout_secs) {
					log_error(LOG_PROTOCOL, _("Timeout connecting to %s\n"), url);
					pthread_cancel(pt);
					ctx->ready = TRUE;
					killed = TRUE;
				} 
				if (viewer->stop_previous || claws_is_exiting()) {
					pthread_cancel(pt);
					ctx->ready = TRUE;
					killed = TRUE;
				} 
			}
		        debug_print("gtkhtml: thread finished\n");

		        pthread_join(pt, &tmpfile);
			if (killed)
				tmpfile = NULL;
	        }
		g_free(ctx->url);
#else
	        debug_print("gtkhtml: no pthreads, run blocking fetch\n");
	        (gchar *)tmpfile = gtkhtml_fetch_feed_threaded(ctx);
#endif
		if (gtkhtml_prefs.cache_images && tmpfile) {
			link(tmpfile, cache_file);
			debug_print("cache file created (%s)\n", cache_file);
			g_free(cache_file);
		}
#else
		debug_print("image not found locally, and no libcurl support.\n");
#endif
        }

found_local:
	debug_print("file %s\n", (char *)tmpfile);
	if (tmpfile) {
		FILE *fp = fopen(tmpfile, "r");
		if (fp == NULL) {
			html_stream_close(stream);
			g_unlink(tmpfile);
#ifdef HAVE_LIBCURL
			g_free(ctx);
#endif
			main_window_cursor_normal(mainwindow_get_mainwindow());
			return;
		}

		while ((loaded = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
			if (viewer->stop_previous || claws_is_exiting())
				break;
			html_stream_write(stream, buffer, loaded);
			while (gtk_events_pending())
				gtk_main_iteration();
		}
		fclose(fp);
		g_unlink(tmpfile);
	}
#ifdef HAVE_LIBCURL
	g_free(ctx);
#endif
fail:
	main_window_cursor_normal(mainwindow_get_mainwindow());
#ifdef HAVE_LIBCURL
	if (remote_not_loaded) {
		MessageView *messageview = ((MimeViewer *)viewer)->mimeview 
						? ((MimeViewer *)viewer)->mimeview->messageview 
						: NULL;
		if (messageview) {
			gchar *text = NULL;
			NoticeView *noticeview = messageview->noticeview;
			if (gtkhtml_prefs.local) {
				text = _("Remote images exist, but weren't loaded\naccording to your preferences.");
			} else if (prefs_common.work_offline) {
				text = _("Remote images exist, but weren't loaded\nbecause you are offline.");
			} else {
				text = _("Remote images exist, but loading them failed.");
			}
			noticeview_set_icon(noticeview, STOCK_PIXMAP_NOTICE_WARN);
			noticeview_set_text(noticeview, text);
			noticeview_set_button_text(noticeview, _("Load images"));
			noticeview_set_button_press_callback(noticeview,
				     G_CALLBACK(reload_with_img), (gpointer)viewer);

			noticeview_set_2ndbutton_text(noticeview, NULL);
			noticeview_show(noticeview);
		}
	}
#endif
	html_stream_close(stream);
}

static gchar *gtkhtml2_get_selection(MimeViewer *_viewer)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *)_viewer;
	
	if (viewer->html_view == NULL)
		return NULL;
	return html_selection_get_text((HtmlView *)viewer->html_view);
}

static HtmlBox *get_next_box(HtmlBox *box)
{
	HtmlBox *parent;
	
	if (!box)
		return NULL;
	if (box->children) {
		return box->children;
	}
	if (box->next) {
		return box->next;
	}

	for (parent = box->parent; parent != NULL;
	     parent = parent->parent) {
		if (parent->next)
			return parent->next;
	}

	return NULL;
}

static gboolean gtkhtml2_search_forward(GtkHtml2Viewer *viewer,
				      const gchar *str, gboolean case_sens,
				      gboolean select_result)
{
	HtmlBox *box = NULL;
	gchar *search_str = case_sens?g_strdup(str):g_utf8_strdown(g_strdup(str), -1);
	gint offset = 0, r_offset = 0;
	DomNode *last_node = NULL;
	if (HTML_VIEW(viewer->html_view) == NULL)
		return FALSE;
		
	box = HTML_VIEW(viewer->html_view)->root;
	for (; box; box = get_next_box(box)) {
		if (HTML_IS_BOX_TEXT(box)) {
			gchar *found = NULL;
			gchar *text = NULL;
			gchar *stext = NULL;
			
			if (HTML_BOX_TEXT(box)->canon_text == NULL)
				continue;
			text = case_sens?g_strndup(HTML_BOX_TEXT(box)->canon_text,
					HTML_BOX_TEXT(box)->length):
					g_utf8_strdown(g_strndup(HTML_BOX_TEXT(box)->canon_text,
					HTML_BOX_TEXT(box)->length), -1);
			stext = text;
			gint len;
			if (box->dom_node != last_node) {
				last_node = box->dom_node;
				r_offset = 0;
			}
search_substring:
			if (!stext)
				continue;
			found = strstr(stext, search_str);

			if (!found) {
				len = g_utf8_strlen(stext, -1);
				offset += len;
				r_offset += len;
			} else {
				debug_print("found: %s\n", found);
				*found = 0;
				debug_print("relative offset %d\n", r_offset);
				len = g_utf8_strlen(stext, -1);
				if (offset + len <= viewer->last_search_match) {
					offset += len;
					r_offset += len;
					*found=0x1;
					stext = found;
					goto search_substring;
				}
				offset += len;
				r_offset += len;
				*found=0x1;
				viewer->last_search_match = offset;
				if (select_result) {
					html_selection_set(HTML_VIEW(viewer->html_view),
						box->dom_node,
						r_offset, g_utf8_strlen(str, -1));
					html_view_scroll_to_node(HTML_VIEW(viewer->html_view),
						box->dom_node, HTML_VIEW_SCROLL_TO_BOTTOM);
				}
				g_free(text);
				return TRUE;
			}
			g_free(text);
		}
	}
	g_free(search_str);
	
	return FALSE;
}

static gboolean gtkhtml2_search_backward(GtkHtml2Viewer *viewer,
				      const gchar *str, gboolean case_sens)
{
	gint highest_index = viewer->last_search_match;
	gint prev_highest = -1;
	gint i = 0, j = 0;
	if (highest_index == -1) {
		/* find last match */
		while (gtkhtml2_search_forward(viewer, str, case_sens, FALSE))
			i++;
		highest_index = viewer->last_search_match;
		if (highest_index == -1)
			return FALSE; /* not found */
	} else {
		/* find previous match */
		prev_highest = viewer->last_search_match;
		viewer->last_search_match = -1;
		while (gtkhtml2_search_forward(viewer, str, case_sens, FALSE)
			 && viewer->last_search_match <= prev_highest)
			i++;
		i--;
		highest_index = viewer->last_search_match;
		if (highest_index == -1 || i < 0)
			return FALSE; /* not found */

	}
	
	if (i == 0)
		return FALSE;
	viewer->last_search_match = 0;
	for (j = 0; j < i-1; j++) {
		gtkhtml2_search_forward(viewer, str, case_sens, FALSE);
	}
	gtkhtml2_search_forward(viewer, str, case_sens, TRUE);
	
	return TRUE;
}

static gboolean	gtkhtml2_text_search (MimeViewer *_viewer, gboolean backward,
				      const gchar *str, gboolean case_sens)
{
	gboolean result;
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *)_viewer;
	
	if (backward)
		result = gtkhtml2_search_backward(viewer, str, case_sens);
	else
		result = gtkhtml2_search_forward(viewer, str, case_sens, TRUE);
	
	if (result == FALSE)
		viewer->last_search_match = -1;
	
	return result;
}

static gboolean gtkhtml2_scroll_page(MimeViewer *_viewer, gboolean up)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *)_viewer;
	GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
		GTK_SCROLLED_WINDOW(viewer->scrollwin));

	if (viewer->html_view == NULL)
		return FALSE;

	return gtkutils_scroll_page(viewer->html_view, vadj, up);
}

static void gtkhtml2_scroll_one_line(MimeViewer *_viewer, gboolean up)
{
	GtkHtml2Viewer *viewer = (GtkHtml2Viewer *)_viewer;
	GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
		GTK_SCROLLED_WINDOW(viewer->scrollwin));

	if (viewer->html_view == NULL)
		return;

	gtkutils_scroll_one_line(viewer->html_view, vadj, up);
}

static gboolean htmlview_scrolled(GtkWidget *widget, GdkEventScroll *event,
				    GtkHtml2Viewer *viewer)
{
	if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) {
		if (event->direction == GDK_SCROLL_UP) {
			html_view_zoom_out(HTML_VIEW(viewer->html_view));
		} else {
			html_view_zoom_in(HTML_VIEW(viewer->html_view));
		}
		return TRUE;
	}
	return FALSE;
}

static MimeViewer *gtkhtml2_viewer_create(void)
{
	GtkHtml2Viewer *viewer;
	GtkAdjustment *adj;
	gfloat min_size = 0.0, min_size_new;
	PangoFontDescription *font_desc = NULL;
	gint n_entries;
	GtkWidget *link_popupmenu;
	GtkItemFactory *link_popupfactory;

	debug_print("gtkhtml2_viewer_create\n");

	viewer = g_new0(GtkHtml2Viewer, 1);
	viewer->mimeviewer.factory = &gtkhtml2_viewer_factory;
	viewer->mimeviewer.get_widget = gtkhtml2_get_widget;
	viewer->mimeviewer.show_mimepart = gtkhtml2_show_mimepart;
	viewer->mimeviewer.clear_viewer = gtkhtml2_clear_viewer;
	viewer->mimeviewer.destroy_viewer = gtkhtml2_destroy_viewer;
	viewer->mimeviewer.get_selection = gtkhtml2_get_selection;
	viewer->mimeviewer.text_search = gtkhtml2_text_search;
	viewer->mimeviewer.scroll_page = gtkhtml2_scroll_page;
	viewer->mimeviewer.scroll_one_line = gtkhtml2_scroll_one_line;

	viewer->html_doc = html_document_new();
	viewer->html_view = html_view_new();
	viewer->scrollwin = gtk_scrolled_window_new(NULL, NULL);
	viewer->base      = NULL;
	viewer->mimeinfo  = NULL;
	viewer->force_image_loading = FALSE;
	viewer->tag       = -1;
	viewer->mutex     = g_mutex_new();

	font_desc = pango_font_description_from_string
			(prefs_common.textfont);
	min_size_new = (gfloat)(pango_font_description_get_size(font_desc)/PANGO_SCALE);
	pango_font_description_free(font_desc);

	g_object_get (gtk_settings_get_default (),
			"gtkhtml-minimum-font-size",
			&min_size, NULL);

	if (min_size > 0.0 && min_size < min_size_new) {
		debug_print("setting minimum size to %.2f (overriding %.2f)\n",
				min_size_new, min_size);
		gtk_settings_set_double_property(gtk_settings_get_default(),
                                       "gtkhtml-minimum-font-size",
                                       (gdouble)min_size_new, "XProperty");
	} else if (min_size <= 0.0) {
		g_warning("Can't set minimum font size - you need libgtkhtml > 2.11.0\n");
	}

	gtk_scrolled_window_set_policy(
			GTK_SCROLLED_WINDOW(viewer->scrollwin), 
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	gtk_scrolled_window_set_shadow_type(
			GTK_SCROLLED_WINDOW(viewer->scrollwin),
			GTK_SHADOW_IN);

	gtk_container_add(
		GTK_CONTAINER(viewer->scrollwin),
		viewer->html_view);

	adj = gtk_scrolled_window_get_vadjustment(
		GTK_SCROLLED_WINDOW(viewer->scrollwin));
	g_signal_connect(G_OBJECT(adj), "value-changed",
			 G_CALLBACK(scrolled_cb), viewer);

	html_view_set_document(HTML_VIEW(viewer->html_view), viewer->html_doc);
	g_signal_connect(G_OBJECT(viewer->html_doc), "set_base", G_CALLBACK(set_base), viewer);
	g_signal_connect(G_OBJECT(viewer->html_doc), "request_url", G_CALLBACK(requested_url), viewer);
	g_signal_connect(G_OBJECT(viewer->html_doc), "link_clicked", G_CALLBACK(link_clicked), viewer);
	g_signal_connect(G_OBJECT(viewer->html_view),"on_url", G_CALLBACK(on_url), viewer);
	g_signal_connect(G_OBJECT(viewer->html_view), "scroll_event",
			 G_CALLBACK(htmlview_scrolled), viewer);

	gtk_widget_show(GTK_WIDGET(viewer->scrollwin));
	gtk_widget_ref(GTK_WIDGET(viewer->scrollwin));
	gtk_widget_show(GTK_WIDGET(viewer->html_view));
	gtk_widget_ref(GTK_WIDGET(viewer->html_view));

	n_entries = sizeof(gtkhtml_link_popup_entries) /
		sizeof(gtkhtml_link_popup_entries[0]);
	link_popupmenu = menu_create_items(gtkhtml_link_popup_entries, n_entries,
				      "<UriPopupMenu>", &link_popupfactory,
				      viewer);
	viewer->link_popupmenu = link_popupmenu;
	viewer->link_popupfactory = link_popupfactory;
	viewer->filename = NULL;

	return (MimeViewer *) viewer;
}

static gchar *content_types[] =
	{"text/html", NULL};

static MimeViewerFactory gtkhtml2_viewer_factory =
{
	content_types,
	0,

	gtkhtml2_viewer_create,
};

gint plugin_init(gchar **error)
{
/*	make_url tests - leaky 
	printf("%s\n", make_url("http://abs_url_w_host", NULL));
	printf("%s\n", make_url("http://abs_url_w_host", "ftp://base/"));
	printf("%s\n", make_url("/abs_url", NULL));
	printf("%s\n", make_url("/abs_url", "ftp://base"));
	printf("%s\n", make_url("/abs_url", "ftp://base/a"));
	printf("%s\n", make_url("/abs_url", "ftp://base/a/"));
	printf("%s\n", make_url("rel_url", NULL));
	printf("%s\n", make_url("rel_url", "ftp://base"));
	printf("%s\n", make_url("rel_url", "ftp://base/a/b"));
	printf("%s\n", make_url("rel_url", "ftp://base/a/b/"));
*/
	bindtextdomain(TEXTDOMAIN, LOCALEDIR);
	bind_textdomain_codeset(TEXTDOMAIN, "UTF-8");

	gtkhtml2_viewer_tmpdir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
				"gtkhtml2_viewer", NULL);

	if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
				VERSION_NUMERIC, _("GtkHtml2 HTML Viewer"), error))
		return -1;

#ifdef HAVE_LIBCURL
	gtkhtml_prefs_init();
	curl_global_init(CURL_GLOBAL_DEFAULT);
#endif
	mimeview_register_viewer_factory(&gtkhtml2_viewer_factory);

	if (!is_dir_exist(gtkhtml2_viewer_tmpdir))
		make_dir_hier(gtkhtml2_viewer_tmpdir);

	return 0;
}

void gtkhtml2_viewer_clear_cache(void)
{
	remove_dir_recursive(gtkhtml2_viewer_tmpdir);
	make_dir_hier(gtkhtml2_viewer_tmpdir);
}

gboolean plugin_done(void)
{
	if (gtkhtml_prefs.clear_cache)
		remove_dir_recursive(gtkhtml2_viewer_tmpdir);
	g_free(gtkhtml2_viewer_tmpdir);
	gtkhtml2_viewer_tmpdir = NULL;

#ifdef HAVE_LIBCURL
	gtkhtml_prefs_done();
#endif
	mimeview_unregister_viewer_factory(&gtkhtml2_viewer_factory);
	return FALSE;
}

const gchar *plugin_name(void)
{
	return _("GtkHtml2 HTML Viewer");
}

const gchar *plugin_desc(void)
{
	return _("This plugin renders HTML mail using the gtkhtml2"
		"rendering widget.");
}

const gchar *plugin_type(void)
{
	return "GTK2";
}

const gchar *plugin_licence(void)
{
	return "GPL3+";
}

const gchar *plugin_version(void)
{
	return PLUGINVERSION;
}

struct PluginFeature *plugin_provides(void)
{
	static struct PluginFeature features[] = 
		{ {PLUGIN_MIMEVIEWER, "text/html"},
		  {PLUGIN_NOTHING, NULL}};
	return features;
}


syntax highlighted by Code2HTML, v. 0.9.1