/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2004  Hidetaka Iwai
 *
 *  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.
 */

/*
 * The original code is gtk2/bbs_thread_ui.c in ochusha-0.5.7
 * Copyright (c) 2003-2004 The Ochusha Project.
 */

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

#include <string.h>

#include "kazehakase.h"
#include "kz-popup-preview.h"
#include "kz-downloader.h"
#include "kz-profile.h"
#include "kz-thumbnail.h"
#include "glib-utils.h"
#include "utils.h"
#include "eggmd5.h"
#include "egg-pixbuf-thumbnail.h"

typedef struct _KzPopupPreviewPriv KzPopupPreviewPriv;
struct _KzPopupPreviewPriv
{
	GtkWidget *popup_window;
	GtkContainer *popup_frame;
	GtkWidget *image;

	gchar *uri;
	guint delay_id;
	guint close_delay_id;
	int x;
	int y;

	gboolean now_shown;
	gboolean now_pointed;
};

#define KZ_POPUP_PREVIEW_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), KZ_TYPE_POPUP_PREVIEW, KzPopupPreviewPriv))

#define DEFAULT_WIDTH 160
#define DEFAULT_HEIGHT 120
#define X_OFFSET 10
#define Y_OFFSET 10
#define DEFAULT_POPUP_RESPONSE_DELAY 100
#define DEFAULT_POPUP_CLOSE_DELAY 100

static void       dispose                (GObject *object);

static gboolean   cb_enter_notify_event                   (GtkWidget *widget,
							   GdkEventCrossing *event,
							   KzPopupPreview* popup);
static gboolean   cb_leave_notify_event                   (GtkWidget *widget,
							   GdkEventCrossing *event,
							   KzPopupPreview *popup);
static gboolean   cb_button_press                         (GtkWidget *widget,
							   GdkEventButton *event,
							   KzPopupPreview *popup);

static void       kz_popup_preview_setup_popup            (KzPopupPreview* popup, 
							   GtkWidget* image);
static void       kz_popup_preview_show_popup             (KzPopupPreview* popup);
static void       kz_popup_preview_show_popup_real        (KzPopupPreview* popup);
static gboolean   cb_delay_timeout                        (KzPopupPreview* popup);
static void       kz_popup_preview_hide_popup             (KzPopupPreview* popup);
static void       kz_popup_preview_hide_popup_real        (KzPopupPreview *popup);
static gboolean   cb_close_delay_timeout                  (KzPopupPreview *popup);

static GtkWidget* kz_popup_preview_get_image              (KzPopupPreview *popup,
							   const gchar *uri);
static GtkWidget* kz_popup_preview_get_thumbnail          (KzPopupPreview *popup,
							   const gchar *uri);

static void       kz_downloader_disconnect_signal         (KzDownloader *dl,
							   const gchar *uri);
static gboolean   idle_unref_dl                           (gpointer data);
static void       cb_downloader_load_complete             (KzDownloader *dl,
							   const gchar *uri);
static void       cb_downloader_load_error                (KzDownloader *dl,
							   const gchar *uri);

static GdkPixbuf* gdk_pixbuf_scale_keeping_aspect_ratio   (const GdkPixbuf *src,
							   int              max_width,
							   int              max_height,
							   GdkInterpType    interp_type);

static KzPopupPreview      *kz_popup_preview_single = NULL;

G_DEFINE_TYPE(KzPopupPreview, kz_popup_preview, G_TYPE_OBJECT)

static void
kz_popup_preview_class_init (KzPopupPreviewClass *klass)
{
	GObjectClass *object_class;

	object_class = (GObjectClass *) klass;

	object_class->dispose = dispose;

	g_type_class_add_private (klass, sizeof(KzPopupPreviewPriv));
}

static void
kz_popup_preview_init (KzPopupPreview *popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);
	GtkWidget *frame;

	priv->popup_window = gtk_window_new(GTK_WINDOW_POPUP);

	frame = gtk_frame_new(NULL);
	gtk_widget_show(frame);
	priv->popup_frame = GTK_CONTAINER(frame);
	gtk_container_add(GTK_CONTAINER(priv->popup_window), frame);

	priv->image = NULL;

	priv->uri = NULL;
	priv->delay_id = 0;
	priv->close_delay_id = 0;
	priv->x = 0;
	priv->y = 0;

	priv->now_shown = FALSE;
	priv->now_pointed = FALSE;
}

static void
dispose (GObject *object)
{
	KzPopupPreview *popup = KZ_POPUP_PREVIEW (object);
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);

	if (priv->uri != NULL)
	{
		g_free(priv->uri);
		priv->uri = NULL;
	}

	if (priv->delay_id != 0)
	{
		g_source_remove(priv->delay_id);
		priv->delay_id = 0;
	}
	
	if (priv->close_delay_id != 0)
	{
		g_source_remove(priv->close_delay_id);
		priv->close_delay_id = 0;
	}

	if (priv->popup_window != NULL)
	{
		gtk_widget_destroy(priv->popup_window);
		priv->popup_window = NULL;
	}

	if (G_OBJECT_CLASS (kz_popup_preview_parent_class)->dispose)
		G_OBJECT_CLASS (kz_popup_preview_parent_class)->dispose(object);
}

KzPopupPreview *
kz_popup_preview_new (void)
{
	KzPopupPreview *popup = g_object_new (KZ_TYPE_POPUP_PREVIEW, NULL);
	return popup;
}

KzPopupPreview *
kz_popup_preview_get_instance (void)
{
	if (!kz_popup_preview_single)
		kz_popup_preview_single = kz_popup_preview_new();
	else
		g_object_ref(kz_popup_preview_single);

	return kz_popup_preview_single;
}


static gboolean
cb_enter_notify_event(GtkWidget *widget,
		      GdkEventCrossing *event,
		      KzPopupPreview* popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);
	
	if (priv->close_delay_id != 0)
	{
		g_source_remove(priv->close_delay_id);
		priv->close_delay_id = 0;
	}
	
	priv->now_pointed = TRUE;
	
	return FALSE;
}

static gboolean
cb_leave_notify_event(GtkWidget *widget,
		      GdkEventCrossing *event,
		      KzPopupPreview *popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);

	priv->now_pointed = FALSE;
	kz_popup_preview_hide_popup(popup);
	return FALSE;
}

static gboolean
cb_button_press (GtkWidget *widget,
		 GdkEventButton *event,
		 KzPopupPreview *popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);

	priv->now_pointed = FALSE;
	kz_popup_preview_hide_popup(popup);
	return FALSE;
}

void
kz_popup_preview_reset(KzPopupPreview *popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);

	if(priv->popup_window)
		gtk_widget_hide_all(priv->popup_window);

	if (priv->close_delay_id != 0)
	{
		g_source_remove(priv->close_delay_id);
		priv->close_delay_id = 0;
	}

	if (priv->delay_id != 0)
	{
		g_source_remove(priv->delay_id);
		priv->delay_id = 0;
	}

	if (priv->uri != NULL)
	{
		g_free(priv->uri);
		priv->uri = NULL;
	}

	priv->x = 0;
	priv->y = 0;

	if(priv->now_shown)
	{
		priv->now_shown = FALSE;
	}
	priv->now_pointed = FALSE;
}

void
kz_popup_preview_start(KzPopupPreview *popup, const gchar* uri,
		       const gchar* img, gint x, gint y)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);
	GtkWidget *image = NULL;

	priv->x = x;
	priv->y = y;
	if (priv->uri != NULL)
		g_free(priv->uri);
	priv->uri = g_strdup(uri);

	if (g_str_has_suffix(uri, ".jpg") || g_str_has_suffix(uri, ".png")
	    || g_str_has_suffix(uri, ".gif") || g_str_has_suffix(uri, ".jpeg")
	    || g_str_has_suffix(uri, ".JPG") || g_str_has_suffix(uri, ".PNG")
	    || g_str_has_suffix(uri, ".GIF") || g_str_has_suffix(uri, ".JPEG"))
	{
		if (!img) image = kz_popup_preview_get_image(popup, uri);
	}
	else
	{
		image = kz_popup_preview_get_thumbnail(popup, uri);
	}

	if (image != NULL)
	{
		kz_popup_preview_setup_popup(popup, image);
		kz_popup_preview_show_popup(popup);
	}
}

static void
kz_popup_preview_setup_popup(KzPopupPreview* popup, GtkWidget* image)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);
	GtkWidget *event_box;

	if(!image) 
	{
		if (priv->image != NULL)
			gtk_container_remove(priv->popup_frame,
					     priv->image);
		priv->image = NULL;
		return;
	}

	gtk_widget_show(image);

	event_box = gtk_event_box_new();
	gtk_container_add(GTK_CONTAINER(event_box), image);
	gtk_widget_show(event_box);

	g_signal_connect(event_box, "enter_notify_event",
			 G_CALLBACK(cb_enter_notify_event),
			 popup);
	g_signal_connect(event_box, "leave_notify_event",
			 G_CALLBACK(cb_leave_notify_event),
			 popup);

	g_signal_connect(priv->popup_window, "button-press-event",
			 G_CALLBACK(cb_button_press), popup);
	
	if (priv->image != NULL)
		gtk_container_remove(priv->popup_frame,
				     priv->image);
	gtk_container_add(priv->popup_frame, event_box);
	gtk_widget_show(GTK_WIDGET(priv->popup_frame));
	priv->image = event_box;

	return;
}

static void
kz_popup_preview_show_popup(KzPopupPreview* popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);
	gint popup_response_delay = DEFAULT_POPUP_RESPONSE_DELAY;

        /* if priv->uri == NULL, popup might be resetted by user*/
	if(!priv->uri || !priv->image) return;

	KZ_CONF_GET("Popup", "response_delay",  popup_response_delay, INT);
	if (popup_response_delay == 0)
		kz_popup_preview_show_popup_real(popup);
	else
	{
		if (priv->close_delay_id != 0)
		{
			g_source_remove(priv->close_delay_id);
			priv->close_delay_id = 0;
		}
		
		if (priv->delay_id != 0)
			g_source_remove(priv->delay_id);

		priv->delay_id
			= g_timeout_add(popup_response_delay,
					(GSourceFunc)cb_delay_timeout, popup);
	}

}
static void
kz_popup_preview_show_popup_real(KzPopupPreview* popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);

	if (priv->now_shown)
		return;

	if (priv->image != NULL)
	{
		gtk_window_move(GTK_WINDOW(priv->popup_window), priv->x + X_OFFSET, priv->y + Y_OFFSET);
		gtk_widget_show_all(priv->popup_window);
		priv->now_shown = TRUE;
	}
}

static gboolean
cb_delay_timeout(KzPopupPreview* popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);
	gboolean result = TRUE;

        /* if priv->uri == NULL, popup might be resetted by user*/
	if (priv->uri != NULL)
	{
		if (priv->delay_id != 0)
			priv->delay_id = 0;

		g_free(priv->uri);
		priv->uri = NULL;

		kz_popup_preview_show_popup_real(popup);
		result = FALSE;
	}

	return result;
}

static void
kz_popup_preview_hide_popup(KzPopupPreview* popup)
{
	gint popup_close_delay = DEFAULT_POPUP_CLOSE_DELAY;
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);

	KZ_CONF_GET("Popup", "close_delay",  popup_close_delay, INT);

	if (popup_close_delay == 0)
		kz_popup_preview_hide_popup_real(popup);
	else if (priv->delay_id == 0)
	{
		if (priv->close_delay_id != 0)
			g_source_remove(priv->close_delay_id); /* reset waiting time */
		priv->close_delay_id
			= g_timeout_add(popup_close_delay,
					(GSourceFunc)cb_close_delay_timeout, popup);
	}
	else
	{
        /* reset waiting popup */
		kz_popup_preview_hide_popup_real(popup);
	}
}

static void
kz_popup_preview_hide_popup_real(KzPopupPreview *popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);

	if (priv->uri != NULL)
	{
		g_free(priv->uri);
		priv->uri = NULL;
	}

	if (priv->delay_id != 0)
	{
		g_source_remove(priv->delay_id);
		priv->delay_id = 0;
	}

	gtk_widget_hide_all(priv->popup_window);

	if (priv->image != NULL)
	{
		gtk_container_remove(priv->popup_frame,
				     priv->image);
		priv->image = NULL;
	}
	priv->now_shown = FALSE;
}

static gboolean
cb_close_delay_timeout(KzPopupPreview *popup)
{
	KzPopupPreviewPriv *priv = KZ_POPUP_PREVIEW_GET_PRIVATE (popup);

	if (!priv->now_pointed)
	{
		kz_popup_preview_hide_popup_real(popup);
	}

	priv->close_delay_id = 0;

	return FALSE;
}

static GtkWidget *
kz_popup_preview_get_image (KzPopupPreview *popup, const gchar *uri)
{
	GtkWidget *image = NULL;
	gchar *tmp_name, *image_name, *popup_file_name;

	if (uri == NULL)
		return NULL;

	tmp_name = egg_str_get_md5_str(uri);
	image_name = g_strconcat(tmp_name, ".png", NULL);
	popup_file_name = g_build_filename(g_get_home_dir(),
					   "."PACKAGE,
					   "popup",
					   image_name,
					   NULL);
	g_free(tmp_name);
	g_free(image_name);

	if (!g_file_test(popup_file_name, G_FILE_TEST_EXISTS))
	{
		KzDownloader *dl;

                /* start download */
		dl = kz_downloader_new_with_filename(uri, popup_file_name);
		if (dl)
		{
			g_signal_connect(dl, "completed",
					 G_CALLBACK(cb_downloader_load_complete),
					 (gpointer)uri);
			g_signal_connect(dl, "error",
					 G_CALLBACK(cb_downloader_load_error),
					 (gpointer)uri);
			kz_downloader_to_file(dl);
		}
	}
	else
	{
		GdkPixbuf *scaled_pixbuf, *orig_pixbuf;
		gint width = DEFAULT_WIDTH;
		gint height = DEFAULT_HEIGHT;

		KZ_CONF_GET("Popup", "width",  width, INT);
		KZ_CONF_GET("Popup", "height", height, INT);


		orig_pixbuf = gdk_pixbuf_new_from_file(popup_file_name, NULL);
		if (!orig_pixbuf) return NULL;

		scaled_pixbuf = gdk_pixbuf_scale_keeping_aspect_ratio(orig_pixbuf,
								      width, height,
								      GDK_INTERP_BILINEAR);

		image = gtk_image_new_from_pixbuf(scaled_pixbuf);

		g_object_unref(orig_pixbuf);
		g_object_unref(scaled_pixbuf);
	}
	g_free(popup_file_name);

	return image;
}

static GtkWidget *
kz_popup_preview_get_thumbnail (KzPopupPreview *popup, const gchar *uri)
{
		
	GtkWidget *image = NULL;
	GdkPixbuf *scaled_pixbuf = NULL, *orig_pixbuf = NULL;
	gchar *filename = NULL;

	if (!uri) return NULL;

	filename = egg_pixbuf_get_thumb_filename(uri,
						 EGG_PIXBUF_THUMB_LARGE);
	if(filename)
	{
		orig_pixbuf = egg_pixbuf_get_thumbnail_for_file(filename,
								EGG_PIXBUF_THUMB_NORMAL,
								NULL);
		g_free(filename);
	}

	if (orig_pixbuf)
	{
		gint width = DEFAULT_WIDTH;
		gint height = DEFAULT_HEIGHT;

		KZ_CONF_GET("Popup", "width",  width, INT);
		KZ_CONF_GET("Popup", "height", height, INT);

		scaled_pixbuf = gdk_pixbuf_scale_keeping_aspect_ratio(orig_pixbuf,
								      width, height,
								      GDK_INTERP_BILINEAR);
		g_object_unref(orig_pixbuf);
	}

	if (scaled_pixbuf)
	{
		image = gtk_image_new_from_pixbuf(scaled_pixbuf);
		g_object_unref(scaled_pixbuf);
	}

	return image;
}

static void
kz_downloader_disconnect_signal(KzDownloader *dl, const gchar *uri)
{
	g_signal_handlers_disconnect_by_func(dl,
					     G_CALLBACK(cb_downloader_load_complete),
					     (gpointer)uri);
	g_signal_handlers_disconnect_by_func(dl,
					     G_CALLBACK(cb_downloader_load_error),
					     (gpointer)uri);
}

static gboolean
idle_unref_dl(gpointer data)
{
	KzDownloader *dl = data;

	g_object_unref(G_OBJECT(dl));

	return FALSE;
}

static void
cb_downloader_load_complete (KzDownloader *dl, const gchar *uri)
{
	KzPopupPreview *popup = kz_popup_preview_single;
	GtkWidget *image = NULL;

	kz_downloader_disconnect_signal(dl, uri);
	g_idle_add(idle_unref_dl, dl);

	g_return_if_fail(popup);

	/* get_image again */
	image = kz_popup_preview_get_image(popup, uri);
	kz_popup_preview_setup_popup(popup, image);
	kz_popup_preview_show_popup(popup);

	return;
}

static void
cb_downloader_load_error (KzDownloader *dl, const gchar *uri)
{
	kz_downloader_disconnect_signal(dl, uri);
	g_idle_add(idle_unref_dl, dl);

	return;
}

/* The original code is gdk-pixbuf-scale.c in gtk+-2.4.13 */
static GdkPixbuf *
gdk_pixbuf_scale_keeping_aspect_ratio (const GdkPixbuf *src,
				       int              max_width,
				       int              max_height,
				       GdkInterpType    interp_type)
{
	GdkPixbuf *dest;
	gdouble ratio;
	int src_width, src_height;
	int dest_width, dest_height;

	g_return_val_if_fail (src != NULL, NULL);

	src_width = gdk_pixbuf_get_width(src);
	src_height = gdk_pixbuf_get_height(src);

	ratio = ((double)max_width / (double)src_width > (double)max_height / (double)src_height) ?
		(double)max_height / (double)src_height : (double)max_width / (double)src_width;
	dest_width = (int)(src_width * ratio);
	dest_height = (int)(src_height * ratio);

	dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha(src), 8, dest_width, dest_height);
	if (!dest)
		return NULL;

	gdk_pixbuf_scale (src, dest,  0, 0, 
			  dest_width,
			  dest_height,
			  0, 0,
			  ratio,
			  ratio,
			  interp_type);

	return dest;
}
