
/***************************************************************************
 *                                                                         *
 *   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 "colors.h"
#include "core-conn.h"
#include "global.h"
#include "icons.h"
#include "misc.h"
#include "misc_gtk.h"
#include "misc_strings.h"
#include "options.h"
#include "notebook.h"
#include "stats.h"
#include "statusbar.h"
#include "status_page.h"

#include <gdk/gdkdrawable.h>
#include <gtk/gtkdrawingarea.h>
#include <gtk/gtkentry.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtkvbox.h>

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

/* FIXME: these macros are horrible */
#define PROCESS_GPTRARRAY(parr,castToDatatype,block)		{if(parr){gint gpainc; for(gpainc=0; parr->len > gpainc; gpainc++){ castToDatatype *item = (castToDatatype*) g_ptr_array_index(parr,gpainc); { block }}}}
#define PROCESS_GPTRARRAY_FROM_END(parr,castToDatatype,block)	{if(parr){gint gpainc; for(gpainc=parr->len-1; gpainc>=0; gpainc--){ castToDatatype *item = (castToDatatype*) g_ptr_array_index(parr,gpainc); { block }}}}

typedef struct
{
	gfloat    up;           /* upload speed in kB/s                     */
	gfloat    down;         /* download speed in kB/s                   */
	time_t    when;
} GuiStatsRecord;


/* to keep track of our chart */
typedef struct
{
	GdkDrawable *pixmap;
	GdkGC       *gc;
} GuiChartPixmap;


static gfloat          total_downloaded;                 /* 0.0 */
static gfloat          total_uploaded;                   /* 0.0 */

static time_t          seconds_download_was_nonzero = 1; /* so we don't divide by zero */
static time_t          seconds_upload_was_nonzero   = 1; /* let's not divide by zero */

static time_t          stats_last_cleared;               /* 0 */


static GtkWidget        *ew_up_wrong;   /* NULL */
static GtkWidget        *ew_down_wrong; /* NULL */
static GtkWidget        *ew_crop_up;    /* NULL */
static GtkWidget        *ew_crop_down;  /* NULL */


static time_t		 time_first_of_packets; /* 0 */
static time_t		 time_first_of_minutes; /* 0 */
static time_t		 time_first_of_hours;   /* 0 */
static time_t		 time_first_of_days;    /* 0 */

static gfloat		 up_max;   /* 0.0 */
static gfloat		 down_max; /* 0.0 */

static GPtrArray	*stats_packets; /* NULL */
static GPtrArray	*stats_minutes; /* NULL */
static GPtrArray	*stats_hours;   /* NULL */
static GPtrArray	*stats_days;    /* NULL */

static PangoLayout      *chart_layout; /* NULL */
static GtkWidget	*chart;        /* NULL */ /* our GtkDrawingArea */
static GuiChartPixmap	*cpm;          /* NULL */ /* pixmap we draw on (we use double-buffering to avoid flicker) */

static GdkGC		*penblack; /* NULL */
static GdkGC		*penred;   /* NULL */
static GdkGC		*penblue;  /* NULL */
static GdkGC		*pengreen; /* NULL */
static GdkGC		*penwhite; /* NULL */

/* functions */

static GuiStatsRecord	 *stats_make_average_stat_record (GPtrArray *statsarray, time_t from, time_t to);

static void			        stats_remove_old_records (GPtrArray *statsarray, gint maxsecondsago);

static void			        stats_repaint (void);

static void             stats_read_from_disk (void);

static void             stats_save_to_disk (void);


/***************************************************************************
 *
 *   stats_print_download_stats_line
 *
 *   Takes some statistics and calculates some more stats
 *    from those and prints out those stats in the statusbar
 *
 *   which = "upload" or "download"
 *   total = total ul/dl in kB
 *   secondss_nonzero = number of seconds upload/download was >0
 *                      (=actually down/uploading)
 *
 ***************************************************************************/

static void
stats_produce_uldlstat_line (const char *which, float total, time_t seconds_nonzero)
{
	// secs since stat last cleared
	float secs_since = (float)(time(NULL)-stats_last_cleared);

	// days since stat last cleared
	float days_since = secs_since/(24.0*3600.0);

	// total ul/dl in GB instead of kB
	float total_GB   = total/(1024.0*1024.0);

	// we don't want to divide by 0 if not uploaded/downloaded yet, so +0.0000001
	// secs downloading since stats last cleared
	float actual_kBs = total/(seconds_nonzero+0.00000001);

	// percent of the time we spent *loading
	float percent    = ((float) seconds_nonzero) / secs_since * 100.0;

	/* stats page disabled? */
	if (chart == NULL)
		return;

	if (percent > 100.0) 
		percent = 100.0;

	statusbar_msg (_(" Total %s: ~%.2fG in %.1f days (~%.1fM/day) - actual: ~%.1fkB/s, %sing %.1f%% of the time (%s)"),
	                which, total_GB,
	                days_since,
	                (total/1024.0)/days_since, 
	                actual_kBs, 
	                which, 
	                percent, 
	                seconds_into_human_time(seconds_nonzero));
}

/***************************************************************************
 *
 *   stats_print_download_stats_line
 *
 ***************************************************************************/

void
stats_print_download_stats_line (void)
{
	stats_produce_uldlstat_line (_("download"), total_downloaded, seconds_download_was_nonzero);
}

/***************************************************************************
 *
 *   stats_print_upload_stats_line
 *
 ***************************************************************************/

void
stats_print_upload_stats_line (void)
{
	stats_produce_uldlstat_line (_("upload"), total_uploaded, seconds_upload_was_nonzero);
}


/***************************************************************************
 *
 *   stats_clear_totals
 *
 ***************************************************************************/

void
stats_clear_totals (void)
{
	stats_last_cleared = time(NULL);
	total_downloaded   = 0.0;
	total_uploaded     = 0.0;
	seconds_download_was_nonzero = (time_t) 1;
	seconds_upload_was_nonzero   = (time_t) 1;
}


/***************************************************************************
 *
 *   stats_onCoreSpeedTotalIncreaseGlobalStats
 *
 *   increase total amount downloaded and uploaded etc.
 *
 ***************************************************************************/

static void
stats_onCoreSpeedTotalIncreaseGlobalStats (gfloat downspeed, gfloat upspeed, time_t now)
{
	static time_t  last_status_packet;  /* 0 */
	gint           timediff = 3;        /* we _should_ receive status packets every 3 secs */

	if (last_status_packet > 0)
		timediff = CLAMP((now-last_status_packet), 2, 4);

	last_status_packet = now;

	if (downspeed > 0.0)
	{
		/* more than 10000 kB/s = ca. 25 MB/s download? unlikely */
		g_return_if_fail (downspeed < 25000.0);
		seconds_download_was_nonzero += timediff;
		total_downloaded +=  downspeed * (gfloat)timediff;
	}

	if (upspeed > 0.0)
	{
		/* more than 10000 kB/s = ca. 10 MB/s upload? unlikely */
		g_return_if_fail (upspeed < 10000.0);
		seconds_upload_was_nonzero += timediff;
		total_uploaded += upspeed * (gfloat)timediff;
	}
}

/***************************************************************************
 *
 *   stats_onCoreSpeedTotal
 *
 *   Signal handler for the "speed-total" signal emitted by the
 *    GuiCoreConn object after it has received a new upload/download
 *    status message pair from the core
 *
 *   Also called from stats_read_file() when we read in the values
 *    and need to fill the time between now and the last time the
 *    GUI has run with 0/0 values.
 *
 *   regtime: either NULL, then the current time is assumed, or
 *            the time value converted with GUINT_TO_POINTER()
 *            (this will be sufficient until the year 2038)
 *
 ***************************************************************************/

static void
stats_onCoreSpeedTotal (GuiCoreConn *conn, gfloat downspeed, gfloat upspeed, gpointer pregtime)
{
	GuiStatsRecord  *statrec;
	static gfloat    lastup=0.0, lastdown=0.0;
	time_t           regtime;

	if (pregtime == NULL)
	{
		regtime = time(NULL);
		stats_onCoreSpeedTotalIncreaseGlobalStats(downspeed, upspeed, regtime);
	}
	else
	{
		regtime = (time_t) GPOINTER_TO_UINT(pregtime);
	}

	if (!stats_packets)
		stats_packets = g_ptr_array_new();

	if (time_first_of_packets == 0)
		time_first_of_packets = regtime;

	statrec = g_new0(GuiStatsRecord, 1);

	up_max = MAX (up_max, upspeed);
	down_max = MAX (down_max, downspeed);

	if (upspeed >= (gfloat)opt_get_int(OPT_GUI_STATS_UP_WRONG))
		upspeed = lastup;

	if (downspeed >= (gfloat)opt_get_int(OPT_GUI_STATS_DOWN_WRONG))
		downspeed = lastdown;

	lastup   = upspeed;
	lastdown = downspeed;

	statrec->up   = upspeed;
	statrec->down = downspeed;
	statrec->when = regtime;

//	g_print ("registered packet #%u - %lu\n", stats_packets->len+1, time(NULL));

	g_ptr_array_add(stats_packets, (gpointer)statrec);

	if ((stats_packets) && regtime-time_first_of_packets >= 60 && time_first_of_packets>0 )
	{
		GuiStatsRecord	*statrec_min;

		if (!stats_minutes)
			stats_minutes = g_ptr_array_new();

		statrec_min = stats_make_average_stat_record (stats_packets, time_first_of_packets, regtime);

		g_return_if_fail (statrec_min!=NULL);

		if (time_first_of_minutes == 0)
			time_first_of_minutes = regtime;

		g_ptr_array_add(stats_minutes, (gpointer)statrec_min);
		time_first_of_packets = 0;
		/* remove all packets stats */
		stats_remove_old_records (stats_packets, 3*(60+1));		// keep one packet more than necessary
	}

	if ((stats_minutes) && regtime-time_first_of_minutes >= 60*60 && time_first_of_minutes>0 )
	{
		GuiStatsRecord	*statrec_hour;

		if (!stats_hours)
			stats_hours = g_ptr_array_new();
		g_return_if_fail (stats_hours!=NULL);

		statrec_hour = stats_make_average_stat_record (stats_minutes, time_first_of_minutes, regtime);
		g_return_if_fail (statrec_hour!=NULL);

		if (time_first_of_hours == 0)
			time_first_of_hours = regtime;

		g_ptr_array_add(stats_hours, (gpointer)statrec_hour);
//		g_print ("stats_hours: added AVG at %lu (first_of_minutes == %lu)\n", statrec_hour->when, time_first_of_minutes);
		time_first_of_minutes = 0;
		// remove all older than 12 hours
		stats_remove_old_records (stats_minutes, 60*60*(12+1));
	}

	if ((stats_hours) && regtime-time_first_of_hours >= 60*60*24 && time_first_of_hours>0 )
	{
		GuiStatsRecord	*statrec_days;

		if (!stats_days)
			stats_days = g_ptr_array_new();
		g_return_if_fail (stats_days!=NULL);

		statrec_days = stats_make_average_stat_record (stats_hours, time_first_of_hours, regtime);
		g_return_if_fail (statrec_days!=NULL);

		if (time_first_of_days == 0)
			time_first_of_days = regtime;

		g_ptr_array_add(stats_days, (gpointer)statrec_days);
		time_first_of_hours = 0;
		// remove all older than 7 days
		stats_remove_old_records (stats_hours, 60*60*24*(7+1));
	}

	if ( (chart) && (cpm) && GTK_WIDGET_MAPPED(chart))
		stats_repaint();
}

#if 0
void
stats_register_upload_download_stats_packet ( time_t regtime,
                                              gfloat upspeed,
                                              gfloat downspeed,
                                              guint  no_up,
                                              guint  no_down )
{
	static gfloat	 lastup=0, lastdown=0;
	GuiStatsRecord		*statrec;

	if (!stats_packets)
		stats_packets = g_ptr_array_new();

	if (time_first_of_packets == 0)
		time_first_of_packets = regtime;

	statrec = g_new0(GuiStatsRecord, 1);

	up_max = MAX (up_max, upspeed);
	down_max = MAX (down_max, downspeed);

	if (upspeed >= (gfloat)opt_get_int(OPT_GUI_STATS_UP_WRONG))
		upspeed = lastup;

	if (downspeed >= (gfloat)opt_get_int(OPT_GUI_STATS_DOWN_WRONG))
		downspeed = lastdown;

	lastup=upspeed;
	lastdown=downspeed;

	statrec->up = upspeed;
	statrec->down = downspeed;
	statrec->when = regtime;

//	g_print ("registered packet #%u - %lu\n", stats_packets->len+1, time(NULL));

	g_ptr_array_add(stats_packets, (gpointer)statrec);

	if ((stats_packets) && regtime-time_first_of_packets >= 60 && time_first_of_packets>0 )
	{
		GuiStatsRecord	*statrec_min;

		if (!stats_minutes)
			stats_minutes = g_ptr_array_new();

		statrec_min = stats_make_average_stat_record (stats_packets, time_first_of_packets, regtime);

		g_return_if_fail (statrec_min!=NULL);

		if (time_first_of_minutes == 0)
			time_first_of_minutes = regtime;

		g_ptr_array_add(stats_minutes, (gpointer)statrec_min);
		time_first_of_packets = 0;
		/* remove all packets stats */
		stats_remove_old_records (stats_packets, 3*(60+1));		// keep one packet more than necessary
	}

	if ((stats_minutes) && regtime-time_first_of_minutes >= 60*60 && time_first_of_minutes>0 )
	{
		GuiStatsRecord	*statrec_hour;

		if (!stats_hours)
			stats_hours = g_ptr_array_new();
		g_return_if_fail (stats_hours!=NULL);

		statrec_hour = stats_make_average_stat_record (stats_minutes, time_first_of_minutes, regtime);
		g_return_if_fail (statrec_hour!=NULL);

		if (time_first_of_hours == 0)
			time_first_of_hours = regtime;

		g_ptr_array_add(stats_hours, (gpointer)statrec_hour);
//		g_print ("stats_hours: added AVG at %lu (first_of_minutes == %lu)\n", statrec_hour->when, time_first_of_minutes);
		time_first_of_minutes = 0;
		// remove all older than 12 hours
		stats_remove_old_records (stats_minutes, 60*60*(12+1));
	}

	if ((stats_hours) && regtime-time_first_of_hours >= 60*60*24 && time_first_of_hours>0 )
	{
		GuiStatsRecord	*statrec_days;

		if (!stats_days)
			stats_days = g_ptr_array_new();
		g_return_if_fail (stats_days!=NULL);

		statrec_days = stats_make_average_stat_record (stats_hours, time_first_of_hours, regtime);
		g_return_if_fail (statrec_days!=NULL);

		if (time_first_of_days == 0)
			time_first_of_days = regtime;

		g_ptr_array_add(stats_days, (gpointer)statrec_days);
		time_first_of_hours = 0;
		// remove all older than 7 days
		stats_remove_old_records (stats_hours, 60*60*24*(7+1));
	}

	if ( (chart) && (cpm) && GTK_WIDGET_MAPPED(chart))
		stats_repaint();
}
#endif

static GuiStatsRecord *
stats_make_average_stat_record (GPtrArray *statsarray, time_t from, time_t to)
{
	GuiStatsRecord *newsr;
	guint		 num = 0;

	g_return_val_if_fail (statsarray!=NULL, NULL);
	g_return_val_if_fail (from < to, NULL);

	newsr = g_new0 (GuiStatsRecord,1);
	g_return_val_if_fail (newsr!=NULL,NULL);

	newsr->up = 0.0;
	newsr->down = 0.0;

	PROCESS_GPTRARRAY_FROM_END ( statsarray, GuiStatsRecord,
		if (item)
		{
			if (item->when < from)	// it's sorted, and we're processing from end, so we can break here
				break;
			newsr->up += item->up;
			newsr->down += item->down;
			num++;
		}
	);

	if(num>0)
	{
		newsr->up /= (num*1.0);
		newsr->down /= (num*1.0);
	}

	newsr->when = from + ((to-from)/2);

	return newsr;
}


// stats_remove_old_records
//
// removes all records from a stats array older than 'maxsecondsago'

static void
stats_remove_old_records (GPtrArray *statsarray, gint maxsecondsago)
{
	time_t	too_old = time(NULL)-maxsecondsago;

	g_return_if_fail (statsarray!=NULL);

	PROCESS_GPTRARRAY_FROM_END ( statsarray, GuiStatsRecord,
		if ((item) && item->when <= too_old)
		{
			// remove item from array
			g_ptr_array_remove (statsarray, (gpointer)item);
			// free item
			memset(item,0x00,sizeof(item));
			g_free(item);
		}
	);
}


// stats_find_highest_samples
//
// Finds the highest upload/download sample
// Returns FALSE on error, otherwise TRUE

static gboolean
stats_find_highest_samples (GPtrArray *statsarray, guint maxsamples, guint *maxupsample, guint *maxdownsample)
{
//	time_t	too_old = time(NULL)-maxsecondsago;
	guint	c = 0;

	*maxupsample = 0;
	*maxdownsample = 0;

	g_return_val_if_fail (statsarray!=NULL, FALSE);

	PROCESS_GPTRARRAY_FROM_END ( statsarray, GuiStatsRecord,
//		if ((item) && item->when < too_old)		// array is sorted, and we're checking from end
//			break;
		if (c>maxsamples)
			break;
		if (item)
		{
			*maxupsample = MAX(*maxupsample, item->up);
			*maxdownsample = MAX(*maxdownsample, item->down);
		} //else g_print ("no item!\n");
		c++;
//		g_print ("c = %u\n", c);
	);

	*maxupsample = CLAMP(*maxupsample, *maxupsample, opt_get_int(OPT_GUI_STATS_UP_CROP));
	*maxdownsample = CLAMP(*maxdownsample, *maxdownsample, opt_get_int(OPT_GUI_STATS_UP_CROP));

	return TRUE;
}


// stats_get_sample
//
// gets the x_last sample (ie. last sample - x samples)
// Returns GuiStatsRecord or NULL if sample not found

static GuiStatsRecord *
stats_get_sample (GPtrArray *statsarr, guint x_last)
{
	guint count=0;
	g_return_val_if_fail (statsarr!=NULL, NULL);

	PROCESS_GPTRARRAY_FROM_END ( statsarr, GuiStatsRecord,
		// greater than because the code could skip the wanted item if item==NULL at count==x_last
		if ((item) && count>=x_last)
			return item;
		count++;
	);
	return NULL;
}


// stats_repaint_paint_points
//
// displays the points and the graphs

static void
stats_repaint_paint_points (GdkPoint *points_up, GdkPoint *points_down, guint num, gfloat hstep, guint midy)
{
	guint i;

	g_return_if_fail (points_up!=NULL);	
	g_return_if_fail (points_down!=NULL);

//	g_print ("paint_points - num=%u, hstep=%f\n", num, hstep);

	// First, draw the individual points (if the view mode requires that)
	for (i=0; i<num; i++)
	{	
		switch (opt_get_int(OPT_GUI_STATS_TIMELINE_MODE))
		{
			case STATS_TIMELINE_DISPLAYMODE_LINES:
			{
				// draw little filled circles to mark a dot in this mode
				gdk_draw_arc (cpm->pixmap, penblue, TRUE, points_up[i].x-2, points_up[i].y-2, 4, 4, 0, 64*360);
				gdk_draw_arc (cpm->pixmap, pengreen, TRUE,points_down[i].x-2-1, points_down[i].y-2-1, 4, 4, 0, 64*360);
			}
			break;

			case STATS_TIMELINE_DISPLAYMODE_BARS:	
			{
//				gint spacing = (hstep>=5.0) ? 2 : -1;	// space between the bars
				gint spacing = -1;
				gint xminus = (guint) (hstep/2.0)-spacing;
				gint up_h = midy-points_up[i].y;				// height of the upload bar
				gint down_h = points_down[i].y-midy;	// height of the download bar

				if (up_h>0)
				{
//					g_print ("\t RECT UP - %d %d %d %d\n", points_up[i].x-xminus, points_up[i].y,
//										((guint)hstep)-spacing, up_h);
					gdk_draw_rectangle (cpm->pixmap, penblue, TRUE,
										points_up[i].x-xminus, points_up[i].y,
										((guint)hstep)-spacing, up_h);
				}
				if (down_h>0)
				{
//					g_print ("\t RECT DOWN - %d %d %d %d\n", points_down[i].x-xminus, midy,
//										((guint)hstep)-spacing, down_h);
					gdk_draw_rectangle (cpm->pixmap, pengreen, TRUE,
										points_down[i].x-xminus, midy,
										((guint)hstep)-spacing, down_h);
				}
				// draw bars
			}
			break;

			default:
			{
				g_printerr ("unknown timeline mode in %s\n", __FUNCTION__);
				opt_set_int(OPT_GUI_STATS_TIMELINE_MODE, STATS_TIMELINE_DISPLAYMODE_BARS);
				stats_repaint_paint_points (points_up, points_down, num, hstep, midy);
				return;
			}
		}
	}

	// Secondly, do anything else that's required:
	switch (opt_get_int(OPT_GUI_STATS_TIMELINE_MODE))
	{
		case STATS_TIMELINE_DISPLAYMODE_LINES:
		{
			// connect points with lines
			gdk_draw_lines (cpm->pixmap, penblue,  points_up, num);
			gdk_draw_lines (cpm->pixmap, pengreen, points_down, num);
		}
		break;
		case STATS_TIMELINE_DISPLAYMODE_BARS:
		default:
		break;
	}
}


/* stats_repaint_get_timeline_parameters
 *
 * sets the parameters that result from different choices of the timeline
 *
 */

static void
stats_repaint_get_timeline_parameters (guint *hunits, guint *tunits, guint *vunits, GPtrArray **statsarr, gchar **unitstr)
{
	/* hunit_table:		number of samples we need to graph
	 * tunit_table:		amount of seconds each single sample is 'worth'
	 * vunit_table:     number of samples between to vertical lines
	 * statsarr_table:	which array to take the samples from
	 */
	const guint	 hunit_table[STATS_TIMELINE_LAST] = {	  60, 	    60,  6*60,	  12,	 24,    48,    72,  7*24,       31,       365 };
	const guint	 tunit_table[STATS_TIMELINE_LAST] = {  	   3,       60,	   60, 60*60, 60*60, 60*60, 60*60, 60*60, 60*60*24,  60*60*24 };
	/* total duration of the graph                       3mn        1h     6h    12h    24h    48h    72h  1week    31days    365days */
	const guint	 vunit_table[STATS_TIMELINE_LAST] = { 	  20, 	    10,    60,	   6,	  6,    24,    24,    24,        1,        30 };
	const gchar* vustr_table[STATS_TIMELINE_LAST]=  {"1 min", "10 min", "1 h", "6 h", "6 h", "1 d", "1 d", "1 d",    "1 d",  "1 month"};
	GPtrArray	*statsarr_table[STATS_TIMELINE_LAST] = { stats_packets, stats_minutes, stats_minutes,
                                                         stats_hours, stats_hours, stats_hours,
                                                         stats_hours, stats_hours,	stats_days, stats_days};

	static gchar  unitstring[128];
	gint          opt_timeline_active;

	g_return_if_fail (hunits!=NULL);
	g_return_if_fail (tunits!=NULL);
	g_return_if_fail (vunits!=NULL);
	g_return_if_fail (statsarr!=NULL);

	*statsarr = NULL;

	opt_timeline_active = opt_get_int (OPT_GUI_STATS_TIMELINE_ACTIVE);

	if ( opt_timeline_active < 0 || opt_timeline_active >= STATS_TIMELINE_LAST )
	{
		opt_timeline_active = STATS_TIMELINE_LAST_3MINS;
		opt_set_int (OPT_GUI_STATS_TIMELINE_ACTIVE, STATS_TIMELINE_LAST_3MINS);
	}

	*hunits   = hunit_table[opt_timeline_active];
	*tunits   = tunit_table[opt_timeline_active];
	*vunits   = vunit_table[opt_timeline_active];
	*statsarr = statsarr_table[opt_timeline_active];

	switch (*tunits)
	{
		case 3:			g_snprintf (unitstring, sizeof(unitstring), _("%u minutes/%s"), (*hunits * *tunits)/60,vustr_table[opt_timeline_active]);
			break;
		case 60:		g_snprintf (unitstring, sizeof(unitstring), _("%u hours/%s"), *hunits/60,vustr_table[opt_timeline_active]);
			break;
		case 60*60:		if (*hunits<=24)
							g_snprintf (unitstring, sizeof(unitstring), _("%u hours/%s"), *hunits,vustr_table[opt_timeline_active]);
						else g_snprintf (unitstring, sizeof(unitstring), _("%u days/%s"), *hunits/24,vustr_table[opt_timeline_active]);
			break;
		case 60*60*24:	g_snprintf (unitstring, sizeof(unitstring), _("%u days/%s"), *hunits,vustr_table[opt_timeline_active]);
			break;
		default:		g_snprintf (unitstring, sizeof(unitstring), "??");
			break;
	}
	*unitstr = unitstring;
}



// stats_repaint_draw_kBs_lines
//
// draws horizontal lines every X kB/s and puts a text label on these lines
// maxsample:	highest kB/s value we got in our sample (both up and down)
// ystep:		the number of vertical pixels for every 1.0kB/s

static void
stats_repaint_draw_kBs_lines (guint maxsample, guint maxx, guint maxy, guint midy, gfloat ystep, gboolean textonly)
{
	gint	ypos, ypos2;
	guint	kBs_per_line = 5;	// how many kB/s the distance between two lines represents
	guint	count = 0;			// counter - we're drawing the n-th line

	g_return_if_fail ( chart        != NULL );
	g_return_if_fail ( chart_layout != NULL );
	g_return_if_fail ( cpm          != NULL );
	g_return_if_fail ( ystep        >  0.0  );

	if (midy<=0)	// don't do anything on pixmap creation (would loop forever)
		return;

	if (maxsample<5) kBs_per_line = 1;
	if (maxsample<10) kBs_per_line = 2;
	if (maxsample>50)  kBs_per_line = 10;
	if (maxsample>100) kBs_per_line = 20;
	if (maxsample>250) kBs_per_line = 50;
	if (maxsample>500) kBs_per_line = 100;

	// label lines above middle
	ypos = ypos2 = midy;
	count = 0;
	while ( ypos > 0  ||  ypos2 < maxy )
	{
		pango_layout_set_text ( chart_layout, UTF8_SHORT_PRINTF("%u kB/s", count*kBs_per_line), -1);

		if (!textonly)
		{
			// draw horizontal line every 5kB/s
			if (ypos>0)
				gdk_draw_line (cpm->pixmap, penblack,  0,  ypos, maxx, ypos);
			if (ypos2<maxy)
				gdk_draw_line (cpm->pixmap, penblack, 0,  ypos2, maxx, ypos2);
//				gdk_draw_line (cpm->pixmap, penblack, 0,  midy+(midy-ypos), maxx, midy+(midy-ypos));
		}

		if ( ypos > 0  &&  ypos < midy )	/* don't label 0kB/s line */
			gdk_draw_layout (cpm->pixmap, penblack, 5, ypos-2, chart_layout);

		if ( ypos2 > midy  &&  ypos2 < maxy )	// don't label 0kB/s line
			gdk_draw_layout (cpm->pixmap, penblack, 5, ypos2-2, chart_layout);
//			gdk_draw_layout (cpm->pixmap, penblack, 5, midy+(midy-ypos)-2, chart_layout);

		ypos  -= ystep*kBs_per_line;
		ypos2 += ystep*kBs_per_line;
		count++;
	}

	// label lines below middle
	ypos = midy;
	count = 0;
	while (ypos < maxy)
	{
		pango_layout_set_text ( chart_layout, UTF8_SHORT_PRINTF("%u kB/s", count*kBs_per_line), -1);

		if (!textonly)
		{
			// draw horizontal line every 5kB/s
			if (ypos>0)
			gdk_draw_line (cpm->pixmap, penblack,  0,  ypos, maxx, ypos);
//			gdk_draw_line (cpm->pixmap, penblack, 0,  midy+(midy-ypos), maxx, midy+(midy-ypos));
		}

		if ( ypos < midy)	// don't label 0kB/s line
		{
			gdk_draw_layout (cpm->pixmap, penblack, 5, ypos-2, chart_layout);
			gdk_draw_layout (cpm->pixmap, penblack, 5, midy+(midy-ypos)-2, chart_layout);
		}

		ypos += ystep*kBs_per_line;
		count++;
	}
}

#if 0

/* stats_get_all_samples_in_interval
 *
 * gets all samples in the intervall [t_from;t_to] from the given samples array
 * returns a GSList with the samples that needs to be freed by the caller
 *
 */

static GSList *
stats_get_all_samples_in_interval (GPtrArray *statsarr, time_t t_from, time_t t_to)
{
	GSList	*samples = NULL;

	g_return_val_if_fail (t_to > t_from, NULL);

	if (statsarr)
	{
		PROCESS_GPTRARRAY ( statsarr, GuiStatsRecord,
			if ((item) && (item->when >= t_from) && (item->when <= t_to))
				samples = g_slist_append (samples, (gpointer)item);
		);
	}
	return samples;
}

#endif

/* stats_repaint
 *
 * repaints our graphs on the pixmap
 *
 */

static void
stats_repaint (void)
{
	GPtrArray		*statsarr = NULL;
	guint			 hunits=0;	/* number of samples (=horizontal 'units')			*/
	guint			 tunits=0;	/* number of seconds each sample represents			*/
	guint			 vunits=0;	/* number of samples between vertical lines			*/
	guint			 vline =0;	/* counter for vertical lines						*/
	guint			 maxx, maxy, xstep, maxupsample, maxdownsample;
	gfloat			 step, opt_up_crop, opt_down_crop;
	gchar			*unitstr = NULL;	/* the interval that is graphed as a string	*/

	g_return_if_fail (cpm!=NULL);
	g_return_if_fail (chart!=NULL);

	opt_up_crop = (gfloat) opt_get_int(OPT_GUI_STATS_UP_CROP);
	opt_down_crop = (gfloat) opt_get_int(OPT_GUI_STATS_DOWN_CROP);

	maxx = chart->allocation.width;
	maxy = chart->allocation.height;

	/* clear drawing area and draw a horizontal line in the middle */
	gdk_draw_rectangle (cpm->pixmap, penwhite, TRUE, 0, 0, maxx, maxy);
	gdk_draw_line (cpm->pixmap, penblack, 0, maxy/2, maxx, maxy/2);

	stats_repaint_get_timeline_parameters (&hunits, &tunits, &vunits, &statsarr, &unitstr );

	if (statsarr)		// maybe we don't have any samples yet...
	{
//		g_print ("Samples to graph: %u (in arr:%u) tunits=%u\n", hunits, statsarr->len, tunits);
		if (stats_find_highest_samples (statsarr, hunits/*tunits*/, &maxupsample, &maxdownsample))
		{
			guint		 maxsample = MAX(maxupsample,maxdownsample)+1;	// 1 for good measure
			gfloat		 ystep = (maxy*1.0)/(maxsample*2.0);
			GdkPoint	*points_up = g_new0 (GdkPoint, hunits+1);
			GdkPoint	*points_down = g_new0 (GdkPoint, hunits+1);
/*			GdkPoint	*points_up = NULL;
			GdkPoint	*points_down = NULL;
*/			guint		 midy, maxupsample2, maxdownsample2, maxvertical;

//			g_print ("max up sample   = %u\n", maxupsample);
//			g_print ("max down sample = %u\n", maxdownsample);
			maxupsample2 = MAX(MAX(maxdownsample/10, maxupsample),1);
			maxdownsample2 = MAX(MAX(maxupsample/10, maxdownsample),1);
			maxvertical = maxupsample2+maxdownsample2+2;	// 2 for good measure

			if (maxupsample == maxdownsample)
				midy = maxy/2;
			else midy = (guint)( (maxy*1.0) * ((maxupsample2*1.0)/(maxvertical*1.0)) );

//			if (maxy-midy<10) midy=maxy-10;
//				if (midy<10) midy=10;
			g_return_if_fail (points_up!=NULL);
			g_return_if_fail (points_down!=NULL);

			// remove old line in exact middle
			gdk_draw_line (cpm->pixmap, penwhite, 0, maxy/2, maxx, maxy/2);

			stats_repaint_draw_kBs_lines (maxsample, maxx, maxy, midy, ystep, FALSE);
//			gdk_draw_line (cpm->pixmap, penblack, 0, midy, maxx, midy);

/* OLD ROUTINE:
   - gets last X samples and takes their values
   - *assumes* that samples are in right order, right intervals + complete
*/
			step = (maxx*1.0)/(hunits*1.0);
			for (xstep=0; xstep<=hunits; xstep++)
			{
				guint		 xpos = (guint) xstep*step;
				guint		 ypos_up = midy;
				guint		 ypos_down = midy;
				GuiStatsRecord	*statsrec = stats_get_sample (statsarr, xstep);

				if (statsrec)
				{
					gfloat upval = CLAMP(statsrec->up, 0.0, opt_up_crop);
					gfloat downval = CLAMP(statsrec->down, 0.0, opt_down_crop);

					ypos_up = midy-(gint)(upval*ystep);
					ypos_down = midy+(gint)(downval*ystep);
				} 

				points_up[xstep].x = maxx-xpos;		// make the graph shift leftwards
				points_up[xstep].y = ypos_up;
				points_down[xstep].x = maxx-xpos;
				points_down[xstep].y = ypos_down;

				if (++vline==vunits)
				{
					gdk_draw_line(cpm->pixmap,penblack,maxx-xpos,0,maxx-xpos,maxy);
					vline=0;
				}
				/* g_print ("vline : %u maxx=%u xpos=%u\n", vline, maxx, xpos); */
			}

#if 0
/* NEW ROUTINE (not finished + not working yet)

   - gets all samples between time t1 and t2
   - plots them on the x-axis depending on t
*/
			if (1==1)
			{
				guint	 interval = hunits * tunits;	/* interval displayed in secs */
				guint	 interval_float = 1.0*interval;
				time_t	 t_from = time(NULL)-interval;
				time_t	 t_to = time(NULL);
				GSList	*samples = stats_get_all_samples_in_interval (statsarr, t_from, t_to);

				if (g_slist_length(samples)>0)
				{
					GSList *node;

					points_up = g_new0(GdkPoint,g_slist_length(samples)+1);
					points_down = g_new0(GdkPoint,g_slist_length(samples)+1);
					xstep = 0;

					for ( node = samples; node != NULL; node = node->next )
					{
						GuiStatsRecord *item = (GuiStatsRecord*)node->data;

						if (xstep>=g_slist_length(samples))
						{
							g_printerr ("debug: in %s - xstep>= g_slist_length(samples)\n", __FUNCTION__);
							continue;
						}
						if (item)
						{
							gfloat	upval = CLAMP(item->up, 0.0, optup_crop);
							gfloat	downval = CLAMP(item->down, 0.0, opt_down_crop);
							gfloat	xpos_float = (((item->when-t_from)*1.0)/interval_float)*(maxx*1.0);
							guint	xpos_int = (guint) xpos_float;
							guint	ypos_up = midy-(gint)(upval*ystep);
							guint	ypos_down = midy+(gint)(downval*ystep);

							/* make the graph shift leftwards */
							points_up[xstep].x = maxx-xpos_int;
							points_up[xstep].y = ypos_up;
							points_down[xstep].x = maxx-xpos_int;
							points_down[xstep].y = ypos_down;
							xstep++;
						}
					}
					g_slist_free(samples);
				}
			}
#endif /* ifdef SKIPTHIS */

			stats_repaint_paint_points (points_up, points_down, hunits+1, step, midy);

			// Now draw the labels on top of the graph again, but not the lines
			stats_repaint_draw_kBs_lines (maxsample, maxx, maxy, midy, ystep, TRUE);

			// draw a new thick line in 'dynamic' vertical middle with an 'arrow'
			gdk_draw_rectangle (cpm->pixmap, penblack, TRUE, 0, midy-1, maxx, 3);
			gdk_draw_arc (cpm->pixmap, penblack, TRUE, maxx-20, midy-10, 40, 20, (180-30)*64, 60*64);

			g_free(points_up);
			g_free(points_down);
		}
	} // else: no samples yet in array

	// display interval
	if ( unitstr )
	{
		pango_layout_set_text ( chart_layout, UTF8_SHORT_PRINTF("%s", unitstr), -1);

		gdk_draw_layout (cpm->pixmap, penblack, maxx-150, 25, chart_layout );
	}

	/* now update the whole drawing area */

	gtk_widget_queue_draw_area (chart, 0, 0, maxx, maxy);

/*	if (1==1)
	{
		GdkRectangle update_area;

		update_area.x = 0;
		update_area.y = 0;
		update_area.width = maxx;
		update_area.height = maxy;

		gtk_widget_draw (chart, &update_area);
	}
*/
}


// stats_create_pen
//
// creates a graphics context for the given colour

static GdkGC *
stats_create_pen (GdkColor *c)
{
	GdkGC *gc;
	g_return_val_if_fail (cpm!=NULL, NULL);
	gc = gdk_gc_new(cpm->pixmap);
	gdk_gc_set_foreground(gc, c);
	return gc;	
}


// stats_configure_event
//
// called when the drawing are is created and/or it's resized

static gint
stats_configure_event (GtkWidget *widget, GdkEventConfigure *event, gpointer data)
{
	if (!cpm)
	{
		cpm = g_new(GuiChartPixmap, 1);
		cpm->gc = NULL;
		cpm->pixmap = NULL;
	}

	// free old pixmap
	if (cpm->pixmap)
		g_object_unref(G_OBJECT(cpm->pixmap));

	// create a new pixmap
	cpm->pixmap = gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1);

	// create pens if we don't have them yet
	if (!penblack)
	{
		penblack = stats_create_pen (&black);
		penred = stats_create_pen (&red);
		penblue = stats_create_pen (&blue);
		pengreen = stats_create_pen (&dark_green);
		penwhite = stats_create_pen (&white);
	}

	// clear new pixmap
	gdk_draw_rectangle (cpm->pixmap, widget->style->white_gc, TRUE, 0,0, widget->allocation.width, widget->allocation.height);
	stats_repaint ();

	return TRUE;
}


// stats_expose_event
//
// called when the drawing are needs to be redrawn in whole or in parts

static gint
stats_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data)
{
	if ((cpm) && (cpm->pixmap))
	{
		// copy relevant part of pixmap over into the drawing area
		gdk_draw_drawable (widget->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], cpm->pixmap,
		                   event->area.x, event->area.y, 
		                   event->area.x, event->area.y, event->area.width, event->area.height);
	}
	return TRUE;
}


// stats_ignore_dialog_ok
//
//

static void
stats_ignore_dialog_ok (GtkWidget *widget, gpointer ignore_dialog)
{
	gchar	*upwstr = gtk_editable_get_chars (GTK_EDITABLE(ew_up_wrong), 0, -1);
	gchar	*upcstr = gtk_editable_get_chars (GTK_EDITABLE(ew_crop_up), 0, -1);
	gchar	*downwstr = gtk_editable_get_chars (GTK_EDITABLE(ew_down_wrong), 0, -1);
	gchar	*downcstr = gtk_editable_get_chars (GTK_EDITABLE(ew_crop_down), 0, -1);
	gint     upwrong=1024, downwrong=2048, upcrop=150, downcrop=150;

	if ((upwstr)&&(upcstr)&&(downwstr)&&(downcstr))
	{
		upwrong   = atoi(upwstr);
		upcrop    = atoi(upcstr);
		downwrong = atoi(downwstr);
		downcrop  = atoi(downcstr);
	}
	else g_printerr (_("Couldn't retrieve text from stats ignore dialog!\n"));

	opt_set_int(OPT_GUI_STATS_UP_WRONG, upwrong);
	opt_set_int(OPT_GUI_STATS_UP_CROP, upcrop);
	opt_set_int(OPT_GUI_STATS_DOWN_WRONG, downwrong);
	opt_set_int(OPT_GUI_STATS_DOWN_CROP, downcrop);

	if (ignore_dialog)
		gtk_widget_destroy(GTK_WIDGET(ignore_dialog));

	G_FREE(upwstr);
	G_FREE(upcstr);
	G_FREE(downwstr);
	G_FREE(downcstr);

	stats_repaint ();
}


// stats_ignore_dialog_destroy 
//
//

static void
stats_ignore_dialog_destroy  (GtkWidget *widget, gpointer ignore_dialog)
{
	if (ignore_dialog)
		gtk_widget_destroy(GTK_WIDGET(ignore_dialog));
}



// stats_popupmenu_item_selected
//
// a button has been pressed or an item from the pop-up menu been selected

static void
stats_popupmenu_item_selected (GtkWidget *widget, gpointer data)
{
	const guint tlarr[STATS_MENUITEM_YEAR+1] =
						{
						 STATS_TIMELINE_LAST_3MINS,	STATS_TIMELINE_LAST_HOUR,
						 STATS_TIMELINE_LAST_6HOURS, STATS_TIMELINE_LAST_12HOURS,
						 STATS_TIMELINE_LAST_24HOURS, STATS_TIMELINE_LAST_48HOURS,
						 STATS_TIMELINE_LAST_72HOURS, STATS_TIMELINE_LAST_WEEK,
						 STATS_TIMELINE_LAST_MONTH, STATS_TIMELINE_LAST_YEAR
						};
	guint menuitem = GPOINTER_TO_UINT(data);

	switch (menuitem)
	{
		case STATS_MENUITEM_3MIN:		case STATS_MENUITEM_HOUR:
		case STATS_MENUITEM_6HOURS:		case STATS_MENUITEM_12HOURS:
		case STATS_MENUITEM_24HOURS:	case STATS_MENUITEM_2DAYS:
		case STATS_MENUITEM_3DAYS:		case STATS_MENUITEM_WEEK:
		case STATS_MENUITEM_MONTH:		case STATS_MENUITEM_YEAR:
			opt_set_int (OPT_GUI_STATS_TIMELINE_ACTIVE, tlarr[menuitem]);
		break;

		case STATS_MENUITEM_NEXT:
			opt_set_int (OPT_GUI_STATS_TIMELINE_ACTIVE, (opt_get_int(OPT_GUI_STATS_TIMELINE_ACTIVE)+1) % STATS_TIMELINE_LAST);
		break;


		/* FIXME: ******************** use dialog_run() here ************************ */
		case STATS_MENUITEM_IGNORE_DIALOG:
		{
			GtkWidget	*ignore_dialog, *button_ok, *button_cancel;
			gchar 		*UP_WRONG	=   _("  Upload speed: values higher than this are definitively wrong (kB/s) ");
			gchar 		*UP_CROP	=	_("  Upload speed: do not DISPLAY values higher than this in graph (kB/s) ");
			gchar 		*DOWN_WRONG	=	_("  Download speed: values higher than this are definitively wrong (kB/s) ");
			gchar 		*DOWN_CROP	=	_("  Download speed: do not DISPLAY values higher than this in graph (kB/s) ");

			ignore_dialog = gtk_dialog_new ();

			g_signal_connect (G_OBJECT (ignore_dialog), "destroy",
			                  G_CALLBACK(stats_ignore_dialog_destroy),
			                  (gpointer) ignore_dialog);

			gtk_window_set_title (GTK_WINDOW (ignore_dialog), UTF8_SHORT_PRINTF("%s",_(" configure upload/download speed values to ignore ")));

			gtk_window_set_position (GTK_WINDOW(ignore_dialog), GTK_WIN_POS_CENTER_ALWAYS);

			new_icon_button_pack_and_signal_connect (&button_ok, GTK_DIALOG(ignore_dialog)->action_area, _("   OK    "),
					    stats_ignore_dialog_ok, (gpointer) ignore_dialog, 1, ICON_OK);

			new_icon_button_pack_and_signal_connect (&button_cancel, GTK_DIALOG(ignore_dialog)->action_area, _(" Cancel "),
					    stats_ignore_dialog_destroy, (gpointer) ignore_dialog, 1, ICON_CANCEL);

			add_new_entry_widget_to_box (GTK_DIALOG(ignore_dialog)->vbox, &ew_up_wrong, UP_WRONG, 10);
			add_new_entry_widget_to_box (GTK_DIALOG(ignore_dialog)->vbox, &ew_crop_up,  UP_CROP, 10);
			add_new_entry_widget_to_box (GTK_DIALOG(ignore_dialog)->vbox, &ew_down_wrong, DOWN_WRONG, 10);
			add_new_entry_widget_to_box (GTK_DIALOG(ignore_dialog)->vbox, &ew_crop_down, DOWN_CROP, 10);
			gtk_entry_set_text (GTK_ENTRY(ew_up_wrong), UTF8_SHORT_PRINTF("%d",opt_get_int(OPT_GUI_STATS_UP_WRONG)));
			gtk_entry_set_text (GTK_ENTRY(ew_down_wrong), UTF8_SHORT_PRINTF("%d",opt_get_int(OPT_GUI_STATS_DOWN_WRONG)));
			gtk_entry_set_text (GTK_ENTRY(ew_crop_up), UTF8_SHORT_PRINTF("%d",opt_get_int(OPT_GUI_STATS_UP_CROP)));
			gtk_entry_set_text (GTK_ENTRY(ew_crop_down), UTF8_SHORT_PRINTF("%d",opt_get_int(OPT_GUI_STATS_DOWN_CROP)));

			gtk_widget_show(ignore_dialog);
		}
		break;

		default:	g_printerr (_("BUG: unknown menuitem number in %s\n"), __FUNCTION__);
		break;
	}
	stats_repaint();
}


/******************************************************************************
 *
 *   stats_onPopupMenu
 *
 ***/

static void
stats_onPopupMenu (GtkWidget *widget, gpointer data)
{
	GtkWidget *menu;

	gboolean   stats_minutes_has_data = ((stats_minutes) && stats_minutes->len>0);
	gboolean   stats_hours_has_data   = ((stats_hours) && stats_hours->len>0);
	gboolean   stats_days_has_data    = ((stats_days) && stats_days->len>0);

	menu = gtk_menu_new();

  misc_gtk_add_menu_header(menu, _("Switch interval displayed"), NULL);

	misc_gtk_add_menu_item ( menu, _(" last 3 minutes"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_3MIN), ICON_STATS, TRUE );

	misc_gtk_add_menu_item ( menu, _(" last hour"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_HOUR), ICON_STATS, stats_minutes_has_data );

	misc_gtk_add_menu_item ( menu, _(" last 6 hours"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_6HOURS), ICON_STATS, stats_minutes_has_data );

	misc_gtk_add_menu_item ( menu, _(" last 12 hours"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_12HOURS), ICON_STATS, stats_hours_has_data );

	misc_gtk_add_menu_item ( menu, _(" last 24 hours"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_24HOURS), ICON_STATS, stats_hours_has_data );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" last 2 days"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_2DAYS), ICON_STATS, stats_hours_has_data );

	misc_gtk_add_menu_item ( menu, _(" last 3 days"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_3DAYS), ICON_STATS, stats_hours_has_data );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" last week"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_WEEK), ICON_STATS, stats_hours_has_data );

	misc_gtk_add_menu_item ( menu, _(" last month"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_MONTH), ICON_STATS, stats_days_has_data );

	misc_gtk_add_menu_item ( menu, _(" last year"), stats_popupmenu_item_selected,
                             GUINT_TO_POINTER(STATS_MENUITEM_YEAR), ICON_STATS, stats_days_has_data );

	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, gdk_event_get_time(NULL));
}


/******************************************************************************
 *
 *   stats_button_press_event
 *
 ***/

static gint
stats_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	g_return_val_if_fail ( event != NULL, FALSE );

	/* single right-click? */
	if ( event->type != GDK_BUTTON_PRESS )
		return FALSE;

	if ( event->button != 1 && event->button != 3 )
		return FALSE;

	stats_onPopupMenu (NULL, NULL);

	return TRUE;
}



// stats_mode_button_clicked
//
// switches to the next display mode

static void
stats_mode_button_clicked (GtkWidget *widget, gpointer data)
{
	gint opt_tlm;

	opt_tlm = opt_get_int (OPT_GUI_STATS_TIMELINE_MODE);

	opt_tlm = (opt_tlm + 1) % STATS_TIMELINE_DISPLAYMODE_LAST;

	opt_set_int (OPT_GUI_STATS_TIMELINE_MODE, opt_tlm);

	stats_repaint();
}


/***************************************************************************
 *
 *   everyFiveMinutes
 *
 ***************************************************************************/

static gboolean
everyFiveMinutes (gpointer data)
{
	stats_save_to_disk();
	return TRUE; /* call us again */
}

/***************************************************************************
 *
 *   stats_create_page
 *
 *   creates a drawing area for the statistics graphs
 *
 ***************************************************************************/

GtkWidget *
stats_create_page(void)
{
	GtkWidget	*hbox, *vbox, *button_mode, *button_timeline, *button_ignore;

	stats_read_from_disk();

	vbox = gtk_vbox_new(FALSE,5);
	hbox = gtk_hbox_new(FALSE,5);

	chart = gtk_drawing_area_new();

	chart_layout = gtk_widget_create_pango_layout( chart, NULL );

	g_signal_connect (G_OBJECT(chart), "expose_event", G_CALLBACK(stats_expose_event), NULL);
	g_signal_connect (G_OBJECT(chart), "configure_event", G_CALLBACK(stats_configure_event), NULL);
	g_signal_connect (G_OBJECT(chart), "button_press_event", G_CALLBACK(stats_button_press_event), NULL);
	g_signal_connect (G_OBJECT(chart), "popup_menu", G_CALLBACK(stats_onPopupMenu), NULL);

	gtk_widget_set_events (GTK_WIDGET(chart), GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);

	new_icon_button_pack_and_signal_connect (&button_mode, hbox, _(" switch graph mode "),
                                                stats_mode_button_clicked, NULL, 1, ICON_STATS );

	new_icon_button_pack_and_signal_connect ( &button_timeline, hbox,
	                                           _(" switch interval displayed "),
	                                           stats_onPopupMenu, NULL, 1, ICON_STATS );

	new_icon_button_pack_and_signal_connect ( &button_ignore, hbox,
	                                          _(" ignore values... "),
	                                          stats_popupmenu_item_selected,
	                                          GUINT_TO_POINTER(STATS_MENUITEM_IGNORE_DIALOG), 1, ICON_STATS );

	GTK_WIDGET_SET_FLAGS( button_mode,     GTK_CAN_DEFAULT );
	GTK_WIDGET_SET_FLAGS( button_timeline, GTK_CAN_DEFAULT );
	GTK_WIDGET_SET_FLAGS( button_ignore,   GTK_CAN_DEFAULT );

	gtk_box_pack_start (GTK_BOX(vbox), chart, TRUE, TRUE, 5);
	gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 5);

	gtk_widget_show(hbox);
	gtk_widget_show(vbox);
	gtk_widget_show(chart);

	g_signal_connect(core, "speed-total", (GCallback) stats_onCoreSpeedTotal, NULL);

	g_atexit(&stats_save_to_disk);

	g_timeout_add(5*60*1000, (GSourceFunc) everyFiveMinutes, NULL);

	return vbox;
}


// stats_save_array_to_disk
//
// writes the data from the given array of samples to the given file,
//	using the marker provided

static void
stats_save_array_to_disk (GPtrArray *arr, gchar marker, FILE *f)
{
	g_return_if_fail (f!=NULL);
	if (arr)
	{
		PROCESS_GPTRARRAY (arr, GuiStatsRecord,
			if (item)
			{
				fprintf (f, "%c %f %f 0 0 %lu\n", marker, item->up, item->down, item->when);
			}
		);
	}
}


/***************************************************************************
 *
 *   stats_save_to_disk
 *
 *   Saves some statistic data to disk
 *    * the general up/download stats (up/down total, secs up/downloaded)
 *    * the up/down rates over time
 *
 ***************************************************************************/

static void
stats_save_to_disk (void)
{
	const gchar  *filename;
	FILE         *f;

	filename = opt_get_opt_filename("gui_stats.txt");

	g_return_if_fail ( filename != NULL );

	if ((f = fopen(filename, "w") )!=NULL)
	{
		fprintf (f, "# ed2k_gui --- gui_stats --- config format version 0.3.0\n");
		fprintf (f, _("#\n# This file is created by the ed2k gui. It contains your\n"));
		fprintf (f, _("# overall statistics. DO NOT CHANGE THE ABOVE HEADER!\n#\n"));
		fprintf (f, _("# total uploaded - total downloaded - stats last cleared - secs DL was nonzero - secs UL was nonzero\n"));
		fprintf (f, "GENERAL STATS: %f %f %lu %lu %lu\n",
					total_uploaded,
					total_downloaded,
					stats_last_cleared,
					seconds_download_was_nonzero,
					seconds_upload_was_nonzero);
		fprintf (f, "FIRST OF TIMES: %lu %lu %lu %lu\n",
					time_first_of_packets,
					time_first_of_minutes,
					time_first_of_hours,
					time_first_of_days);
		if (stats_packets) stats_save_array_to_disk (stats_packets, 'p', f);
		if (stats_minutes) stats_save_array_to_disk (stats_minutes, 'm', f);
		if (stats_hours) stats_save_array_to_disk (stats_hours, 'h', f);
		if (stats_days) stats_save_array_to_disk (stats_days, 'd', f);

		fclose (f);
	} else status_system_error_msg(_("couldn't save stats.txt"));
}


/***************************************************************************
 *
 *   stats_read_from_disk
 *
 ***************************************************************************/

static void
stats_read_from_disk (void)
{
	const gchar   *cfn;
	time_t         newest_record = 0;
	gchar        **line, **linearray;
	guint          vermaj, vermin, verminmin;

	cfn = opt_get_opt_filename("gui_stats.txt");

	/* init this properly in case file isn't there */
	stats_last_cleared = time(NULL);

	g_return_if_fail ( cfn != NULL );

	/* Now read in the file */
	linearray = misc_get_array_of_lines_from_textfile (cfn, FALSE, TRUE);

	if (!linearray)
		return;

	line = linearray;
	if ((*line==NULL) || sscanf(*line, "# ed2k_gui --- gui_stats --- config format version %u.%u.%u\n", &vermaj, &vermin, &verminmin)!=3)
	{
		g_printerr (_("gui_stats.txt: invalid header?\n"));
		g_strfreev (linearray);
		return;
	}

	while (*(++line))
	{
		gfloat	up, down;
		time_t	when;
		gchar	marker;

		if (**line=='#' || **line==0x00)
			continue;

		if ( g_ascii_strncasecmp(*line, "GENERAL STATS:", 13) == 0 )
		{
			sscanf(*line, "GENERAL STATS: %f %f %lu %lu %lu\n",
				&total_uploaded,
				&total_downloaded,
				&stats_last_cleared,
				&seconds_download_was_nonzero,
				&seconds_upload_was_nonzero
			);
			continue;
		}

		if (g_ascii_strncasecmp(*line, "FIRST OF TIMES:", 12)==0)
		{
			sscanf (*line, "FIRST OF TIMES: %lu %lu %lu %lu\n",
				&time_first_of_packets,
				&time_first_of_minutes,
				&time_first_of_hours,
				&time_first_of_days
			);
			continue;
		}

		if (4 == sscanf(*line, "%c %f %f 0 0 %lu\n", &marker, &up, &down, &when))
		{
			static gfloat	  lastup=0.0, lastdown=0.0;
			GuiStatsRecord		 *statrec = g_new0(GuiStatsRecord,1);
			GPtrArray		**addarr = NULL;
			g_return_if_fail (statrec!=NULL);

			if (up >= (gfloat) opt_get_int(OPT_GUI_STATS_UP_WRONG))
				up = lastup;
			if (down >= (gfloat) opt_get_int(OPT_GUI_STATS_DOWN_WRONG))
				down = lastdown;

			lastup=up;
			lastdown=down;
			statrec->up = up;
			statrec->down = down;
			statrec->when = when;

			if (when>newest_record)
				newest_record=when;

			switch (marker)	// bad style, but shorter
			{
				case 'p': addarr = &stats_packets;
				case 'm': if (!addarr) addarr = &stats_minutes;
				case 'h': if (!addarr) addarr = &stats_hours;
				case 'd': if (!addarr) addarr = &stats_days;
				break;
				default: g_printerr (_("Unknown marker in gui_stats.txt (ignored)\n"));
				break;
			}

			if (addarr)
			{
				if (!*addarr) *addarr = g_ptr_array_new();
				g_ptr_array_add (*addarr, (gpointer)statrec);
			} else g_printerr ("addarr==NULL!?\n");
		}
	}

	/* newest_record>0 can only happen if we've done the above loop else newest_record==0 */
	if (newest_record == 0)
	{
		g_printerr (_("gui_stats.txt: No valid stats records found after reading the file [harmless error].\n"));
		g_strfreev (linearray);
		return;
	} else {
		gint 	i;
		gint 	diff = time(NULL)-newest_record;
		time_t	newest_record_then = newest_record;	// while we register, the content in newest_record changes

		// Fill the time between last GUI shutdown and now with empty values
		if (diff>=3)
		{
			for (i=0; i<diff/3; i++)
			{
				stats_onCoreSpeedTotal (core, 0.0, 0.0, GUINT_TO_POINTER((guint)(newest_record_then+(i*3))));
			}
		}
	}
	g_strfreev (linearray);
}


