/*
 * e-mail-display.c
 *
 * 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 of the License, or (at your option) version 3.
 *
 * 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 the program; if not, see <http://www.gnu.org/licenses/>  
 *
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#include "e-mail-display.h"

#include <string.h>
#include <glib/gi18n.h>

#include "e-util/e-util.h"

#define E_MAIL_DISPLAY_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_MAIL_DISPLAY, EMailDisplayPrivate))

struct _EMailDisplayPrivate {
	EMFormatHTML *formatter;
};

enum {
	PROP_0,
	PROP_ANIMATE,
	PROP_CARET_MODE,
	PROP_FORMATTER
};

enum {
	POPUP_EVENT,
	STATUS_MESSAGE,
	LAST_SIGNAL
};

static gpointer parent_class;
static guint signals[LAST_SIGNAL];

static gboolean
mail_display_emit_popup_event (EMailDisplay *display,
                               GdkEventButton *event,
                               const gchar *uri,
                               EMFormatPURI *puri)
{
	CamelMimePart *mime_part;
	gboolean stop_handlers = FALSE;

	mime_part = (puri != NULL) ? puri->part : NULL;

	g_signal_emit (
		display, signals[POPUP_EVENT], 0,
		event, uri, mime_part, &stop_handlers);

	return stop_handlers;
}

static void
mail_display_emit_status_message (EMailDisplay *display,
                                  const gchar *status_message)
{
	g_signal_emit (display, signals[STATUS_MESSAGE], 0, status_message);
}

static void
mail_display_get_uri_puri (EMailDisplay *display,
                           GdkEventButton *event,
                           gchar **uri,
                           EMFormatPURI **puri)
{
	EMFormat *formatter;
	GtkHTML *html;
	gchar *text_uri;
	gchar *image_uri;
	gboolean is_cid;

	html = GTK_HTML (display);
	formatter = EM_FORMAT (display->priv->formatter);

	if (event != NULL) {
		text_uri = gtk_html_get_url_at (html, event->x, event->y);
		image_uri = gtk_html_get_image_src_at (html, event->x, event->y);
	} else {
		text_uri = gtk_html_get_cursor_url (html);
		image_uri = gtk_html_get_cursor_image_src (html);
	}

	is_cid = (image_uri != NULL) &&
		(g_ascii_strncasecmp (image_uri, "cid:", 4) == 0);

	if (image_uri != NULL) {
		if (strstr (image_uri, "://") == NULL && !is_cid) {
			gchar *temp;

			temp = g_strconcat ("file://", image_uri, NULL);
			g_free (image_uri);
			temp = image_uri;
		}
	}

	if (puri != NULL) {
		if (text_uri != NULL)
			*puri = em_format_find_puri (formatter, text_uri);

		if (*puri == NULL && image_uri != NULL)
			*puri = em_format_find_puri (formatter, image_uri);
	}

	if (uri != NULL) {
		*uri = NULL;
		if (is_cid) {
			if (text_uri != NULL)
				*uri = g_strdup_printf (
					"%s\n%s", text_uri, image_uri);
			else {
				*uri = image_uri;
				image_uri = NULL;
			}
		} else {
			*uri = text_uri;
			text_uri = NULL;
		}
	}

	g_free (text_uri);
	g_free (image_uri);
}

static void
mail_display_update_formatter_colors (EMailDisplay *display)
{
	EMFormatHTMLColorType type;
	EMFormatHTML *formatter;
	GdkColor *color;
	GtkStyle *style;
	gint state;

	state = GTK_WIDGET_STATE (display);
	formatter = display->priv->formatter;

	style = gtk_widget_get_style (GTK_WIDGET (display));
	if (style == NULL)
		return;

	g_object_freeze_notify (G_OBJECT (formatter));

	color = &style->bg[state];
	type = EM_FORMAT_HTML_COLOR_BODY;
	em_format_html_set_color (formatter, type, color);

	color = &style->base[GTK_STATE_NORMAL];
	type = EM_FORMAT_HTML_COLOR_CONTENT;
	em_format_html_set_color (formatter, type, color);

	color = &style->dark[state];
	type = EM_FORMAT_HTML_COLOR_FRAME;
	em_format_html_set_color (formatter, type, color);

	color = &style->fg[state];
	type = EM_FORMAT_HTML_COLOR_HEADER;
	em_format_html_set_color (formatter, type, color);

	color = &style->text[state];
	type = EM_FORMAT_HTML_COLOR_TEXT;
	em_format_html_set_color (formatter, type, color);

	g_object_thaw_notify (G_OBJECT (formatter));
}

static void
mail_display_set_property (GObject *object,
                           guint property_id,
                           const GValue *value,
                           GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_ANIMATE:
			e_mail_display_set_animate (
				E_MAIL_DISPLAY (object),
				g_value_get_boolean (value));
			return;

		case PROP_CARET_MODE:
			e_mail_display_set_caret_mode (
				E_MAIL_DISPLAY (object),
				g_value_get_boolean (value));
			return;

		case PROP_FORMATTER:
			e_mail_display_set_formatter (
				E_MAIL_DISPLAY (object),
				g_value_get_object (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
mail_display_get_property (GObject *object,
                           guint property_id,
                           GValue *value,
                           GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_ANIMATE:
			g_value_set_boolean (
				value, e_mail_display_get_animate (
				E_MAIL_DISPLAY (object)));
			return;

		case PROP_CARET_MODE:
			g_value_set_boolean (
				value, e_mail_display_get_caret_mode (
				E_MAIL_DISPLAY (object)));
			return;

		case PROP_FORMATTER:
			g_value_set_object (
				value, e_mail_display_get_formatter (
				E_MAIL_DISPLAY (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
mail_display_dispose (GObject *object)
{
	EMailDisplayPrivate *priv;

	priv = E_MAIL_DISPLAY_GET_PRIVATE (object);

	if (priv->formatter) {
		g_object_unref (priv->formatter);
		priv->formatter = NULL;
	}

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
mail_display_realize (GtkWidget *widget)
{
	/* Chain up to parent's realize() method. */
	GTK_WIDGET_CLASS (parent_class)->realize (widget);

	mail_display_update_formatter_colors (E_MAIL_DISPLAY (widget));
}

static void
mail_display_style_set (GtkWidget *widget,
                        GtkStyle *previous_style)
{
	EMailDisplayPrivate *priv;

	priv = E_MAIL_DISPLAY_GET_PRIVATE (widget);

	/* Chain up to parent's style_set() method. */
	GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);

	mail_display_update_formatter_colors (E_MAIL_DISPLAY (widget));
	em_format_redraw (EM_FORMAT (priv->formatter));
}

static gboolean
mail_display_button_press_event (GtkWidget *widget,
                                 GdkEventButton *event)
{
	if (event->button == 3) {
		EMailDisplay *display;
		EMFormatPURI *puri = NULL;
		gboolean stop_handlers = TRUE;
		gchar *uri = NULL;

		display = E_MAIL_DISPLAY (widget);
		mail_display_get_uri_puri (display, event, &uri, &puri);

		if (uri == NULL || !g_str_has_prefix (uri, "##"))
			stop_handlers = mail_display_emit_popup_event (
				display, event, uri, puri);

		g_free (uri);

		if (stop_handlers)
			return TRUE;
	}

	/* Chain up to parent's button_press_event() method. */
	return GTK_WIDGET_CLASS (parent_class)->
		button_press_event (widget, event);
}

static gboolean
mail_display_scroll_event (GtkWidget *widget,
                           GdkEventScroll *event)
{
	if (event->state & GDK_CONTROL_MASK) {
		switch (event->direction) {
			case GDK_SCROLL_UP:
				gtk_html_zoom_in (GTK_HTML (widget));
				return TRUE;
			case GDK_SCROLL_DOWN:
				gtk_html_zoom_out (GTK_HTML (widget));
				return TRUE;
			default:
				break;
		}
	}

	return FALSE;
}

static void
mail_display_link_clicked (GtkHTML *html,
                           const gchar *uri)
{
	EMailDisplayPrivate *priv;

	priv = E_MAIL_DISPLAY_GET_PRIVATE (html);
	g_return_if_fail (priv->formatter != NULL);

	if (g_str_has_prefix (uri, "##")) {
		guint32 flags;

		flags = priv->formatter->header_wrap_flags;

		if (strcmp (uri, "##TO##") == 0) {
			if (!(flags & EM_FORMAT_HTML_HEADER_TO))
				flags |= EM_FORMAT_HTML_HEADER_TO;
			else
				flags &= ~EM_FORMAT_HTML_HEADER_TO;
		} else if (strcmp (uri, "##CC##") == 0) {
			if (!(flags & EM_FORMAT_HTML_HEADER_CC))
				flags |= EM_FORMAT_HTML_HEADER_CC;
			else
				flags |= EM_FORMAT_HTML_HEADER_CC;
		} else if (strcmp (uri, "##BCC##") == 0) {
			if (!(flags & EM_FORMAT_HTML_HEADER_BCC))
				flags |= EM_FORMAT_HTML_HEADER_BCC;
			else
				flags |= EM_FORMAT_HTML_HEADER_BCC;
		}

		priv->formatter->header_wrap_flags = flags;
		em_format_redraw (EM_FORMAT (priv->formatter));

	} else if (*uri == '#')
		gtk_html_jump_to_anchor (html, uri + 1);

	else if (g_ascii_strncasecmp (uri, "thismessage:", 12) == 0)
		/* ignore */ ;

	else if (g_ascii_strncasecmp (uri, "cid:", 4) == 0)
		/* ignore */ ;

	else {
		gpointer parent;

		parent = gtk_widget_get_toplevel (GTK_WIDGET (html));
		parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

		e_show_uri (parent, uri);
	}
}

static void
mail_display_on_url (GtkHTML *html,
                     const gchar *uri)
{
	EMailDisplay *display;
	CamelInternetAddress *address;
	CamelURL *curl;
	const gchar *format = NULL;
	gchar *message = NULL;
	gchar *who;

	display = E_MAIL_DISPLAY (html);

	if (uri == NULL || *uri == '\0')
		goto exit;

	if (g_str_has_prefix (uri, "mailto:"))
		format = _("Click to mail %s");
	else if (g_str_has_prefix (uri, "callto:"))
		format = _("Click to call %s");
	else if (g_str_has_prefix (uri, "h323:"))
		format = _("Click to call %s");
	else if (g_str_has_prefix (uri, "sip:"))
		format = _("Click to call %s");
	else if (g_str_has_prefix (uri, "##"))
		message = g_strdup (_("Click to hide/unhide addresses"));
	else
		message = g_strdup_printf (_("Click to open %s"), uri);

	if (format == NULL)
		goto exit;

	curl = camel_url_new (uri, NULL);
	address = camel_internet_address_new ();
	camel_address_decode (CAMEL_ADDRESS (address), curl->path);
	who = camel_address_format (CAMEL_ADDRESS (address));
	camel_object_unref (address);
	camel_url_free (curl);

	if (who == NULL)
		who = g_strdup (strchr (uri, ':') + 1);

	message = g_strdup_printf (format, who);

	g_free (who);

exit:
	mail_display_emit_status_message (display, message);

	g_free (message);
}

static void
mail_display_iframe_created (GtkHTML *html,
                             GtkHTML *iframe)
{
	g_signal_connect_swapped (
		iframe, "button-press-event",
		G_CALLBACK (mail_display_button_press_event), html);
}

static void
mail_display_class_init (EMailDisplayClass *class)
{
	GObjectClass *object_class;
	GtkWidgetClass *widget_class;
	GtkHTMLClass *html_class;

	parent_class = g_type_class_peek_parent (class);
	g_type_class_add_private (class, sizeof (EMailDisplayPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = mail_display_set_property;
	object_class->get_property = mail_display_get_property;
	object_class->dispose = mail_display_dispose;

	widget_class = GTK_WIDGET_CLASS (class);
	widget_class->realize = mail_display_realize;
	widget_class->style_set = mail_display_style_set;
	widget_class->button_press_event = mail_display_button_press_event;
	widget_class->scroll_event = mail_display_scroll_event;

	html_class = GTK_HTML_CLASS (class);
	html_class->link_clicked = mail_display_link_clicked;
	html_class->on_url = mail_display_on_url;
	html_class->iframe_created = mail_display_iframe_created;

	g_object_class_install_property (
		object_class,
		PROP_ANIMATE,
		g_param_spec_boolean (
			"animate",
			"Animate Images",
			NULL,
			FALSE,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_CARET_MODE,
		g_param_spec_boolean (
			"caret-mode",
			"Caret Mode",
			NULL,
			FALSE,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_FORMATTER,
		g_param_spec_object (
			"formatter",
			"HTML Formatter",
			NULL,
			EM_TYPE_FORMAT_HTML,
			G_PARAM_READWRITE));

	signals[POPUP_EVENT] = g_signal_new (
		"popup-event",
		G_TYPE_FROM_CLASS (class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (EMailDisplayClass, popup_event),
		g_signal_accumulator_true_handled, NULL,
		e_marshal_BOOLEAN__BOXED_POINTER_POINTER,
		G_TYPE_BOOLEAN, 3,
		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE,
		G_TYPE_POINTER,
		G_TYPE_POINTER);

	signals[STATUS_MESSAGE] = g_signal_new (
		"status-message",
		G_TYPE_FROM_CLASS (class),
		G_SIGNAL_RUN_FIRST,
		G_STRUCT_OFFSET (EMailDisplayClass, status_message),
		NULL, NULL,
		g_cclosure_marshal_VOID__STRING,
		G_TYPE_NONE, 1,
		G_TYPE_STRING);
}

static void
mail_display_init (EMailDisplay *display)
{
	display->priv = E_MAIL_DISPLAY_GET_PRIVATE (display);
}

GType
e_mail_display_get_type (void)
{
	static GType type = 0;

	if (G_UNLIKELY (type == 0)) {
		static const GTypeInfo type_info = {
			sizeof (EMailDisplayClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) mail_display_class_init,
			(GClassFinalizeFunc) NULL,
			NULL,  /* class_data */
			sizeof (EMailDisplay),
			0,     /* n_preallocs */
			(GInstanceInitFunc) mail_display_init,
			NULL   /* value_table */
		};

		type = g_type_register_static (
			GTK_TYPE_HTML, "EMailDisplay", &type_info, 0);
	}

	return type;
}

gboolean
e_mail_display_get_animate (EMailDisplay *display)
{
	/* XXX This is just here to maintain symmetry
	 *     with e_mail_display_set_animate(). */

	g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), FALSE);

	return gtk_html_get_animate (GTK_HTML (display));
}

void
e_mail_display_set_animate (EMailDisplay *display,
                            gboolean animate)
{
	/* XXX GtkHTML does not utilize GObject properties as well
	 *     as it could.  This just wraps gtk_html_set_animate()
	 *     so we can get a "notify::animate" signal. */

	g_return_if_fail (E_IS_MAIL_DISPLAY (display));

	gtk_html_set_animate (GTK_HTML (display), animate);

	g_object_notify (G_OBJECT (display), "animate");
}

gboolean
e_mail_display_get_caret_mode (EMailDisplay *display)
{
	/* XXX This is just here to maintain symmetry
	 *     with e_mail_display_set_caret_mode(). */

	g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), FALSE);

	return gtk_html_get_caret_mode (GTK_HTML (display));
}

void
e_mail_display_set_caret_mode (EMailDisplay *display,
                               gboolean caret_mode)
{
	/* XXX GtkHTML does not utilize GObject properties as well
	 *     as it could.  This just wraps gtk_html_set_caret_mode()
	 *     so we can get a "notify::caret-mode" signal. */

	g_return_if_fail (E_IS_MAIL_DISPLAY (display));

	gtk_html_set_caret_mode (GTK_HTML (display), caret_mode);

	g_object_notify (G_OBJECT (display), "caret-mode");
}

EMFormatHTML *
e_mail_display_get_formatter (EMailDisplay *display)
{
	g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), NULL);

	return display->priv->formatter;
}

void
e_mail_display_set_formatter (EMailDisplay *display,
                              EMFormatHTML *formatter)
{
	g_return_if_fail (E_IS_MAIL_DISPLAY (display));
	g_return_if_fail (EM_IS_FORMAT_HTML (formatter));

	if (display->priv->formatter != NULL)
		g_object_unref (display->priv->formatter);

	display->priv->formatter = g_object_ref (formatter);

	g_object_notify (G_OBJECT (display), "formatter");
}