/***************************************************************************
                  uploadstats.c  -  upload statistics
                  -----------------------------------
    begin                : Sat Feb 8 2003
    copyright            : (C) 2003 by Tim-Philipp Müller
    email                : t.i.m@orange.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "core-conn.h"
#include "global.h"
#include "options.h"
#include "icons.h"
#include "mainwindow.h"
#include "misc.h"
#include "misc_gtk.h"
#include "misc_strings.h"
#include "shared_files.h"
#include "uploadstats.h"

#include "stats.h"
#include "statusbar.h"
#include "status_page.h"

#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <gtk/gtkhbox.h>
#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtktreemodelsort.h>
#include <gtk/gtkvbox.h>

enum
{
	ULSTATS_FILENAME_COLUMN = 0,
	ULSTATS_FILEHASH_COLUMN,
	ULSTATS_FILESIZE_COLUMN,
	ULSTATS_UPLOADED_COLUMN,		/* amount uploaded 	in kB */
	ULSTATS_RATIO_COLUMN,
	ULSTATS_LAST_UPLOADED_COLUMN,
	ULSTATS_RATIO_STRING_COLUMN,
	ULSTATS_UPLOADED_STRING_COLUMN,
	ULSTATS_LAST_UPLOADED_STRING_COLUMN,
	ULSTATS_N_COLUMNS
};

#define ULSTATS_TYPES_PLACEHOLDER \
	G_TYPE_STRING,  /* ULSTATS_FILENAME_COLUMN             */ \
	G_TYPE_POINTER, /* ULSTATS_FILEHASH_COLUMN             */ \
	G_TYPE_UINT,    /* ULSTATS_FILESIZE_COLUMN             */ \
	G_TYPE_FLOAT,   /* ULSTATS_UPLOADED_COLUMN             */ \
	G_TYPE_FLOAT,   /* ULSTATS_RATIO_COLUMN                */ \
	G_TYPE_UINT,    /* ULSTATS_LAST_UPLOADED_COLUMN time_t */ \
	G_TYPE_STRING,  /* ULSTATS_RATIO_STRING_COLUMN         */ \
	G_TYPE_STRING,  /* ULSTATS_UPLOADED_STRING_COLUMN      */ \
	G_TYPE_STRING   /* ULSTATS_LAST_UPLOADED_STRING_COLUMN */

#define NUM_VIEW_COLS 4


struct _GuiSharedFileShort
{
	guint8        hash[16];
	gchar         fn[192];   /* if it's longer - tough luck */
	guint         fnhash;    /* this is the hash of the full filename */
	guint         size;
};

typedef struct _GuiSharedFileShort GuiSharedFileShort;

static GtkListStore   *ulstats_store = NULL;
static GtkWidget      *ulstats_view = NULL;

static guint           column_widths[NUM_VIEW_COLS];

static gboolean        uploadstats_read_from_file (gpointer data);
static gboolean        uploadstats_remove_iter (GtkTreeIter *iter);
//static const gchar    *uploadstats_get_last_uploaded_string (time_t last_uploaded);


static GuiSharedFileShort *shared_files; /* NULL */  /* yeah, this costs memory, but it's modular and Axel loves that ;-) */

/* functions */

static gboolean        write_gui_uploadstats (gpointer foo);

static void            ulstats_add_or_increase_record ( const gchar  *name,
                                                        const guint8 *filehash,
                                                        gfloat        speed,
                                                        gint          timediff,
                                                        gfloat        initial_uploaded,
                                                        time_t        initial_last_uploaded,
                                                        guint         initial_filesize );


/******************************************************************************
 *
 *  upload_stats_add_or_increase
 *
 ******************************************************************************/

void
upload_stats_add_or_increase (const gchar  *filename,
                              const guint8 *filehash,
                              gfloat        speed,
                              gint          timediff)
{
	/* page disabled? */
	if (ulstats_view == NULL)
		return;

	ulstats_add_or_increase_record (filename, filehash, speed, timediff, 0.0, time(NULL), G_MAXUINT);
}


/******************************************************************************
 *
 *  atExit
 *
 ******************************************************************************/

static void
atExit (void)
{
	if (shared_files)
		g_free(shared_files);

	(void) write_gui_uploadstats(NULL);
}

/******************************************************************************
 *
 *  onSharedFiles
 *
 ******************************************************************************/

static void
onSharedFiles (GuiCoreConn   *conn,
               guint          num,
               const guint8 **hash_arr,
               const gchar  **name_arr,
               guint         *size_arr,
               const gchar  **format_arr,
               const gchar  **type_arr,
               guint         *prio_arr,
               gpointer       data)
{
	guint n;

	if (shared_files)
		g_free(shared_files);

	if (num == 0)
		return;

	shared_files = g_new(GuiSharedFileShort, num+1);

	for (n = 0; n < num; ++n)
	{
		memcpy(shared_files[n].hash, hash_arr[n], 16);
		strncpy(shared_files[n].fn, name_arr[n], sizeof(shared_files[n].fn));
		shared_files[n].fnhash = g_str_hash(name_arr[n]);
		shared_files[n].size = size_arr[n];
	}

	memset(shared_files[num].hash, 0x00, 16);
	memset(shared_files[num].fn, 0x00, sizeof(shared_files[num].fn));
	shared_files[num].fnhash = 0;
	shared_files[num].size = 0;
}


/******************************************************************************
 *
 *   onColumnResizedSaveWidths
 *
 ******************************************************************************/

static gboolean
onColumnResizedSaveWidths (gint *p_handler)
{
	gchar buf[512], num[32], i;

	if (p_handler)
		*p_handler = 0;

	buf[0] = 0x00;

	for (i = 0;  i < NUM_VIEW_COLS;  ++i)
	{
		g_snprintf(num, sizeof(num), "%u;", column_widths[(guint)i]);
		g_strlcat(buf, num, sizeof(buf));
	}

	if (buf[0] != 0x00)
	{
		buf[strlen(buf)-1] = 0x00; /* remove semicolon at the end */
	}

	opt_set_str(OPT_GUI_COLUMN_WIDTHS_UPLOAD_STATS, buf);

	return FALSE; /* Don't call again */
}

/******************************************************************************
 *
 *   onColumnResized
 *
 ******************************************************************************/

static void
onColumnResized (GObject *obj, GParamSpec *pspec, gpointer data)
{
	GtkTreeViewColumn *col;
	static gint        handler; /* 0 */
	GList             *cols;
	gint               num;

	col = GTK_TREE_VIEW_COLUMN(obj);

	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(ulstats_view));

	num = g_list_index(cols, col);

	g_list_free(cols);

	if (num < 0)
		return;

	g_return_if_fail (num < NUM_VIEW_COLS);

	column_widths[num] = gtk_tree_view_column_get_width(col);

	if (handler == 0)
		handler = g_timeout_add(1000, (GSourceFunc) onColumnResizedSaveWidths, &handler);
}

/******************************************************************************
 *
 *  onSortColumnChanged
 *
 ******************************************************************************/

static void
onSortColumnChanged (GtkTreeSortable *sortable, gpointer data)
{
	GtkSortType order;
	gint        sortid;

	if (gtk_tree_sortable_get_sort_column_id(sortable, &sortid, &order))
	{
		opt_set_int (OPT_GUI_SORT_COL_UPLOAD_STATS, sortid);
		opt_set_int (OPT_GUI_SORT_TYPE_UPLOAD_STATS, order);
	}
}

/******************************************************************************
 *
 *   restore_tree_view_column_widths
 *
 ******************************************************************************/

static void
restore_tree_view_column_widths (GtkWidget *tview)
{
	GtkTreeViewColumn *col;
	const gchar       *pos;
	GList             *cols;
	guint              n = 0;

	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(tview));

	g_assert (g_list_length(cols) == NUM_VIEW_COLS);

 	pos = opt_get_str(OPT_GUI_COLUMN_WIDTHS_UPLOAD_STATS);
	while ((pos) && *pos != 0x00 &&  n < NUM_VIEW_COLS)
	{
		column_widths[n] = (guint) atoi(pos);

		col = GTK_TREE_VIEW_COLUMN(g_list_nth_data(cols, n));

		g_signal_handlers_block_by_func(col, onColumnResized, NULL);

		gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
		gtk_tree_view_column_set_resizable(col, TRUE);
		gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(col), column_widths[n]);

		g_signal_handlers_unblock_by_func(col, onColumnResized, NULL);

		pos = strchr(pos, ';');

		if (pos)
			++pos; /* skip ';' */

		++n;
	}

	g_list_free(cols);
}



/******************************************************************************
 *
 *   ulstats_get_iter_from_filehash
 *
 ***/

static gboolean
ulstats_get_iter_from_filehash (const guint8 *hash, GtkTreeIter *iter)
{
	GtkTreeIter  tmpiter;
	gboolean     val;

	g_return_val_if_fail ( hash != NULL, FALSE);
	g_return_val_if_fail ( iter != NULL, FALSE);
	g_return_val_if_fail ( ulstats_store != NULL, FALSE);

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ulstats_store), &tmpiter);

	while (val)
	{
		guint8	*fhash = NULL;

		gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), &tmpiter,
		                     ULSTATS_FILEHASH_COLUMN, &fhash,
		                     -1);

		if ((fhash) && (memcmp(fhash, hash, 16) == 0))
		{
			*iter = tmpiter;

			return TRUE;
		}

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL(ulstats_store), &tmpiter);
	}

	return FALSE;
}


/******************************************************************************
 *
 *   cell_data_function_last_uploaded
 *
 ******************************************************************************/

static void
cell_data_function_last_uploaded (GtkTreeViewColumn *column,
                                    GtkCellRenderer *renderer,
                                    GtkTreeModel *model,
                                    GtkTreeIter *iter,
                                    gpointer data)
{
	guint         lastuploaded = 0;
	time_t        last_uploaded, timediff;
	const gchar  *utf8 = NULL;

	gtk_tree_model_get ( GTK_TREE_MODEL(model), iter, GPOINTER_TO_UINT(data), &lastuploaded, -1);

	g_return_if_fail ( lastuploaded > 0 );

	last_uploaded = (time_t) lastuploaded;
	timediff = time(NULL) - last_uploaded;

	if ( timediff < 3 )
	{
		last_uploaded = time(NULL);
		timediff = 0;
	}

	if (1==1)
	{
		struct tm		*timethen = localtime (&last_uploaded);
		static gchar	**months = NULL;

		if (!months)
		{
			months = g_strsplit ( _("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"), "|", 0);
		}

		/* 'now' ? */
		if (timediff == 0)
		{
			utf8 = UTF8_SHORT_PRINTF(_("%s %02d  %02d.%02dh  (%s)"), months[timethen->tm_mon], timethen->tm_mday,
			          timethen->tm_hour, timethen->tm_min, seconds_into_human_time(timediff));
		} else {
			utf8 = UTF8_SHORT_PRINTF(_("%s %02d  %02d.%02dh  (%s ago)"), months[timethen->tm_mon], timethen->tm_mday,
			          timethen->tm_hour, timethen->tm_min, seconds_into_human_time(timediff));
		}
	}

    g_object_set (renderer, "text", utf8, NULL);
}


/******************************************************************************
 *
 *   ultats_add_or_increase_record
 *
 *   increases the number of uploaded bytes for the upload stats
 *   record matching the given hash
 *
 ******************************************************************************/

static void
ulstats_add_or_increase_record ( const gchar *name,
                                 const guint8 *filehash,
                                 gfloat speed,
                                 gint timediff,
                                 gfloat initial_uploaded,
                                 time_t initial_last_uploaded,
                                 guint  initial_filesize )
{
	GtkTreeIter  iter;
	gfloat       uploaded, ratio, ul_add;
	guint        filesize = 0;

	g_return_if_fail ( name     != NULL);
	g_return_if_fail ( filehash != NULL);

	/* create record if it does not exist yet */
	if ( ulstats_get_iter_from_filehash (filehash, &iter) == FALSE )
	{
		const gchar *uploadedstring;
		gchar        ratiostring[32];

		gtk_list_store_append (ulstats_store, &iter);

		ratio = (initial_uploaded*1024.0) / (((gfloat)initial_filesize)+0.001);

		g_snprintf (ratiostring, sizeof(ratiostring)/sizeof(gchar), "%.1f", ratio);

		uploadedstring = UTF8_SHORT_PRINTF(_("%.1fM"), initial_uploaded/1024.0);

		gtk_list_store_set ( ulstats_store, &iter,
		                     ULSTATS_FILENAME_COLUMN, name,		/* name is already utf8 */
		                     ULSTATS_FILEHASH_COLUMN, g_memdup(filehash,16),
		                     ULSTATS_FILESIZE_COLUMN, initial_filesize,
		                     ULSTATS_UPLOADED_COLUMN, initial_uploaded,		/* amount uploaded in kB */
		                     ULSTATS_UPLOADED_STRING_COLUMN, uploadedstring,
		                     ULSTATS_RATIO_COLUMN, ratio,
		                     ULSTATS_RATIO_STRING_COLUMN, ratiostring,
		                     ULSTATS_LAST_UPLOADED_COLUMN, (guint)initial_last_uploaded,
		                     -1);
	}

	gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), &iter,
	                     ULSTATS_UPLOADED_COLUMN, &uploaded,
	                     ULSTATS_FILESIZE_COLUMN, &filesize,
	                     -1);

	ul_add = speed * (timediff*1.0);

	if ( (speed == 0.0) && (initial_uploaded > 0.0) )
	{
		uploaded = initial_uploaded;
	}

	if ( filesize == G_MAXUINT )
	{
		guint32  n, newsize = G_MAXUINT;

		for (n = 0; ((shared_files) && shared_files[n].size > 0 && newsize == G_MAXUINT);  ++n)
		{
			if (misc_hash_equal(shared_files[n].hash, filehash))
				newsize = shared_files[n].size;
		}

		if ( newsize != G_MAXUINT )
		{
			gchar  ratiostring[32];

			ratio = (uploaded*1024.0) / (((gfloat)newsize)+0.001);

			g_snprintf (ratiostring, sizeof(ratiostring)/sizeof(gchar), "%.1f", ratio);

			gtk_list_store_set ( ulstats_store, &iter,
			                     ULSTATS_FILESIZE_COLUMN, newsize,
			                     ULSTATS_RATIO_COLUMN, ratio,
			                     ULSTATS_RATIO_STRING_COLUMN, ratiostring,
			                     -1);
		}
	}

	/* more than ca. 330kB/s for one single upload? unlikely... */
	if ( ul_add > 1000.0 )
	{
		g_printerr ("GUI DEBUG: ul_add = %.1f kB in %s (speed=%f, timediff=%u)\n",
			ul_add, __FUNCTION__, speed, timediff);
	}
	else
	{
		const gchar *uploadedstring;
		gchar        ratiostring[32];

		ratio = (uploaded*1024.0) / (((gfloat)filesize)+0.001);

		g_snprintf (ratiostring, sizeof(ratiostring)/sizeof(gchar), "%.1f", ratio);

		uploadedstring = UTF8_SHORT_PRINTF(_("%.1fM"), (uploaded + ul_add)/1024.0);

		gtk_list_store_set ( ulstats_store, &iter,
		                     ULSTATS_UPLOADED_COLUMN, (uploaded + ul_add),
		                     ULSTATS_UPLOADED_STRING_COLUMN, uploadedstring,
		                     ULSTATS_RATIO_COLUMN, ratio,
		                     ULSTATS_RATIO_STRING_COLUMN, ratiostring,
		                     -1);
	}

	/* this condition is here so we can add records when we
	 *	read in the upload stats list from file without setting
	 *	all values to now...                                     */

	if ( speed > 0.0 )
	{
		gtk_list_store_set ( ulstats_store, &iter,
		                     ULSTATS_LAST_UPLOADED_COLUMN, (guint)time(NULL),
		                     -1);
	}
}




/******************************************************************************
 *
 *   ulstats_sync_ulstats_filenames_timeout
 *
 *   sync filenames from shared files list to upload stats list
 *    (in case a file got renamed)
 *
 ******************************************************************************/

static gboolean
ulstats_sync_ulstats_filenames_timeout (gpointer data)
{
	GtkTreeIter  tmpiter;
	gboolean     val;

	g_return_val_if_fail ( ulstats_store != NULL, TRUE );

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ulstats_store), &tmpiter);

	while (val)
	{
		guint8 *filehash = NULL;
		gchar  *filename = NULL;

		gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), &tmpiter,
		                     ULSTATS_FILEHASH_COLUMN, &filehash,
		                     ULSTATS_FILENAME_COLUMN, &filename,
		                     -1);

		if ((filename)&&(filehash))
		{
			gchar *sharedname = NULL;
			guint  n;

			for (n = 0; ((shared_files) && shared_files[n].size > 0 && sharedname == NULL);  ++n)
			{
				if (misc_hash_equal(shared_files[n].hash, filehash))
				{
					if (g_utf8_validate(shared_files[n].fn, -1, NULL))
					{
						sharedname = g_strdup(shared_files[n].fn);
					}
					else
					{
						sharedname = TO_UTF8(shared_files[n].fn);
						if (g_utf8_validate(sharedname, -1, NULL) == FALSE)
							g_warning("g_utf8_validate() failed in %s:%u (maybe filename "
							          "is neither in UTF-8 nor in locale charset?)\n", __FUNCTION__, __LINE__);
					}
				}
			}

			if (sharedname)
			{
				if ( strcmp(sharedname,filename) != 0 )
				{
					gtk_list_store_set (ulstats_store, &tmpiter,
		                     ULSTATS_FILENAME_COLUMN, sharedname,		/* name is already utf8 */
		                     -1);
				}
				g_free(sharedname);
			}
		}

		G_FREE(filename);

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL(ulstats_store), &tmpiter);
	}

	return TRUE; /* we want to be called again */
}


/******************************************************************************
 *
 *   ulstats_popupmenu_onRemoveItemsSmallerThan
 *
 *   The 'remove all items < X' pop-up menu option has been selected
 *
 *   'data' contains the min. size of all items NOT to remove.
 *
 ***/

static void
ulstats_popupmenu_onRemoveItemsSmallerThan (GtkWidget *widget, gpointer data)
{
	GtkTreeIter  tmpiter;
	gboolean     val;
	gfloat       maxsize = (gfloat) GPOINTER_TO_UINT(data);

	g_return_if_fail ( ulstats_store != NULL );

	/* remove all? => better ask */

	if ( GPOINTER_TO_UINT(data) == G_MAXUINT )
	{
		GtkWidget *dialog;
		gint       ret;

		dialog = gtk_message_dialog_new ( GTK_WINDOW(window),
		                                  GTK_DIALOG_DESTROY_WITH_PARENT,
		                                  GTK_MESSAGE_QUESTION,
		                                  GTK_BUTTONS_YES_NO,
		                                  UTF8_SHORT_PRINTF("%s",_("Do you REALLY want to remove all items\n"
		                                    "from the upload statistics list?")) );

		ret = gtk_dialog_run (GTK_DIALOG(dialog));

		gtk_widget_destroy(dialog);

		if ( ret != GTK_RESPONSE_YES )
			return;
	}

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ulstats_store), &tmpiter);

	while (val)
	{
		gfloat uploaded = G_MAXFLOAT;

		gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), &tmpiter,
		                     ULSTATS_UPLOADED_COLUMN, &uploaded,
		                     -1);

		if ( ( uploaded != G_MAXFLOAT )  && ( (uploaded*1024.0) < maxsize ) )
			g_idle_add ( (GSourceFunc) uploadstats_remove_iter, gtk_tree_iter_copy (&tmpiter));

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL(ulstats_store), &tmpiter);
	}
}



/******************************************************************************
 *
 *   ulstats_popupmenu_onRemoveItemsLastUploadedBefore
 *
 *   The 'remove all items last uploaded more than X days ago' pop-up menu option has been selected
 *
 *   'data' contains the min. size of all items NOT to remove.
 *
 ***/

static void
ulstats_popupmenu_onRemoveItemsLastUploadedBefore (GtkWidget *widget, gpointer data)
{
	GtkTreeIter  tmpiter;
	gboolean     val;
	time_t       maxtime = time(NULL) - (GPOINTER_TO_UINT(data) * 24 * 60 * 60);

	g_return_if_fail ( ulstats_store != NULL );

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ulstats_store), &tmpiter);

	while (val)
	{
		guint last_uploaded = G_MAXUINT;

		gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), &tmpiter,
		                     ULSTATS_LAST_UPLOADED_COLUMN, &last_uploaded,
		                     -1);

		if ( ( last_uploaded != G_MAXUINT )  && ( last_uploaded < maxtime ) )
			g_idle_add ( (GSourceFunc) uploadstats_remove_iter, gtk_tree_iter_copy (&tmpiter));

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL(ulstats_store), &tmpiter);
	}
}


/******************************************************************************
 *
 *   hash_is_shared_file
 *
 ******************************************************************************/

static gboolean
hash_is_shared_file (const guint8 *hash)
{
	guint n = 0;

	while ((shared_files) && shared_files[n].size > 0)
	{
		if (misc_hash_equal(shared_files[n].hash, hash))
			return TRUE;

		++n;
	}

	return FALSE;
}

/******************************************************************************
 *
 *   ulstats_popupmenu_onRemoveItemsNotSharedAnyLonger
 *
 ***/

static void
ulstats_popupmenu_onRemoveItemsNotSharedAnyLonger (GtkWidget *widget, gpointer data)
{
	GtkTreeIter  tmpiter;
	gboolean     val;

	g_return_if_fail ( ulstats_store != NULL );

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ulstats_store), &tmpiter);

	while (val)
	{
		guint8 *filehash = NULL;

		gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), &tmpiter,
		                     ULSTATS_FILEHASH_COLUMN, &filehash,
		                     -1);

		if ((filehash) && !hash_is_shared_file(filehash))
			g_idle_add ( (GSourceFunc) uploadstats_remove_iter, gtk_tree_iter_copy (&tmpiter));

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL(ulstats_store), &tmpiter);
	}
}


/******************************************************************************
 *
 *   ulstats_popupmenu_onResetStats
 *
 *   Reset the total uploaded/downloaded statistics
 *
 ***/

static void
ulstats_popupmenu_onResetStats (GtkWidget *widget, gpointer data)
{
	GtkWidget *dialog;
	gint       ret;

	dialog = gtk_message_dialog_new ( GTK_WINDOW(window),
	                                  GTK_DIALOG_DESTROY_WITH_PARENT,
	                                  GTK_MESSAGE_QUESTION,
	                                  GTK_BUTTONS_YES_NO,
	                                  UTF8_SHORT_PRINTF("%s",_("Do you REALLY want to set the total amount uploaded and downloaded to zero?")) );

	ret = gtk_dialog_run (GTK_DIALOG(dialog));

	gtk_widget_destroy(dialog);

	if ( ret != GTK_RESPONSE_YES )
		return;

	stats_clear_totals();

	statusbar_msg (_("okay, set total ul/dl stats to zero."));
}



/******************************************************************************
 *
 *   ulstats_popupmenu_onRemoveSelected
 *
 ***/

static void
ulstats_foreach_helper (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
	guint8  *filehash = NULL;

	g_return_if_fail ( iter  != NULL );
	g_return_if_fail ( model != NULL );

	gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), iter,
	                     ULSTATS_FILEHASH_COLUMN, &filehash,
	                     -1);
	if (filehash)
	{
		memset (filehash, 0xdd, 16);	/* marker for removal */
	}
}

static void
ulstats_popupmenu_onRemoveSelected (GtkWidget *widget, gpointer data)
{
	GtkTreeSelection *selection;
	GtkTreeIter       tmpiter;
	gboolean          val;

	g_return_if_fail ( ulstats_view != NULL );

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(ulstats_view));

	if (!selection)
		return;

	gtk_tree_selection_selected_foreach (selection, ulstats_foreach_helper, NULL);

	g_return_if_fail ( ulstats_store != NULL );

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ulstats_store), &tmpiter);

	while (val)
	{
		const guint8 deletehash[16] = {0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd};
		guint8 *filehash = NULL;

		gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), &tmpiter,
		                     ULSTATS_FILEHASH_COLUMN, &filehash,
		                     -1);

		if ( memcmp(deletehash,filehash,16) == 0 )
			g_idle_add ( (GSourceFunc) uploadstats_remove_iter, gtk_tree_iter_copy (&tmpiter));

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL(ulstats_store), &tmpiter);
	}

}



/******************************************************************************
 *
 *   ulstats_popup_menu
 *
 *   pop up a menu after right-click on the upload stats list
 *
 ***/

static void
ulstats_popup_menu (guint button, guint32 activate_time)
{
	GtkWidget          *menu;
	GtkTreeSelection   *selection;

	menu  = gtk_menu_new();

	g_return_if_fail ( ulstats_view != NULL );

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(ulstats_view));

  misc_gtk_add_menu_header(menu, _("Upload statistics"), NULL);

	misc_gtk_add_menu_item ( menu, _(" remove all selected items "), ulstats_popupmenu_onRemoveSelected, NULL,
	                         ICON_MENU_CANCEL, (misc_gtk_tree_selection_count_selected_rows (selection) > 0) );

	misc_gtk_add_menu_item ( menu, _(" remove all items <  10M "), ulstats_popupmenu_onRemoveItemsSmallerThan,
	                          GUINT_TO_POINTER(10*1024*1024), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_item ( menu, _(" remove all items <  50M "), ulstats_popupmenu_onRemoveItemsSmallerThan,
	                          GUINT_TO_POINTER(50*1024*1024), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_item ( menu, _(" remove all items < 100M "), ulstats_popupmenu_onRemoveItemsSmallerThan,
	                          GUINT_TO_POINTER(100*1024*1024), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_item ( menu, _(" remove all items < 500M "), ulstats_popupmenu_onRemoveItemsSmallerThan,
	                          GUINT_TO_POINTER(500*1024*1024), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" remove items last uploaded more than 3 days ago "),
	                          ulstats_popupmenu_onRemoveItemsLastUploadedBefore,
	                          GUINT_TO_POINTER(3), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_item ( menu, _(" remove items last uploaded more than a week ago "),
	                          ulstats_popupmenu_onRemoveItemsLastUploadedBefore,
	                          GUINT_TO_POINTER(7), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_item ( menu, _(" remove items last uploaded more than two weeks ago "),
	                          ulstats_popupmenu_onRemoveItemsLastUploadedBefore,
	                          GUINT_TO_POINTER(14), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_item ( menu, _(" remove items last uploaded more than a month ago "),
	                          ulstats_popupmenu_onRemoveItemsLastUploadedBefore,
	                          GUINT_TO_POINTER(30), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" remove all items not shared anymore "),
	                          ulstats_popupmenu_onRemoveItemsNotSharedAnyLonger,
	                          NULL, ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" clear entire list "),
	                          ulstats_popupmenu_onRemoveItemsSmallerThan,
	                          GUINT_TO_POINTER(G_MAXUINT), ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" reset total upload/download stats "),
	                          ulstats_popupmenu_onResetStats,
	                          NULL, ICON_MENU_REFRESH, TRUE );

	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, activate_time);
}


/******************************************************************************
 *
 *   ulstats_button_pressed
 *
 *   pop up a menu on right-click on the upload stats list
 *
 ***/

static gint
ulstats_onButtonPress (GtkWidget *widget, GdkEventButton *bevent, gpointer data)
{
	if ( bevent->button != 3 )
		return FALSE;

	ulstats_popup_menu (bevent->button, bevent->time);

	return TRUE;
}




/******************************************************************************
 *
 *   ulstats_name_sort_function
 *
 ***/

static gint
ulstats_name_sort_function (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
{
	gchar *name_a = NULL;
	gchar *name_b = NULL;
	gint   ret = 0;

	g_return_val_if_fail ( a     != NULL, 0 );
	g_return_val_if_fail ( b     != NULL, 0 );
	g_return_val_if_fail ( model != NULL, 0 );

	gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), a,
	                     ULSTATS_FILENAME_COLUMN, &name_a,
	                     -1);

	gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), b,
	                     ULSTATS_FILENAME_COLUMN, &name_b,
	                     -1);

	g_return_val_if_fail ( name_a   != NULL, 0 );
	g_return_val_if_fail ( name_b   != NULL, 0 );

	ret = g_utf8_collate (name_a, name_b);

	g_free (name_a);
	g_free (name_b);

	return ret;
}

/******************************************************************************
 *
 *  uploadstats_remove_iter
 *
 *  callback for idle timeout to remove multiple iters when
 *  walking through a store or using foo_foreach();
 *
 ***/

static gboolean
uploadstats_remove_iter (GtkTreeIter *iter)
{
	guint8   *hash = NULL;

	g_return_val_if_fail (iter != NULL, FALSE);

	gtk_tree_model_get ( GTK_TREE_MODEL (ulstats_store), iter,
	                     ULSTATS_FILEHASH_COLUMN, &hash,
	                     -1);

	G_FREE(hash);

	gtk_list_store_remove ( ulstats_store, iter);

	gtk_tree_iter_free (iter);

	return FALSE;
}


/******************************************************************************
 *
 *   onOptionChanged
 *
 ******************************************************************************/

static void
onOptionChanged (OptNum bla)
{
	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(ulstats_view), opt_get_bool(OPT_GUI_SET_RULES_HINT_ON_LISTS));
}


/******************************************************************************
 *
 *   uploadstats_create_store_model_and_view
 *
 ******************************************************************************/

GtkWidget *
uploadstats_create_store_model_and_view (void)
{
	GtkWidget        	*scrollwin;
	GtkCellRenderer  	*renderer;
	GtkTreeViewColumn	*column;
	GtkTreeSelection 	*selection;

	opt_notify (OPT_GUI_SET_RULES_HINT_ON_LISTS, onOptionChanged);

	ulstats_store = gtk_list_store_new ( ULSTATS_N_COLUMNS,
	                                     ULSTATS_TYPES_PLACEHOLDER );

	g_signal_connect(ulstats_store, "sort-column-changed", (GCallback) onSortColumnChanged,  NULL);

	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ulstats_store),
	                                     opt_get_int(OPT_GUI_SORT_COL_UPLOAD_STATS),
	                                     opt_get_int(OPT_GUI_SORT_TYPE_UPLOAD_STATS));

	ulstats_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(ulstats_store));

	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(ulstats_view), opt_get_bool(OPT_GUI_SET_RULES_HINT_ON_LISTS));

	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(UTF8_SHORT_PRINTF("%s",_("Uploaded")), renderer,
	                                                  "text", ULSTATS_UPLOADED_STRING_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, ULSTATS_UPLOADED_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(ulstats_view), column);
	gtk_tree_view_column_set_resizable ( column, TRUE );
	gtk_tree_view_column_set_alignment(column, 0.80);
	g_object_set (G_OBJECT(renderer), "xalign", 1.0, NULL);
	g_object_set (G_OBJECT(renderer), "xpad", 10, NULL);

	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(UTF8_SHORT_PRINTF("%s",_("UL/Size")), renderer,
	                                                  "text", ULSTATS_RATIO_STRING_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, ULSTATS_RATIO_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(ulstats_view), column);
	gtk_tree_view_column_set_resizable ( column, TRUE );
	gtk_tree_view_column_set_alignment(column, 0.80);
	g_object_set (G_OBJECT(renderer), "xalign", 1.0, NULL);
	g_object_set (G_OBJECT(renderer), "xpad", 10, NULL);

	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(UTF8_SHORT_PRINTF("%s",_("Filename")), renderer,
	                                                  "text", ULSTATS_FILENAME_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, ULSTATS_FILENAME_COLUMN);

	gtk_tree_view_append_column(GTK_TREE_VIEW(ulstats_view), column);
	gtk_tree_view_column_set_resizable ( column, TRUE );
	gtk_tree_view_column_set_alignment(column, 0.02);
	g_object_set (G_OBJECT(renderer), "xpad", 10, NULL);

	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(UTF8_SHORT_PRINTF("%s",_("Last time uploaded")), renderer,
	                                                  "text", ULSTATS_LAST_UPLOADED_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, ULSTATS_LAST_UPLOADED_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(ulstats_view), column);
	gtk_tree_view_column_set_resizable ( column, TRUE );
	gtk_tree_view_column_set_alignment(column, 0.15);
	g_object_set (G_OBJECT(renderer), "xpad", 10, NULL);
	gtk_tree_view_column_set_cell_data_func( column, renderer,
	                                         cell_data_function_last_uploaded,
	                                         GUINT_TO_POINTER(ULSTATS_LAST_UPLOADED_COLUMN),
	                                         NULL);


	gtk_tree_sortable_set_sort_func ( GTK_TREE_SORTABLE(ulstats_store),
	                                  ULSTATS_FILENAME_COLUMN,
	                                  ulstats_name_sort_function,
	                                  NULL,
	                                  NULL );

	g_signal_connect (ulstats_view, "button-press-event", G_CALLBACK(ulstats_onButtonPress), NULL);


	g_object_set_data_full (G_OBJECT(ulstats_view), "foo1",
	                        &ulstats_view, (GDestroyNotify)g_nullify_pointer);

	g_object_set_data_full (G_OBJECT(ulstats_store), "foo1",
	                        &ulstats_store, (GDestroyNotify)g_nullify_pointer);

	scrollwin = gtk_scrolled_window_new (NULL,NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scrollwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_container_add (GTK_CONTAINER(scrollwin), ulstats_view);

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(ulstats_view));
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);

	gtk_widget_show_all (scrollwin);

	restore_tree_view_column_widths(ulstats_view);

	/* timeout to sync filename changes to upload stats list */
	(void) g_timeout_add(1000*60, ulstats_sync_ulstats_filenames_timeout, NULL);

	/* read upload stats data in 3 seconds, after window is up (=>less delay) */
	(void) g_timeout_add(3000, uploadstats_read_from_file, NULL);

	g_signal_connect(core, "shared-files", (GCallback) onSharedFiles, NULL);

	/* Save uploadstats every five minutes */
	g_timeout_add(5*60*1000, write_gui_uploadstats, NULL);

	g_atexit(&atExit);

	return (scrollwin);
}




/******************************************************************************
 *
 *   uploadstats_read_from_file
 *
 *   reads the upload stats from disk (the new ascii format version)
 *
 */

static gboolean
uploadstats_read_from_file (gpointer data)
{
	static gboolean     busy = FALSE;
	const gchar        *filename;
	gchar             **line, **linearray;
	guint               vermaj, vermin, verminmin, ret;

	if (busy)
		return FALSE;

	busy = TRUE;

	filename = opt_get_opt_filename("gui_uploadstats.txt");

	g_return_val_if_fail ( filename != NULL, FALSE );

	linearray = misc_get_array_of_lines_from_textfile (filename, FALSE, TRUE);

	/* there should be at least our header! */
	if (!linearray)
	{
		busy = FALSE;
		return FALSE; /* don't call us again */
	}

	line = linearray;
	ret = sscanf(*line, "# ed2k_gui --- gui_uploadstats --- config format version %u.%u.%u\n", &vermaj, &vermin, &verminmin);
	if (ret != 3)
	{
		g_printerr (_("gui_uploadstats.txt: invalid header?\n"));
		g_strfreev(linearray);
		busy = FALSE;
		return FALSE; /* don't call us again */
	}
	else
	{
		gchar			 hashstr[33], fn[4096];
		gulong			 last_upload;
		guint			 size;
		gfloat			 uploaded;

		line++;
		while (*line)
		{
			if (**line!='#' && **line!=0x00)
			{
				fn[0]=0x00;

				if (5 == sscanf(*line, "%32s %u %f %lu \\%4095[^\\]\\\n", hashstr, &size, &uploaded, &last_upload, fn))
				{
					if (g_utf8_validate(fn,-1,NULL))
					{
						ulstats_add_or_increase_record (fn,
						                                hash_str_to_hash(hashstr),
						                                0.0,
						                                0,
						                                uploaded,
						                                (time_t)last_upload,
						                                size );
					}
					else
					{
						gchar *fn_utf8 = TO_UTF8(fn);
						if (fn_utf8)
						{
							if (!g_utf8_validate(fn_utf8,-1,NULL))
							{
								static gboolean beenhere; /* FALSE */
								if (!beenhere)
								{
									g_warning(_("Could not convert filename '%s' into UTF8 encoding from locale in\n"
									            "\t%s:%u (maybe filename neither in UTF8 nor in locale charset encoding?)\n"
									            "\tYou might get other warnings and cut-off strings in the upload\n"
									            "\tstats list (this warning will only be shown once).\n"), fn, __FUNCTION__, __LINE__);
									beenhere = TRUE;
								}
							}

							ulstats_add_or_increase_record (fn_utf8,
							                                hash_str_to_hash(hashstr),
							                                0.0,
							                                0,
							                                uploaded,
							                                (time_t)last_upload,
							                                size );
							FREE_UTF8(fn_utf8);
						}
					}
				}
			}
			line++;
		}
	}

	g_strfreev (linearray);

	busy = FALSE;

	return FALSE;	/* don't call us again */
}


/******************************************************************************
 *
 *   write_gui_uploadstats
 *
 *   writes the upload stats to disk (strings will be saved in UTF8)
 *
 */

static gboolean
write_gui_uploadstats (gpointer foo)
{
	const gchar  *filename;
	GtkTreeIter   tmpiter;
	gboolean      val;
	FILE         *f;

	g_return_val_if_fail ( ulstats_store != NULL, TRUE );

	filename = opt_get_opt_filename("gui_uploadstats.txt");
	g_return_val_if_fail ( filename != NULL, TRUE );

	f = fopen (filename, "w");
	if ( f == NULL )
	{
		status_system_error_msg(_("couldn't save upload stats"));
		return TRUE;
	}

	fprintf (f, "# ed2k_gui --- gui_uploadstats --- config format version 0.3.0\n");
	fprintf (f, "#\n%s\n", _("# This file is created by the ed2k gui. It contains your"));
	fprintf (f, "%s\n#\n", _("# upload statistics. DO NOT CHANGE THE ABOVE HEADER!"));
	fprintf (f, "%s", _("# file hash - file size - amount uploaded - time last uploaded - file name\n"));

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ulstats_store), &tmpiter);

	while (val)
	{
		guint8   *fhash = NULL;
		gchar    *fname = NULL;
		guint     filesize = 0;
		guint     lastup   = 0;
		gfloat    uploaded = 0.0;

		gtk_tree_model_get ( GTK_TREE_MODEL(ulstats_store), &tmpiter,
		                     ULSTATS_FILEHASH_COLUMN, &fhash,
		                     ULSTATS_FILENAME_COLUMN, &fname,
		                     ULSTATS_FILESIZE_COLUMN, &filesize,
		                     ULSTATS_UPLOADED_COLUMN, &uploaded,
		                     ULSTATS_LAST_UPLOADED_COLUMN, &lastup,
		                     -1);

		if ( (fhash) && (fname) )
		{
			fprintf (f, "%32s %u %f %u \\%s\\\n",
				hash_to_hash_str(fhash), filesize, uploaded, lastup, fname);
		}

		G_FREE(fname);

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL(ulstats_store), &tmpiter);
	}

#ifdef G_OS_UNIX
	/* set file permissions so that only the owner can read and write to it */
	fchmod (fileno(f), (mode_t) S_IRUSR|S_IWUSR);
#endif

	fclose (f);

	return TRUE;
}


