/***************************************************************************
                          uploads.c  -  description
                             -------------------
    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 "global.h"
#include "core-conn.h"
#include "options.h"
#include "misc_strings.h"
#include "uploads.h"
#include "uploadstats.h"

#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkentry.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtklabel.h>
#include <gtk/gtkvpaned.h>
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtktreeselection.h>
#include <gtk/gtktreeview.h>

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

enum
{
  SORTID_UPLOADS_FILENAME = 99,
  SORTID_UPLOADS_USERNAME,
  SORTID_UPLOADS_SPEED,
  SORTID_UPLOADS_OTHER,
};

struct _GuiUpload
{
	guint8       filehash[16];

	guint        slotid;
	gfloat       speed;
	gchar       *filename_utf8;
	gchar       *username_utf8;

	gboolean     remove_flag;

	GtkTreeIter  iter;
};

typedef struct _GuiUpload  GuiUpload;


#define NUM_VIEW_COLS   3

static GtkListStore         *uploads_store = NULL;
static GtkWidget            *uploads_view = NULL;

static guint                 column_widths[NUM_VIEW_COLS];

static GList                 *uploads;  /* NULL */


/* functions */


/******************************************************************************
 *
 *   upload_free
 *
 *   If remove is FALSE, then it only frees the contents, otherwise
 *    it removes the upload from the list store and the list.
 *
 ******************************************************************************/

static void
upload_free (GuiUpload *ul, gboolean remove)
{
	g_return_if_fail ( ul != NULL );

	G_FREE(ul->filename_utf8);
	G_FREE(ul->username_utf8);
	ul->speed = 0.0;

	if (!remove)
		return;

	gtk_list_store_remove(uploads_store, &(ul->iter));

	memset(ul, 0x00, sizeof(GuiUpload));

	uploads = g_list_remove_all(uploads, ul);

	g_free(ul);
}


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

static void
atExit (void)
{
	GList *node;

	for (node = uploads;  node != NULL;  node = node->next)
	{
		GuiUpload *ul = (GuiUpload*) node->data;
		upload_free(ul, FALSE);
		memset(ul, 0x00, sizeof(GuiUpload));
		g_free(ul);
	}
	g_list_free(uploads);
	uploads = NULL;
}


/******************************************************************************
 *
 *  onUploadStatus
 *
 ******************************************************************************/

static void
onUploadStatus (GuiCoreConn *conn, guint num, gfloat *speed_arr, guint  *slotid_arr, gpointer data)
{
	static time_t   lastpacket; /* 0 */
	GuiUpload      *ul;
	time_t          now;
	GList          *node;
	guint           n, timediff;

	now = time(NULL);

	timediff = CLAMP((guint)(now-lastpacket), 2, 4);

	for (node = uploads;  node != NULL;  node = node->next)
		((GuiUpload*)node->data)->remove_flag = TRUE;

	for (n = 0;  n < num;  ++n)
	{
		ul = NULL;
		for (node = uploads;  node != NULL && ul == NULL;  node = node->next)
		{
			if (((GuiUpload*)node->data)->slotid == slotid_arr[n])
				ul = (GuiUpload*)node->data;
		}

		if (ul == NULL)
			continue;

		ul->remove_flag = FALSE;
		ul->speed = speed_arr[n];

		if (*((guint64*)(ul->filehash)) != (guint64) 0L  &&  speed_arr[n] > 0.0)
		{
			upload_stats_add_or_increase (ul->filename_utf8, ul->filehash, ul->speed, timediff);
		}

		/* Refresh row */
		gtk_list_store_set (uploads_store, &(ul->iter), 0, ul, -1);
	}


	/* Remove slots that are not referenced in the upload status any longer */
	for (node = uploads;  node != NULL;  node = node->next)
	{
		ul = (GuiUpload*)node->data;
		if (ul->remove_flag == TRUE)
		{
			upload_free(ul, TRUE);
			g_free(ul);
		}
	}

	lastpacket = now;
}




/******************************************************************************
 *
 *  onRemoveUploadSlot
 *
 ******************************************************************************/

static void
onRemoveUploadSlot (GuiCoreConn *conn, guint slotid, gpointer data)
{
	GuiUpload *ul = NULL;
	GList     *node;

	g_return_if_fail (uploads_store != NULL);

	for (node = uploads;  node != NULL;  node = node->next)
	{
		ul = (GuiUpload*)node->data;
		if ((ul) && ul->slotid == slotid)
		{
			upload_free(ul, TRUE);
			return;
		}
	}
}


/******************************************************************************
 *
 *  onChangeUploadSlot
 *
 ******************************************************************************/

static void
onChangeUploadSlot (GuiCoreConn   *conn,
                    guint          slotid,
                    const gchar   *filename,
	                  const guint8  *filehash,
	                  const gchar   *username,
	                  const guint8  *userhash,  /* this is a zero-hash for now */
                    gpointer       data)
{
	GuiUpload *ul = NULL;
	GList     *node;

	g_return_if_fail (uploads_store != NULL);

	for (node = uploads;  node != NULL && ul == NULL;  node = node->next)
	{
		if ((node->data) && ((GuiUpload*)node->data)->slotid == slotid)
		{
			ul = (GuiUpload*)node->data;
			upload_free(ul, FALSE); /* only free contents */
			gtk_list_store_set(uploads_store, &(ul->iter), 0, ul, -1); /* refresh row */
		}
	}

	if (!ul)
	{
		ul = g_new0(GuiUpload, 1);
		ul->slotid = slotid;

		gtk_list_store_append (uploads_store, &(ul->iter));

		uploads = g_list_append(uploads, ul);
	}

#if 0
	g_print ("slotid   = %u\n", slotid);
	g_print ("filename = %s\n", filename);
	g_print ("filehash = %lu\n", (gulong) *((gulong*)filehash));
	g_print ("username = %s\n", username);
	g_print ("\n");
#endif

	if ((filename) && (*filename))
		ul->filename_utf8 = TO_UTF8(filename);
	else
		ul->filename_utf8 = UTF8_PRINTF("%s", _("(upload slot allocated)"));

	if ((username) && (*username))
		ul->username_utf8 = TO_UTF8(username);
	else
		ul->username_utf8 = UTF8_PRINTF("%s", _("(empty username)"));

	memcpy(ul->filehash, filehash, 16);
//	memcpy(ul->userhash, userhash, 16);

	/* refresh row */
	gtk_list_store_set (uploads_store, &(ul->iter), 0, ul, -1);
}


/******************************************************************************
 *
 *  onCoreConnStatus
 *
 ******************************************************************************/

static void
onCoreConnStatus (GuiCoreConn *conn, guint status, GtkWidget *mainwindow)
{
	if (status != CONN_STATUS_COMMUNICATING  &&  status != CONN_STATUS_AUTHENTICATING)
	{
		if (uploads_store)
		{
			while (uploads)
				upload_free((GuiUpload*)uploads->data, TRUE);
		}
		else
		{
			uploads = NULL; /* leaks, but shouldn't happy anyway! */
		}
	}
}


/******************************************************************************
 *
 *   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_UPLOADS, 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(uploads_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_UPLOADS, sortid);
		opt_set_int (OPT_GUI_SORT_TYPE_UPLOADS, 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_UPLOADS);
	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);
}

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

static void
onOptionChanged (OptNum num)
{
	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(uploads_view), opt_get_bool(OPT_GUI_SET_RULES_HINT_ON_LISTS));
}

/******************************************************************************
 *
 *  cell_data_func_filename
 *
 ******************************************************************************/

static void
cell_data_func_filename (GtkTreeViewColumn *col,
                         GtkCellRenderer   *cell,
                         GtkTreeModel      *model,
                         GtkTreeIter       *iter,
                         gpointer           data)
{
	GuiUpload *ul = NULL;

	gtk_tree_model_get(model, iter, 0, &ul, -1);

	if (ul != NULL)
		g_object_set(G_OBJECT(cell), "text", ul->filename_utf8, NULL);
}

/******************************************************************************
 *
 *  cell_data_func_username
 *
 ******************************************************************************/

static void
cell_data_func_username (GtkTreeViewColumn *col,
                         GtkCellRenderer   *cell,
                         GtkTreeModel      *model,
                         GtkTreeIter       *iter,
                         gpointer           data)
{
	GuiUpload *ul = NULL;

	gtk_tree_model_get(model, iter, 0, &ul, -1);

	if (ul != NULL)
		g_object_set(G_OBJECT(cell), "text", ul->username_utf8, NULL);
}

/******************************************************************************
 *
 *  cell_data_func_speed
 *
 ******************************************************************************/

static void
cell_data_func_speed (GtkTreeViewColumn *col,
                      GtkCellRenderer   *cell,
                      GtkTreeModel      *model,
                      GtkTreeIter       *iter,
                      gpointer           data)
{
	GuiUpload *ul = NULL;

	gtk_tree_model_get(model, iter, 0, &ul, -1);

	if (ul)
	{
		if (ul->speed > 0.1)
			g_object_set(G_OBJECT(cell), "text", UTF8_SHORT_PRINTF(_("%.1f kB/s "), ul->speed), NULL);
		else
			g_object_set(G_OBJECT(cell), "text", UTF8_SHORT_PRINTF(_("%.0f byte/s "), ul->speed*1024.0), NULL);
	}
	else
		g_object_set(G_OBJECT(cell), "text", "", NULL);
}



/******************************************************************************
 *
 *  sort_func
 *
 ******************************************************************************/

static gint
sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
{
	GuiUpload *ula = NULL, *ulb = NULL;
	guint      sortid = GPOINTER_TO_UINT(data);
	gint       ret = 0;

	gtk_tree_model_get(model, a, 0, &ula, -1);
	gtk_tree_model_get(model, b, 0, &ulb, -1);

	if (!ula || !ulb)
		return 0;

	switch (sortid)
	{
  	case SORTID_UPLOADS_FILENAME:
			ret = g_utf8_collate((ula->filename_utf8) ? ula->filename_utf8 : "", (ulb->filename_utf8) ? ulb->filename_utf8 : "");
			break;

  	case SORTID_UPLOADS_USERNAME:
			ret = g_utf8_collate( (ula->username_utf8) ? ula->username_utf8: "" , (ulb->username_utf8) ? ulb->username_utf8: "");
			break;

  	case SORTID_UPLOADS_SPEED:
			if (ula->speed != ulb->speed)
				ret = (ula->speed > ulb->speed) ? 1 : -1;
			break;

  	case SORTID_UPLOADS_OTHER:
		default:
			break;
	}

	return ret;
}


/******************************************************************************
 *
 *  uploads_create_store_model_and_view
 *
 ******************************************************************************/

GtkWidget *
uploads_create_store_model_and_view (void)
{
	GtkWidget        	*scrollwin;
	GtkCellRenderer  	*renderer;
	GtkTreeViewColumn	*column;
	GtkTreeSelection 	*selection;
	GtkTreeSortable   *sort;

	opt_notify (OPT_GUI_SET_RULES_HINT_ON_LISTS, onOptionChanged);

	uploads_store = gtk_list_store_new (1, G_TYPE_POINTER);

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

	sort = GTK_TREE_SORTABLE(uploads_store);

	gtk_tree_sortable_set_sort_func(sort, SORTID_UPLOADS_FILENAME, sort_func,
	                                GUINT_TO_POINTER(SORTID_UPLOADS_FILENAME),
	                                NULL);

	gtk_tree_sortable_set_sort_func(sort, SORTID_UPLOADS_USERNAME, sort_func,
	                                GUINT_TO_POINTER(SORTID_UPLOADS_USERNAME),
	                                NULL);

	gtk_tree_sortable_set_sort_func(sort, SORTID_UPLOADS_SPEED, sort_func,
	                                GUINT_TO_POINTER(SORTID_UPLOADS_SPEED),
	                                NULL);

	gtk_tree_sortable_set_default_sort_func(sort, sort_func,
	                                GUINT_TO_POINTER(SORTID_UPLOADS_OTHER),
	                                NULL);

	/* Transition from old uploads stuff to new stuff */
	if (opt_get_int(OPT_GUI_SORT_COL_UPLOADS) < SORTID_UPLOADS_FILENAME)
		opt_set_int(OPT_GUI_SORT_COL_UPLOADS, SORTID_UPLOADS_FILENAME);

	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(uploads_store),
	                                     opt_get_int(OPT_GUI_SORT_COL_UPLOADS),
	                                     opt_get_int(OPT_GUI_SORT_TYPE_UPLOADS));

	uploads_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(uploads_store));

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

	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new();
	gtk_tree_view_column_pack_start(column, renderer, TRUE);
	gtk_tree_view_column_set_title(column, UTF8_SHORT_PRINTF("%s",_("File being uploaded")));
	gtk_tree_view_column_set_cell_data_func(column, renderer, &cell_data_func_filename, NULL, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SORTID_UPLOADS_FILENAME);
	gtk_tree_view_append_column(GTK_TREE_VIEW(uploads_view), column);
	gtk_tree_view_column_set_alignment(column, 0.02);
	g_object_set (G_OBJECT(renderer), "xalign", 0.0, NULL);
	g_object_set (G_OBJECT(renderer), "xpad", 2, NULL);

	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new();
	gtk_tree_view_column_pack_start(column, renderer, TRUE);
	gtk_tree_view_column_set_title(column, UTF8_SHORT_PRINTF("%s",_("Who")));
	gtk_tree_view_column_set_cell_data_func(column, renderer, &cell_data_func_username, NULL, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SORTID_UPLOADS_USERNAME);
	gtk_tree_view_append_column(GTK_TREE_VIEW(uploads_view), column);
	gtk_tree_view_column_set_alignment(column, 0.02);
	g_object_set (G_OBJECT(renderer), "xalign", 0.00, NULL);
	g_object_set (G_OBJECT(renderer), "xpad", 2, NULL);

	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new();
	gtk_tree_view_column_pack_start(column, renderer, TRUE);
	gtk_tree_view_column_set_title(column, UTF8_SHORT_PRINTF("%s",_("Speed")));
	gtk_tree_view_column_set_cell_data_func(column, renderer, &cell_data_func_speed, NULL, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SORTID_UPLOADS_SPEED);

	gtk_tree_view_append_column(GTK_TREE_VIEW(uploads_view), column);
	gtk_tree_view_column_set_alignment(column, 0.9);
	g_object_set (G_OBJECT(renderer), "xalign", 1.0, NULL);
	g_object_set (G_OBJECT(renderer), "xpad", 2, NULL);

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

	g_object_set_data_full (G_OBJECT(uploads_store), "foo1",
	                        &uploads_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), uploads_view);

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(uploads_view));
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);

	gtk_widget_show_all (scrollwin);

	restore_tree_view_column_widths(uploads_view);

	g_signal_connect(core, "upload-status",      (GCallback) onUploadStatus,     NULL);
	g_signal_connect(core, "core-conn-status",   (GCallback) onCoreConnStatus,   NULL);
	g_signal_connect(core, "change-upload-slot", (GCallback) onChangeUploadSlot, NULL);
	g_signal_connect(core, "remove-upload-slot", (GCallback) onRemoveUploadSlot, NULL);

	g_atexit(&atExit);

	return (scrollwin);
}



