/*
** 1998-09-29 -	This is a Russion space shuttle. It is also the module responsible for
**		reporting various commands' progress to the user, and providing a nice
**		way of aborting commands prematurely.
** 1999-01-30 -	Now cancels operation if ESCape is hit. Pretty convenient.
** 1999-03-05 -	Adapted for new selection management. Also fixes a couple of minor bugs.
*/

#include "gentoo.h"

#include <sys/stat.h>
#include <unistd.h>

#include <gdk/gdkkeysyms.h>

#include "fileutil.h"
#include "dirpane.h"
#include "sizeutil.h"
#include "progress.h"

/* ----------------------------------------------------------------------------------------- */

static struct {
	guint		level;		/* Current begin/end nesting level. Keep first!! */
	guint32		flags;		/* Flags from original progress_begin() call. */
	guint		cancel;		/* Gets set as soon as user clicks the button. */
	guint		show_byte : 1;	/* Show total bytes progress bar? */
	guint		show_item : 1;	/* Show the item bar? */
	guint		delayed : 1;	/* Set to 1 during initial display delay. */
	guint		tot_num;	/* Total number of items to process. */
	guint		tot_pos;	/* Index of current item. */
	guint64		byte_tot;	/* Total bytes in operation. */
	guint64		byte_pos;	/* Position, in bytes. */
	guint32		item_size;	/* Size of current item being processed. */
	guint32		item_pos;	/* Position in the item. */
	struct timeval	time_begin;	/* The time when pgs_progress_begin() was called. */
	guint		delay_time;	/* Delay until display, in microseconds (!). */

	MainInfo	*min;
	GtkWidget	*dlg;
	GtkWidget	*body;
	GtkWidget	*tot_pgs;

	GtkWidget	*byte_pgs;
	GtkWidget	*byte_high;

	GtkWidget	*item;
	GtkWidget	*item_pgs;
	GtkWidget	*item_high;
} pgs_info = { 0 };

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-30 -	Count number of top-level selections.
** 1998-10-01 -	Added size support.
*/
static guint count_toplevel_selected(MainInfo *min, guint64 *size)
{
	DirPane	*dp = min->gui->cur_dir;
	GSList	*slist, *iter;
	guint	num = 0;

	if(size != NULL)
		*size = 0;

	for(iter = slist = dp_get_selection(dp); iter != NULL; iter = g_slist_next(iter), num++)
	{
		if(size)
			*size += (guint64) DP_SEL_LSTAT(iter).st_size;
	}
	dp_free_selection(slist);
	return num;
}

/* 1998-09-30 -	Count number of items recursively from all selections.
** 1998-10-01 -	Added size support.
** 1998-12-20 -	Now uses the fut_dir_size() function for the recursive scanning.
*/
static int count_recursive_selected(MainInfo *min, guint64 *size)
{
	DirPane	*dp = min->gui->cur_dir;
	GSList	*slist, *iter;
	guint	num = 0;

	if(size != NULL)
		*size = 0;
	for(iter = slist = dp_get_selection(dp); iter != NULL; iter = g_slist_next(iter), num++)
	{
		if(size)
			*size += (gsize) DP_SEL_LSTAT(iter).st_size;
		if(S_ISDIR(DP_SEL_LSTAT(iter).st_mode))
		{
			FUCount	fu;

			if(fut_dir_size(min, dp_full_name(dp, DP_SEL_INDEX(dp, iter)), size, &fu))
				num += fu.num_total;
		}
	}
	dp_free_selection(slist);
	return num;
}

/* 1998-09-30 -	A callback for the big "Cancel" button. */
static gint evt_cancel_clicked(GtkWidget *wid, gpointer user)
{
	int	*cancel = (int *) user;

	*cancel = 1;
	errno = EINTR;
	return TRUE;
}

static gint evt_keypress(GtkWidget *wid, GdkEventKey *evt, gpointer user)
{
	int	*cancel = (int *) user;

	if(evt->keyval == GDK_Escape)
	{
		*cancel = TRUE;
		errno = EINTR;
	}

	return TRUE;
}

/* 1998-09-30 -	Begin a new "session" with progress reporting. */
void pgs_progress_begin(MainInfo *min, const gchar *op_name, guint32 flags)
{
	char		size_buf[32], buf[128];
	GtkWidget	*cancel, *hbox, *label;

	if(pgs_info.level == 0)
	{
		if(flags & PFLG_BUSY_MODE)
			flags &= PFLG_BUSY_MODE;	/* Clear any other flag. */

		pgs_info.flags	    = flags;
		pgs_info.show_byte  = (flags & PFLG_BYTE_VISIBLE) ? TRUE : FALSE;
		pgs_info.show_item  = (flags & PFLG_ITEM_VISIBLE) ? TRUE : FALSE;
		pgs_info.cancel	    = 0;
		pgs_info.delayed    = 1;
		pgs_info.delay_time = 250000;
		pgs_info.byte_pos   = 0;

		if(flags & PFLG_COUNT_RECURSIVE)
			pgs_info.tot_num = count_recursive_selected(min, &pgs_info.byte_tot);
		else
			pgs_info.tot_num = count_toplevel_selected(min, &pgs_info.byte_tot);

		pgs_info.tot_pos = 0;

		pgs_info.dlg = gtk_dialog_new();
		pgs_info.body = gtk_label_new(op_name);
		gtk_signal_connect(GTK_OBJECT(pgs_info.dlg), "key_press_event", GTK_SIGNAL_FUNC(evt_keypress), &pgs_info.cancel);
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(pgs_info.dlg)->vbox), pgs_info.body, FALSE, FALSE, 0);
		gtk_widget_show(pgs_info.body);

		hbox = gtk_hbox_new(FALSE, 0);
		pgs_info.tot_pgs = gtk_progress_bar_new();
		if(flags & PFLG_BUSY_MODE)
		{
			gtk_progress_set_activity_mode(GTK_PROGRESS(pgs_info.tot_pgs), TRUE);
			gtk_progress_bar_set_activity_step(GTK_PROGRESS_BAR(pgs_info.tot_pgs), 3);
			gtk_progress_bar_set_activity_blocks(GTK_PROGRESS_BAR(pgs_info.tot_pgs), 10);
			gtk_box_pack_start(GTK_BOX(hbox), pgs_info.tot_pgs, TRUE, TRUE, 0);
		}
		else
		{
			label = gtk_label_new("0");
			gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
			gtk_widget_show(label);
			gtk_box_pack_start(GTK_BOX(hbox), pgs_info.tot_pgs, TRUE, TRUE, 0);
			g_snprintf(buf, sizeof buf, "%d", pgs_info.tot_num);
			label = gtk_label_new(buf);
			gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 5);
			gtk_widget_show(label);
		}
		gtk_progress_bar_update(GTK_PROGRESS_BAR(pgs_info.tot_pgs), 0.0f);
		gtk_widget_show(pgs_info.tot_pgs);
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(pgs_info.dlg)->vbox), hbox, FALSE, FALSE, 0);
		gtk_widget_show(hbox);

		if(pgs_info.show_byte)
		{
			sze_put_size(pgs_info.byte_tot, size_buf, sizeof size_buf, SZE_AUTO);
			g_snprintf(buf, sizeof buf, "Total (%s)", size_buf);
			label = gtk_label_new(buf);
			gtk_box_pack_start(GTK_BOX(GTK_DIALOG(pgs_info.dlg)->vbox), label, FALSE, FALSE, 0);
			gtk_widget_show(label);
			pgs_info.byte_pgs = gtk_progress_bar_new();
			gtk_box_pack_start(GTK_BOX(GTK_DIALOG(pgs_info.dlg)->vbox), pgs_info.byte_pgs, TRUE, TRUE, 0);
			gtk_widget_show(pgs_info.byte_pgs);
		}

		if(pgs_info.show_item)
		{
			pgs_info.item = gtk_label_new("");
			gtk_box_pack_start(GTK_BOX(GTK_DIALOG(pgs_info.dlg)->vbox), pgs_info.item, FALSE, FALSE, 5);
			gtk_widget_show(pgs_info.item);
			hbox = gtk_hbox_new(FALSE, 0);
			pgs_info.item_pgs = gtk_progress_bar_new();
			gtk_box_pack_start(GTK_BOX(hbox), pgs_info.item_pgs, TRUE, TRUE, 0);
			gtk_progress_bar_update(GTK_PROGRESS_BAR(pgs_info.item_pgs), 0.0f);
			gtk_widget_show(pgs_info.item_pgs);
			gtk_box_pack_start(GTK_BOX(GTK_DIALOG(pgs_info.dlg)->vbox), hbox, FALSE, FALSE, 0);
			gtk_widget_show(hbox);
		}

		cancel = gtk_button_new_with_label("Cancel");
		gtk_signal_connect(GTK_OBJECT(cancel), "clicked", GTK_SIGNAL_FUNC(evt_cancel_clicked), (gpointer) &pgs_info.cancel);
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(pgs_info.dlg)->action_area), cancel, TRUE, TRUE, 0);
		gtk_widget_show(cancel);

		gettimeofday(&pgs_info.time_begin, NULL);
	}
	else
		fprintf(stderr, "**PROGRESS: No nesting, please\n");
	pgs_info.level++;
}

/* 1998-09-30 -	End a progress-reporting session. Closes down the dialog box. */
void pgs_progress_end(MainInfo *min)
{
	if(pgs_info.level == 1)
	{
		gtk_widget_destroy(pgs_info.dlg);
		pgs_info.dlg = NULL;
	}
	pgs_info.level--;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-30 -	Begin on a new "item"; typically a file, being <size> bytes. */
void pgs_progress_item_begin(MainInfo *min, const gchar *name, gsize size)
{
	gchar	size_buf[32], buf[FILENAME_MAX + 32];

	if(pgs_info.flags & PFLG_BUSY_MODE)
	{
		GtkAdjustment	*adj;
		gfloat		val;
		static gint	scale = 10;

		if(!--scale)
		{
			adj = GTK_PROGRESS(pgs_info.tot_pgs)->adjustment;
			val = adj->value + 1;
			if(val > adj->upper)
				val = adj->lower;
			gtk_progress_set_value(GTK_PROGRESS(pgs_info.tot_pgs), val);
			scale = 10;
		}
	}
	else
	{
		pgs_info.tot_pos++;
		pgs_info.byte_pos += size;
		gtk_progress_bar_update(GTK_PROGRESS_BAR(pgs_info.tot_pgs), (float) pgs_info.tot_pos / pgs_info.tot_num);
	}

	pgs_info.item_size = size;

	if(pgs_info.show_item)
	{
		sze_put_size(size, size_buf, sizeof size_buf, SZE_AUTO);
		g_snprintf(buf, sizeof buf, "%s (%s)", name, size_buf);
		gtk_label_set_text(GTK_LABEL(pgs_info.item), buf);
	}
	while(gtk_events_pending())
		gtk_main_iteration();
}

/* 1998-09-30 -	Indicate progress operating on the most recently registered item. Our
**		current position in the item is <pos> bytes.
*/
PgsRes pgs_progress_item_update(MainInfo *min, guint32 pos)
{
	gfloat		p = (float) pos / (float) pgs_info.item_size;
	struct timeval	time_now;
	gint		micro;

	if(pgs_info.dlg != NULL)
	{
		if(pgs_info.delayed)
		{
			gettimeofday(&time_now, NULL);
			micro = 1E6 * (time_now.tv_sec - pgs_info.time_begin.tv_sec) + 
				      (time_now.tv_usec - pgs_info.time_begin.tv_usec);
			if(micro >= pgs_info.delay_time)
				pgs_info.delayed = 0;
		}
		if(!pgs_info.delayed)
			gtk_widget_show(pgs_info.dlg);
		if(pgs_info.show_item && pgs_info.item_size)
		{
			p = (gfloat) pos / (float) pgs_info.item_size;
			gtk_progress_bar_update(GTK_PROGRESS_BAR(pgs_info.item_pgs), p);
		}
		while(gtk_events_pending())
			gtk_main_iteration();
		if(pgs_info.cancel)
			return PGS_CANCEL;
	}
	return PGS_PROCEED;
}

/* 1998-09-30 -	Done with an item. */
void pgs_progress_item_end(MainInfo *min)
{
	gint	old_errno = errno;

	if(pgs_info.show_byte && pgs_info.byte_tot)
		gtk_progress_bar_update(GTK_PROGRESS_BAR(pgs_info.byte_pgs), 1.0);
	while(gtk_events_pending())
		gtk_main_iteration();

	errno = old_errno;
}
