/*
 *  Copyright (C) 2000-2004 Marco Pesenti Gritti
 *  Copyright (C) 2003, 2004 Xan Lopez
 *
 *  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.
 *
 *  $Id$
 */

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

#include "downloader-view.h"
#include "ephy-file-helpers.h"
#include "ephy-embed-shell.h"
#include "ephy-stock-icons.h"

#include <libgnomevfs/gnome-vfs-utils.h>
#include <eggstatusicon.h>
#include <eggtraymanager.h>
#include <gtk/gtktreeview.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtkbutton.h>
#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkcellrendererpixbuf.h>
#include <gtk/gtkcellrendererprogress.h>
#include <gtk/gtktreeselection.h>
#include <gtk/gtktreeviewcolumn.h>
#include <gtk/gtkicontheme.h>
#include <gtk/gtkiconfactory.h>
#include <libgnomeui/gnome-icon-lookup.h>
#include <glib/gi18n.h>

#define CONF_DOWNLOADING_SHOW_DETAILS "/apps/epiphany/dialogs/downloader_show_details"

enum
{
	COL_STATE,
	COL_PERCENT,
	COL_IMAGE,
	COL_FILE,
	COL_REMAINING,
	COL_DOWNLOAD_OBJECT
};

enum
{
	PROGRESS_COL_POS,
	FILE_COL_POS,
	REMAINING_COL_POS
};

#define EPHY_DOWNLOADER_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_DOWNLOADER_VIEW, DownloaderViewPrivate))

struct DownloaderViewPrivate
{
	GtkTreeModel *model;
	GHashTable *downloads_hash;

	/* Widgets */
	GtkWidget *window;
	GtkWidget *treeview;
	GtkWidget *pause_button;
	GtkWidget *abort_button;

	EggStatusIcon *status_icon;

	long remaining_secs;
};

typedef struct
{
	gboolean is_paused;
	DownloaderViewPrivate *priv;
} ControlsInfo;

enum
{
	PROP_WINDOW,
	PROP_TREEVIEW,
	PROP_PAUSE_BUTTON,
	PROP_ABORT_BUTTON,
};

static const
EphyDialogProperty properties [] =
{
	{ "download_manager_dialog",	NULL, PT_NORMAL, 0 },
	{ "clist",			NULL, PT_NORMAL, 0 },
	{ "pause_button",		NULL, PT_NORMAL, 0 },
	{ "abort_button",		NULL, PT_NORMAL, 0 },

	{ NULL }
};

static void
downloader_view_build_ui (DownloaderView *dv);
static void
downloader_view_class_init (DownloaderViewClass *klass);
static void
downloader_view_finalize (GObject *object);
static void
downloader_view_init (DownloaderView *dv);

/* Callbacks */
void
download_dialog_pause_cb (GtkButton *button, DownloaderView *dv);
void
download_dialog_abort_cb (GtkButton *button, DownloaderView *dv);
gboolean
download_dialog_delete_cb (GtkWidget *window, GdkEventAny *event,
			   DownloaderView *dv);

static GObjectClass *parent_class = NULL;

GType
downloader_view_get_type (void)
{
       static GType downloader_view_type = 0;

	if (downloader_view_type == 0)
	{
		static const GTypeInfo our_info =
		{
			sizeof (DownloaderViewClass),
			NULL, /* base_init */
			NULL, /* base_finalize */
			(GClassInitFunc) downloader_view_class_init,
			NULL, /* class_finalize */
			NULL, /* class_data */
			sizeof (DownloaderView),
			0,    /* n_preallocs */
			(GInstanceInitFunc) downloader_view_init
		};

		downloader_view_type = g_type_register_static (EPHY_TYPE_DIALOG,
							       "DownloaderView",
							       &our_info, 0);
	}

	return downloader_view_type;
}

static void
downloader_view_class_init (DownloaderViewClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);

	object_class->finalize = downloader_view_finalize;

	g_type_class_add_private (object_class, sizeof(DownloaderViewPrivate));
}

static void
status_icon_activated (EggStatusIcon *icon, DownloaderView *dv)
{
	gtk_window_present (GTK_WINDOW (dv->priv->window));
}

static void
show_status_icon (DownloaderView *dv)
{
	GdkPixbuf *pixbuf;

	pixbuf = gdk_pixbuf_new_from_file (ephy_file ("epiphany-download.png"), NULL);
	dv->priv->status_icon = egg_status_icon_new_from_pixbuf (pixbuf);
	g_object_unref (pixbuf);

	g_signal_connect (dv->priv->status_icon, "activate",
			  G_CALLBACK (status_icon_activated), dv);
}

static void
downloader_view_init (DownloaderView *dv)
{
	dv->priv = EPHY_DOWNLOADER_VIEW_GET_PRIVATE (dv);

	dv->priv->downloads_hash = g_hash_table_new_full
		(g_direct_hash, g_direct_equal, NULL,
		 (GDestroyNotify)gtk_tree_row_reference_free);

	downloader_view_build_ui (dv);

	show_status_icon (dv);

	g_object_ref (embed_shell);
}

static void
downloader_view_finalize (GObject *object)
{
	DownloaderView *dv = EPHY_DOWNLOADER_VIEW (object);

	g_object_unref (dv->priv->status_icon);
	g_hash_table_destroy (dv->priv->downloads_hash);
	g_object_unref (embed_shell);

	G_OBJECT_CLASS (parent_class)->finalize (object);
}

DownloaderView *
downloader_view_new (void)
{
	return EPHY_DOWNLOADER_VIEW (g_object_new (EPHY_TYPE_DOWNLOADER_VIEW,
			 			   "persist-position", TRUE,
						   "default-width", 420,
						   "default-height", 250,
			 			   NULL));
}

static char *
format_interval (long interval)
{
	int secs, hours, mins;														     
	secs = (int)(interval + .5);
	hours = secs / 3600;
	secs -= hours * 3600;
	mins = secs / 60;
	secs -= mins * 60;

	if (hours)
	{
		return g_strdup_printf (_("%u:%02u.%02u"), hours, mins, secs);
	}
	else
	{
		return g_strdup_printf (_("%02u.%02u"), mins, secs);
	}
}

static GtkTreeRowReference *
get_row_from_download (DownloaderView *dv, EphyDownload *download)
{
	return  g_hash_table_lookup (dv->priv->downloads_hash, download);
}

static void
update_buttons (DownloaderView *dv)
{
	GtkTreeSelection *selection;
	GtkTreeModel *model;
	GtkTreeIter iter;
	GValue val = {0, };
	EphyDownload *download;
	EphyDownloadState state;
	gboolean pause_enabled = FALSE;
	gboolean abort_enabled = FALSE;
	gboolean label_pause = TRUE;

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(dv->priv->treeview));

	if (gtk_tree_selection_get_selected (selection, &model, &iter))
	{
		gtk_tree_model_get_value (model, &iter, COL_DOWNLOAD_OBJECT, &val);
		download = g_value_get_object (&val);
		g_value_unset (&val);

		state = ephy_download_get_state (download);

		switch (state)
		{
		case EPHY_DOWNLOAD_DOWNLOADING:
			pause_enabled = TRUE;
			abort_enabled = TRUE;
			break;
		case EPHY_DOWNLOAD_PAUSED:
			pause_enabled = TRUE;
			abort_enabled = TRUE;
			label_pause = FALSE;
			break;
		default:
			abort_enabled = TRUE;
			break;
		}
	}
	gtk_widget_set_sensitive (dv->priv->pause_button, pause_enabled);
	gtk_widget_set_sensitive (dv->priv->abort_button, abort_enabled);
	gtk_button_set_label (GTK_BUTTON (dv->priv->pause_button), label_pause ? _("_Pause") : _("_Resume"));
}

static void
update_download_row (DownloaderView *dv, EphyDownload *download)
{
	GtkTreeRowReference *row_ref;
	GtkTreePath *path;
	GtkTreeIter iter;
	EphyDownloadState state;
	long total, current, remaining_secs = 0;
	char *remaining, *file, *cur_progress, *name;
	struct tm;
	int percent = 0;

	row_ref = get_row_from_download (dv, download);
	g_return_if_fail (row_ref != NULL);

	/* State special casing */
	state = ephy_download_get_state (download);
	switch (state)
	{
	case EPHY_DOWNLOAD_COMPLETED:
		downloader_view_remove_download (dv, download);
		return;
	case EPHY_DOWNLOAD_PAUSED:
	case EPHY_DOWNLOAD_DOWNLOADING:
		percent = ephy_download_get_percent (download);
		remaining_secs = ephy_download_get_remaining_time (download);
		break;
	default:
		break;
	}

	total = ephy_download_get_total_progress (download);
	current = ephy_download_get_current_progress (download);

	cur_progress = gnome_vfs_format_file_size_for_display (current);

	name = ephy_download_get_name (download);

	if (total != -1)
	{
		char *total_progress;

		total_progress = gnome_vfs_format_file_size_for_display (total);
		file = g_strdup_printf ("%s\n%s of %s", name,
					cur_progress, total_progress);
		g_free (total_progress);
	}
	else
	{
		file = g_strdup_printf ("%s\n%s", name, cur_progress);
	}

	if (remaining_secs < 0)
	{
		remaining = g_strdup (_("Unknown"));
	}
	else
	{
		remaining = format_interval (remaining_secs);
	}

	path = gtk_tree_row_reference_get_path (row_ref);
	gtk_tree_model_get_iter (dv->priv->model, &iter, path);
	gtk_list_store_set (GTK_LIST_STORE (dv->priv->model),
			    &iter,
			    COL_STATE, state,
			    COL_PERCENT, percent,
			    COL_FILE, file,
			    COL_REMAINING, remaining,
			    -1);
	gtk_tree_path_free (path);

	g_free (name);
	g_free (cur_progress);
	g_free (file);
	g_free (remaining);

	update_buttons (dv);
}

static void
seconds_remaining_total (EphyDownload *download, gpointer data, DownloaderView *dv)
{
	long secs;

	secs = ephy_download_get_remaining_time (download);
	if (secs > 0)
	{
		dv->priv->remaining_secs += secs;
	}
}

static void
update_status_icon (DownloaderView *dv)
{
	char *tooltip, *downloadstring, *remainingstring;
	int downloads, remaining;

	dv->priv->remaining_secs = 0;
	g_hash_table_foreach (dv->priv->downloads_hash,
			      (GHFunc) seconds_remaining_total, dv);
	
	remaining = (dv->priv->remaining_secs);

	if (remaining < 60)
	{
		remainingstring = g_strdup_printf (ngettext ("About %d second left",
					"About %d seconds left", remaining),
					remaining);
	}
	else
	{
		remaining /= 60;
		
		remainingstring = g_strdup_printf (ngettext ("About %d minute left",
					"About %d minutes left", remaining),
					remaining);
	}

	downloads = g_hash_table_size (dv->priv->downloads_hash);

	downloadstring = g_strdup_printf (ngettext ("%d download", 
					"%d downloads", downloads), 
					downloads);

	tooltip = g_strdup_printf ("%s\n%s",
					downloadstring, remainingstring);

	egg_status_icon_set_tooltip (dv->priv->status_icon,
				     tooltip, NULL);

	g_free (tooltip);
	g_free (downloadstring);
	g_free (remainingstring);
}

static void
download_changed_cb (EphyDownload *download, DownloaderView *dv)
{
	update_download_row (dv, download);
	update_status_icon (dv);
}

void
downloader_view_add_download (DownloaderView *dv,
			      EphyDownload *download)
{
	GtkTreeRowReference *row_ref;
	GtkTreeIter iter;
	GtkTreeSelection *selection;
	GtkTreePath *path;
	GtkIconTheme *theme;
	GtkIconInfo *icon_info;
	GdkPixbuf *pixbuf;
	char *mime, *icon_name;
	int width = 16, height = 16;

	g_object_ref (download);

	gtk_list_store_append (GTK_LIST_STORE (dv->priv->model),
			       &iter);
	gtk_list_store_set (GTK_LIST_STORE (dv->priv->model),
			    &iter, COL_DOWNLOAD_OBJECT, download, -1);

	path =  gtk_tree_model_get_path (GTK_TREE_MODEL (dv->priv->model), &iter);
	row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (dv->priv->model), path);
	gtk_tree_path_free (path);

	g_hash_table_insert (dv->priv->downloads_hash,
			     download,
			     row_ref);

	update_download_row (dv, download);
	update_status_icon (dv);

	selection = gtk_tree_view_get_selection
		(GTK_TREE_VIEW(dv->priv->treeview));
	gtk_tree_selection_unselect_all (selection);
	gtk_tree_selection_select_iter (selection, &iter);

	g_signal_connect_object (download, "changed",
				 G_CALLBACK (download_changed_cb), dv, 0);

	/* Show it already */
	ephy_dialog_show (EPHY_DIALOG (dv));
	
	mime =  ephy_download_get_mime (download);

	theme = gtk_icon_theme_get_default ();
	icon_name = gnome_icon_lookup (theme, NULL, NULL, NULL, NULL,
				       mime, GNOME_ICON_LOOKUP_FLAGS_NONE, NULL);
	g_free (mime);
	
	gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
	width *= 2;

	icon_info = gtk_icon_theme_lookup_icon (theme, icon_name, width, 0);
	g_free (icon_name);
	if (icon_info == NULL) return;

	pixbuf = gdk_pixbuf_new_from_file_at_size
		(gtk_icon_info_get_filename (icon_info), width, width, NULL);
	gtk_icon_info_free (icon_info);

	gtk_list_store_set (GTK_LIST_STORE (dv->priv->model),
			    &iter, COL_IMAGE, pixbuf, -1);
	if (pixbuf != NULL)
	{
		g_object_unref (pixbuf);
	}

}

static void
selection_changed (GtkTreeSelection *selection, DownloaderView *dv)
{
	update_buttons (dv);
}

static void
progress_cell_data_func (GtkTreeViewColumn *col,
			 GtkCellRenderer   *renderer,
			 GtkTreeModel      *model,
			 GtkTreeIter       *iter,
			 gpointer           user_data)
{
	EphyDownloadState state;
	const char *text = NULL;
	int percent;

	gtk_tree_model_get (model, iter,
			    COL_STATE, &state,
			    COL_PERCENT, &percent,
			    -1);

	switch (state)
	{
		case EPHY_DOWNLOAD_INITIALISING:
			text = Q_("download status|Unknown");
			break;
		case EPHY_DOWNLOAD_FAILED:
			text = Q_("download status|Failed");
			break;
		case EPHY_DOWNLOAD_DOWNLOADING:
		case EPHY_DOWNLOAD_PAUSED:
			if (percent == -1)
			{
				text = Q_("download status|Unknown");
				percent = 0;
			}
			break;
		default:
			g_return_if_reached ();
	}

	g_object_set (renderer, "text", text, "value", percent, NULL);
}

static void
downloader_view_build_ui (DownloaderView *dv)
{
	DownloaderViewPrivate *priv = dv->priv;
	GtkListStore *liststore;
	GtkTreeViewColumn *column;
	GtkCellRenderer *renderer;
	EphyDialog *d = EPHY_DIALOG (dv);
	GtkTreeSelection *selection;

	ephy_dialog_construct (d,
			       properties,
			       ephy_file ("epiphany.glade"),
			       "download_manager_dialog",
			       NULL);

	/* lookup needed widgets */
	priv->window = ephy_dialog_get_control(d, properties[PROP_WINDOW].id);
	priv->treeview = ephy_dialog_get_control (d, properties[PROP_TREEVIEW].id);
	priv->pause_button = ephy_dialog_get_control (d, properties[PROP_PAUSE_BUTTON].id);
	priv->abort_button = ephy_dialog_get_control (d, properties[PROP_ABORT_BUTTON].id);

	gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview)),
				     GTK_SELECTION_BROWSE);

	liststore = gtk_list_store_new (6,
					G_TYPE_INT,
					G_TYPE_INT,
					GDK_TYPE_PIXBUF,
					G_TYPE_STRING,
					G_TYPE_STRING,
					G_TYPE_OBJECT);

	gtk_tree_view_set_model (GTK_TREE_VIEW(priv->treeview),
				 GTK_TREE_MODEL (liststore));
	g_object_unref (liststore);
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(priv->treeview),
					   TRUE);
	/* Icon and filename column*/
	column = gtk_tree_view_column_new ();
	gtk_tree_view_column_set_title (column, _("File"));
	renderer = gtk_cell_renderer_pixbuf_new ();
	g_object_set (renderer, "xpad", 3, NULL);
	gtk_tree_view_column_pack_start (column, renderer, FALSE);
	gtk_tree_view_column_set_attributes (column, renderer,
					    "pixbuf", COL_IMAGE,
					    NULL);
	renderer = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (column, renderer, TRUE);
	gtk_tree_view_column_set_attributes (column, renderer,
					     "text", COL_FILE,
					     NULL);
	gtk_tree_view_insert_column (GTK_TREE_VIEW (priv->treeview), column, FILE_COL_POS);
	gtk_tree_view_column_set_expand (column, TRUE);
	gtk_tree_view_column_set_resizable (column, TRUE);
	gtk_tree_view_column_set_sort_column_id (column, COL_FILE);
	gtk_tree_view_column_set_spacing (column, 3);

	/* Progress column */
	renderer = gtk_cell_renderer_progress_new ();
	g_object_set (renderer, "xalign", 0.5, NULL);
	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(priv->treeview),
						     PROGRESS_COL_POS, _("%"),
						     renderer,
						     NULL);
	column = gtk_tree_view_get_column (GTK_TREE_VIEW(priv->treeview), PROGRESS_COL_POS);
	gtk_tree_view_column_set_cell_data_func(column, renderer, progress_cell_data_func, NULL, NULL);
	gtk_tree_view_column_set_sort_column_id (column, COL_PERCENT);

	/* Remainng time column */
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (renderer, "xalign", 0.5, NULL);
	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(priv->treeview),
						     REMAINING_COL_POS, _("Remaining"),
						     renderer,
						     "text", COL_REMAINING,
						     NULL);

	column = gtk_tree_view_get_column (GTK_TREE_VIEW(priv->treeview),
					   REMAINING_COL_POS);
	gtk_tree_view_column_set_sort_column_id (column, COL_REMAINING);

	priv->model = GTK_TREE_MODEL (liststore);

	gtk_window_set_icon_name (GTK_WINDOW (priv->window), EPHY_STOCK_DOWNLOAD);

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
	g_signal_connect (selection, "changed", G_CALLBACK (selection_changed), dv);
}

void
download_dialog_pause_cb (GtkButton *button, DownloaderView *dv)
{
	GtkTreeSelection *selection;
	GtkTreeModel *model;
	GtkTreeIter iter;
	GValue val = {0, };
	EphyDownload *download;
	EphyDownloadState state;

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(dv->priv->treeview));

	if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return;

	gtk_tree_model_get_value (model, &iter, COL_DOWNLOAD_OBJECT, &val);
	download = g_value_get_object (&val);

	state = ephy_download_get_state (download);

	if (state == EPHY_DOWNLOAD_DOWNLOADING)
	{
		ephy_download_pause (download);
	}
	else if (state == EPHY_DOWNLOAD_PAUSED)
	{
		ephy_download_resume (download);
	}

	g_value_unset (&val);

	update_buttons (dv);
}

void
downloader_view_remove_download (DownloaderView *dv, EphyDownload *download)
{
	GtkTreeRowReference *row_ref;
	GtkTreePath *path = NULL;
	GtkTreeIter iter, iter2;

	row_ref = get_row_from_download (dv, download);
	g_return_if_fail (row_ref);

	/* Get the row we'll select after removal ("smart" selection) */
	
	path = gtk_tree_row_reference_get_path (row_ref);
	gtk_tree_model_get_iter (GTK_TREE_MODEL (dv->priv->model),
				 &iter, path);
	gtk_tree_path_free (path);

	row_ref = NULL;
	iter2 = iter;			 
	if (gtk_tree_model_iter_next (GTK_TREE_MODEL (dv->priv->model), &iter))
	{
		path = gtk_tree_model_get_path  (GTK_TREE_MODEL (dv->priv->model), &iter);
		row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (dv->priv->model), path);
	}
	else
	{
		path = gtk_tree_model_get_path (GTK_TREE_MODEL (dv->priv->model), &iter2);
		if (gtk_tree_path_prev (path))
		{
			row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (dv->priv->model),
							      path);
		}
	}
	gtk_tree_path_free (path);

	/* Removal */
	
	gtk_list_store_remove (GTK_LIST_STORE (dv->priv->model), &iter2);
	g_hash_table_remove (dv->priv->downloads_hash,
			     download);
	g_object_unref (download);

	/* Actual selection */

	if (row_ref != NULL)
	{
		path = gtk_tree_row_reference_get_path (row_ref);
		if (path != NULL)
		{
			gtk_tree_view_set_cursor (GTK_TREE_VIEW (dv->priv->treeview),
						  path, NULL, FALSE);
			gtk_tree_path_free (path);
		}
		gtk_tree_row_reference_free (row_ref);
	}

	update_status_icon (dv);

	/* Close the dialog if there are no more downloads */

	if (!g_hash_table_size (dv->priv->downloads_hash))
		g_object_unref (G_OBJECT (dv));
}

void
download_dialog_abort_cb (GtkButton *button, DownloaderView *dv)
{
	GValue val = {0, };
	GtkTreeSelection *selection;
	GtkTreeIter iter;
	GtkTreeModel *model;
	gpointer download;

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(dv->priv->treeview));
	
	if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return;

	gtk_tree_model_get_value (model, &iter, COL_DOWNLOAD_OBJECT, &val);
	
	download = g_value_get_object (&val);
	g_return_if_fail (download != NULL);

	ephy_download_cancel ((EphyDownload*)download);
	downloader_view_remove_download (dv, download);

	g_value_unset (&val);
}

gboolean
download_dialog_delete_cb (GtkWidget *window, GdkEventAny *event,
			   DownloaderView *dv)
{
	if (egg_tray_manager_check_running (gdk_screen_get_default ()))
	{
		gtk_widget_hide (dv->priv->window);
	}

	return TRUE;
}