#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <gtk/gtk.h>
#include <unistd.h>

#include "../../include/fio.h"
#include "../../include/disk.h"
#include "../../include/prochandle.h"

#include "../guiutils.h"
#include "../cdialog.h"
#include "../edv_types.h"

#include "formatmanager.h"
#include "formatcb.h"
#include "config.h"


gint FormatManagerButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);
gint FormatManagerDiskIconExposeCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
);

gint FormatManagerFormatTOCB(gpointer data);

gint FormatManagerDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);

static void FormatManagerDoStart(
	format_manager_struct *fm, gint dev_num
);
void FormatManagerStartCB(GtkWidget *widget, gpointer data);
void FormatManagerStopCB(GtkWidget *widget, gpointer data);
void FormatManagerClearCB(GtkWidget *widget, gpointer data);
void FormatManagerCloseBtnCB(GtkWidget *widget, gpointer data);

void FormatManagerSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);
void FormatManagerUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)

#define KILL(p,s)       (((p) > 0) ? kill((p), (s)) : -1)
#define UNLINK(p)       (((p) != NULL) ? unlink(p) : -1)


/*
 *	GtkCList "button_press_event" or "button_release_event"
 *	signal callback.
 */
gint FormatManagerButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	GtkCList *clist;
	format_manager_struct *fm = FORMAT_MANAGER(data);
	if((widget == NULL) || (button == NULL) || (fm == NULL))
	    return(status);

	clist = GTK_CLIST(widget);
	if(clist->clist_window != ((GdkEventAny *)button)->window)
	    return(status);


	/* Handle by which widget this event is for */

	/* Devices List? */
	if(fm->devices_clist == widget)
	{
	    gint row, column;
	    GtkWidget *w;


	    /* Find row and column based on given coordinates */
	    if(!gtk_clist_get_selection_info(
		clist, button->x, button->y, &row, &column
	    ))
	    {
		row = -1;
		column = 0;
	    }

	    /* Handle by event type */
	    switch((gint)button->type)
	    {
	      case GDK_2BUTTON_PRESS:
		/* Handle by button number */
		switch(button->button)
		{
		  case 1:
		    /* Do start */
		    FormatManagerStartCB(widget, data);
		    status = TRUE;
		    break;
		}
		break;

	      case GDK_BUTTON_PRESS:
		/* Handle by button number */
		switch(button->button)
		{
		  case 3:
		    gtk_clist_freeze(clist);
		    if(!(button->state & GDK_CONTROL_MASK) &&
		       !(button->state & GDK_SHIFT_MASK)
		    )
			gtk_clist_unselect_all(clist);
		    clist->focus_row = row;
		    gtk_clist_select_row(clist, row, column);
		    gtk_clist_thaw(clist);

		    /* Get right click menu widget and map it */
		    w = fm->devices_menu;
		    if(w != NULL)
			gtk_menu_popup(
			    GTK_MENU(w), NULL, NULL,
			    NULL, NULL,
			    button->button, button->time
			);

		    status = TRUE;
		    break;
		}
		break;

	      case GDK_BUTTON_RELEASE:
		break;
	    }
	}

	return(status);
}

/*
 *	Redraws the disk animation icon on the format manager window.
 *
 *	A new frame will be drawn each time.
 */
gint FormatManagerDiskIconExposeCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
)
{
	gint i, state, width, height;
	GdkGC *gc;
	GdkWindow *window;
	GdkPixmap *pixmap, *bg_pixmap;
	GtkStyle *style;
	GtkWidget *w;

	format_manager_struct *fm = FORMAT_MANAGER(data);
	if(fm == NULL)
	    return(FALSE);

	w = fm->disk_anim_da;
	if(w == NULL)
	    return(FALSE);

	gc = fm->gc;
	window = w->window;
	pixmap = fm->disk_anim_pm;
	if((gc == NULL) || (window == NULL) || (pixmap == NULL))
	    return(FALSE);

	state = GTK_WIDGET_STATE(w);
	style = gtk_widget_get_style(w);
	gdk_window_get_size((GdkWindow *)pixmap, &width, &height);
	if((style == NULL) || (width <= 0) || (height <= 0))
	    return(FALSE);


	/* Begin drawing */
	bg_pixmap = style->bg_pixmap[state];
	if(bg_pixmap != NULL)
	{
	    gint tx, ty;

	    gdk_gc_set_tile(gc, bg_pixmap);
	    gdk_gc_set_fill(gc, GDK_TILED);
	    gdk_window_get_position(window, &tx, &ty);
	    gdk_gc_set_ts_origin(gc, -tx, -ty);
	}
	else
	{
	    gdk_gc_set_fill(gc, GDK_SOLID);
	}
	gdk_gc_set_clip_mask(gc, NULL);

	/* Draw background to cover entire pixmap buffer */
	gdk_gc_set_foreground(gc, &style->bg[state]);
	gdk_draw_rectangle(
	    (GdkDrawable *)pixmap, gc, TRUE,
	    0, 0, width, height
	);

	gdk_gc_set_fill(gc, GDK_SOLID);
	gdk_gc_set_ts_origin(gc, 0, 0);

	/* Find which disk animation icon we want to draw */
	i = fm->last_disk_icon_num;
	if((i >= 0) && (i < fm->total_disk_icons))
	{
	    gdk_gc_set_clip_mask(gc, fm->disk_icon_mask[i]);

	    if(fm->disk_icon_pixmap[i] != NULL)
		gdk_draw_pixmap(
		    pixmap, gc,
		    fm->disk_icon_pixmap[i],
		    0, 0,
		    0, 0,
		    width, height
		);
	}

	/* At this point the pixmap buffer has been drawn, now put the
	 * pixmap buffer to the drawing area's window
	 */
	gdk_draw_pixmap(
	    window, gc,
	    pixmap,
	    0, 0,
	    0, 0,
	    width, height
	);

	return(TRUE);
}

/*
 *	Format timeout callback.
 *
 *	Called while a format or mkfs process is running, the process is
 *	monitored and the appropriate action is taken.
 */
gint FormatManagerFormatTOCB(gpointer data)
{
	FILE *fp;
	pid_t p;
	format_manager_struct *fm = FORMAT_MANAGER(data);
	if(fm == NULL)
	    return(FALSE);

/* Resets the stop count, restores progress bar to non-checking state,
 * marks the current format_toid=0, closes format stdout and
 * stderr streams, removes format stdout and stderr tempory files,
 * removes the current device from the queued list of devices, and
 * starts another check process for the next queued device (if any)
 */
#define DO_PROCESS_END	{			\
 fm->stop_count = 0;				\
 FormatManagerSetProgress(fm, 0.0f, FALSE);	\
						\
 fm->format_toid = 0;				\
						\
 KILL(fm->format_pid, SIGTERM);			\
 fm->format_pid = 0;				\
						\
 FClose(fm->stdout_fp);				\
 fm->stdout_fp = NULL;				\
 FClose(fm->stderr_fp);				\
 fm->stderr_fp = NULL;				\
						\
 UNLINK(fm->stdout_path);			\
 g_free(fm->stdout_path);			\
 fm->stdout_path = NULL;			\
						\
 UNLINK(fm->stderr_path);			\
 g_free(fm->stderr_path);			\
 fm->stderr_path = NULL;			\
						\
 /* Able to go to next format stage? */		\
 if(fm->format_stage < FORMAT_STAGE_HIGHEST) {	\
  /* Get current queued device to format */	\
  GList *glist = fm->queued_device_to_format;	\
  if(glist != NULL) {				\
   gint dev_num = (gint)gtk_clist_get_row_data(	\
    (GtkCList *)fm->devices_clist,		\
    (gint)glist->data   /* Row index */		\
   );						\
   fm->format_stage++;				\
   FormatManagerDoStart(fm, dev_num);		\
  }						\
 }						\
 /* Already past highest format stage, so see	\
  * if there is another queued device to	\
  * format?					\
  */						\
 else if(fm->queued_device_to_format != NULL) {	\
  /* Remove the last queued device from the	\
   * list					\
   */						\
  GList *glist = fm->queued_device_to_format;	\
  GList *glist2 = NULL;				\
  if(glist->next != NULL)			\
   glist2 = g_list_copy(glist->next);		\
  fm->queued_device_to_format = glist2;		\
  g_list_free(glist);				\
						\
  /* Check if there is a current device to	\
   * format, if so then format it		\
   */						\
  glist = fm->queued_device_to_format;		\
  if(glist != NULL) {				\
   gint dev_num = (gint)gtk_clist_get_row_data(	\
    (GtkCList *)fm->devices_clist,		\
    (gint)glist->data	/* Row index */	\
   );						\
   fm->format_stage = FORMAT_STAGE_FORMAT;	\
   FormatManagerDoStart(fm, dev_num);		\
  }						\
  else						\
  {						\
   /* No other queued devices, we're done with	\
    * everything				\
    */						\
   FormatManagerAppendMessage(			\
    fm, fm->text_font, NULL, NULL,		\
    "Format done.\n", TRUE			\
   );						\
  }						\
 }						\
						\
 FormatManagerUpdateMenus(fm);			\
						\
}

	/* Need to stop? */
	if(fm->stop_count > 0)
	{
	    /* First remove all queued devices and set the format
	     * stage to the highest, then DO_PROCESS_END so that
	     * no other queued devices are processed and everything
	     * is properly stopped
	     */
	    g_list_free(fm->queued_device_to_format);
	    fm->queued_device_to_format = NULL;
	    fm->format_stage = FORMAT_STAGE_HIGHEST;

EDVPlaySoundWarning(fm->ctx);
FormatManagerAppendMessage(
 fm, fm->text_font, &fm->error_color, NULL,
 "\n*** Format Interrupted! ***\n",
 TRUE
);

	    /* Now process end */
	    DO_PROCESS_END
	    return(FALSE);
	}



	/* Read from stdout output file */
	fp = fm->stdout_fp;
	if(fp != NULL)
	{
	    gint bytes_read = 1;
	    gchar buf[1024];

	    while(bytes_read > 0)
	    {
		bytes_read = (gint)fread(
		    (char *)buf, sizeof(gchar), sizeof(buf), fp
		);
		if(bytes_read > 0)
		{
		    gint len = bytes_read;
		    if(len >= sizeof(buf))
			len = sizeof(buf) - 1;

		    buf[len] = '\0';

		    FormatManagerAppendMessage(
			fm,
			fm->text_font,
			NULL, NULL,
			buf, TRUE
		    );
		}
	    }
	}

	/* Read from stderr output file */
	fp = fm->stderr_fp;
	if(fp != NULL)
	{
	    gint bytes_read = 1;
	    gchar buf[1024];

	    while(bytes_read > 0)
	    {
		bytes_read = (gint)fread(
		    (char *)buf, sizeof(gchar), sizeof(buf), fp
		);
		if(bytes_read > 0)
		{
		    gint len = bytes_read;
		    if(len >= sizeof(buf))
			len = sizeof(buf) - 1;

		    buf[len] = '\0';

		    FormatManagerAppendMessage(
			fm,
			fm->text_font,
			&fm->error_color, NULL,
			buf, TRUE
		    );
		}
	    }
	}



	/* Process no longer exists? */
	p = fm->format_pid;
	if(!ExecProcessExists(p))
	{
	    /* Go on to next format stage, next queued device, or end
	     * process
	     */
	    if(fm->format_stage >= FORMAT_STAGE_HIGHEST)
	    {
		EDVPlaySoundCompleted(fm->ctx);
		gtk_widget_show_raise(fm->toplevel);
	    }
	    DO_PROCESS_END
	    return(FALSE);
	}

	/* Update progress bar and disk animation icon */
	FormatManagerSetProgress(fm, -1.0f, FALSE);

	fm->last_disk_icon_num++;
	if(fm->last_disk_icon_num >= fm->total_disk_icons)
	    fm->last_disk_icon_num = 0;
	FormatManagerDiskIconExposeCB(fm->disk_anim_da, NULL, fm);

	return(TRUE);
#undef DO_PROCESS_END
}


/*
 *      Format Manager toplevel GtkWindow "delete_event" signal
 *	callback.
 */
gint FormatManagerDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	FormatManagerCloseBtnCB(widget, data);
	return(TRUE);
}


/*
 *	Sets up the format manager window to start checking the given
 *	device.  The format program will be runned and timeout callback
 *	set.
 *
 *	The calling function must set the format_stage on the fm. With
 *	the exception that if quick_format is checked, then if the
 *	format_stage is set to FORMAT_STAGE_FORMAT then it will
 *	automatically be skipped to the next format_stage.
 */
static void FormatManagerDoStart(
	format_manager_struct *fm, gint dev_num
)
{
	const gchar *s, *capacity_str;
	gulong capacity = 1440l;
	gboolean	quick_format = FALSE,
			verbose = FALSE;
	gchar *fs_type = EDV_FS_TYPE_MSDOS_NAME;
	const gchar *volume_label = NULL;
	GtkWidget *toplevel;
	GtkEntry *entry;
	pid_t p;
	guint toid;
	gchar *cmd, *prog, *prog_name, *startup_message;
	pulist_struct *pulist;
	edv_device_struct *dev;

	if(fm == NULL)
	    return;

	toplevel = fm->toplevel;

	/* Get format parameters */
	quick_format = GTK_TOGGLE_BUTTON_GET_ACTIVE(
	   fm->quick_format_check
	);

	/* Get the capacity */
	pulist = PUListBoxGetPUList(fm->capacity_pulistbox);
	capacity = (gulong)PUListGetItemData(
	    pulist,
	    PUListGetSelectedLast(pulist)
	);

	/* Get the filesystem type */
	pulist = PUListBoxGetPUList(fm->filesystem_type_pulistbox);
	PUListGetItemText(
	    pulist,
	    PUListGetSelectedLast(pulist),
	    &fs_type
	);

	/* Set aliases for filesystem types */
	if(!strcmp(fs_type, EDV_FS_TYPE_VFAT_NAME))
	    fs_type = EDV_FS_TYPE_MSDOS_NAME;

	entry = (GtkEntry *)fm->volume_label_entry;
	if(entry != NULL)
	    volume_label = gtk_entry_get_text(entry);

	verbose = GTK_TOGGLE_BUTTON_GET_ACTIVE(
	    fm->verbose_check
	);


	/* Get pointer to device */
	dev = ((dev_num >= 0) && (dev_num < fm->total_devices)) ?
	    fm->device[dev_num] : NULL;
	if(dev == NULL)
	    return;


	/* Skip the format stage if quick format is selected and we are
	 * currently at the format stage
	 */
	if(quick_format && (fm->format_stage == FORMAT_STAGE_FORMAT))
	    fm->format_stage++;


	/* Check if this device is already mounted, in which case we
	 * cannot use format on it
	 */
	if(EDV_DEVICE_IS_MOUNTED(dev))
	{
	    gchar *buf = g_strdup_printf(
#ifdef PROG_LANGUAGE_ENGLISH
"Device `%s' is currently mounted.\n\
\n\
You must unmount the device before formatting.",
#endif
#ifdef PROG_LANGUAGE_SPANISH
"El artefacto `%s' se monta actualmente.\n\
\n\
Usted debe unmount el artefacto primero antes de format.",
#endif
#ifdef PROG_LANGUAGE_FRENCH
"`%s' d'appareil est actuellement monté.\n\
\n\
Vous devez démonté l'appareil avant de pouvoir le formatter.",
#endif
		dev->device_path
	    );
	    EDVPlaySoundWarning(fm->ctx);
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
#if defined(PROG_LANGUAGE_SPANISH)
		"El Format Falló",
		buf,
"El artefacto se debe unmounted antes se puede format,\n\
esto deberá prevenir los errores debido a lectura/escritura\n\
que pueden occure durante el proceso que format. Use la\n\
Endeavour otra aplicación al unmount el artefacto primero.",
#elif defined(PROG_LANGUAGE_FRENCH)
		"Le Format a Echoué",
		buf,
"L'appareil doit être démonté avant qu'il puisse être\n\
formatté, cela empêchera les erreurs de lecture/écriture\n\
qui pourraient se produire pendant le procédé de formattage.\n\
Utilise Endeavour ou une autre commande pour démonter.",
#else
		"Format Failed",
		buf,
"The device must be unmounted before the media can be\n\
formatted, this is to prevent errors due to read/writes\n\
that may occure during the formatting process.\n\
Use Endeavour or another application to unmount\n\
the device first.",
#endif
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    g_free(buf);
	    return;
	}


	/* Check if there is already an format process running, in which
	 * case we will increment the stop count so the next call to
	 * the timeout callback stops it
	 */
	p = fm->format_pid;
	if(p != 0)
	{
	    if(ExecProcessExists(p))
	    {
		EDVPlaySoundError(fm->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Internal Error",
"Starting format while a format process is\n\
already running.",
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		return;
	    }
	}


	/* Format the command, get program name, and startup message 
	 * based on which stage of format we are at
	 */
	cmd = NULL;
	prog = NULL;
	startup_message = NULL;
	switch(fm->format_stage)
	{
	  case FORMAT_STAGE_FORMAT:
	    /* Get the capacity string */
	    switch(capacity)
	    {
	      case 1440:
		capacity_str = "H1440";
		break;
	      case 1200:
		capacity_str = "h1200";
		break;
	      case 720:
		capacity_str = "H720";
		break;
	      case 360:
		capacity_str = "H360";
		break;
	      default:	/* All else assume H1440 */
		capacity_str = "H1440";
		break;
	    }
	    /* Format the command
	     *
	     * First check if the superformat program is installed, if
	     * it is not then use the fdformat program
	     */
	    prog = EDVWhich(SUPERFORMAT_PROG);
	    if(prog != NULL)
	    {
		cmd = g_strdup_printf(
		    "%s %s%s",
		    prog, dev->device_path, capacity_str
		);
	    }
	    else
	    {
		/* Could not find superformat, try fdformat */
		prog = EDVWhich(FDFORMAT_PROG);
		if(prog != NULL)
		    cmd = g_strdup_printf(
			"%s %s%s",
			prog, dev->device_path, capacity_str
		    );
	    }
	    /* No format programs installed? */
	    if((prog == NULL) || (cmd == NULL))
	    {
		gchar *buf = g_strdup_printf(
"Unable to find the Format programs:\n\
\n\
    %s\n\
    %s\n\
\n\
Please verify that at least one of the programs is\n\
installed in a location specified by the %s\n\
environment and that it is executable by this program.",
		    SUPERFORMAT_PROG,
		    FDFORMAT_PROG,
		    ENV_VAR_NAME_PATH
		);
		EDVPlaySoundWarning(fm->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Unable To Find Format Program",
		    buf,
		    NULL,
		    CDIALOG_ICON_WARNING,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		g_free(buf);
		g_free(prog);
		g_free(cmd);
		return;
	    }
	    startup_message = g_strdup_printf(
"-------------------------------------------------\n\
Formatting media in device %s...\n",
		dev->device_path
	    );
	    break;

	  case FORMAT_STAGE_MKFS:
	    /* Get the mkfs program appropriate for the selected
	     * filesystem
	     */
	    prog_name = g_strconcat(MKFS_PROG, ".", fs_type, NULL);
	    prog = EDVWhich(prog_name);
	    if(prog == NULL)
	    {
		gchar *buf = g_strdup_printf(
"Unable to find the MKFS program:\n\
\n\
    %s\n\
\n\
Please verify that the program is installed at a location\n\
specified by the %s environment and that it is\n\
executable by this program.",
		    prog_name,
		    ENV_VAR_NAME_PATH
		);
		EDVPlaySoundWarning(fm->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Unable To Find MKFS Program",
		    buf,
		    NULL,
		    CDIALOG_ICON_WARNING,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		g_free(buf);
		g_free(prog);
		g_free(prog_name);
		return;
	    }

	    g_free(prog_name);
	    prog_name = NULL;

	    /* Add option to specify volume label (only for
	     * mkfs.msdos)?
	     */
	    if(!STRISEMPTY(volume_label) &&
	       !strcmp(fs_type, EDV_FS_TYPE_MSDOS_NAME)
	    )
	    {
		gchar *dvolume_label = g_strdup(volume_label);
		/* No more than 11 characters */
		if(STRLEN(dvolume_label) > 11)
		    dvolume_label[11] = '\0';
 		cmd = g_strdup_printf(
		    "%s %s %s -n \"%s\"",
		    prog, dev->device_path,
		    verbose ? "-v" : "",
		    dvolume_label
		);
		g_free(dvolume_label);
	    }
	    else
		cmd = g_strdup_printf(
		    "%s %s %s",
		    prog, dev->device_path,
		    verbose ? "-v" : ""
		);
	    startup_message = g_strdup_printf(
"-------------------------------------------------\n\
Creating %s file system...\n",
		fs_type
	    );
	    break;

	}
	/* Unable to generate command? */
	if(cmd == NULL)
	{
	    EDVPlaySoundError(fm->ctx);
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
		"Internal Error",
"Invalid format stage specified as the starting\n\
format stage.",
		NULL,
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);

	    g_free(cmd);
	    g_free(prog);
	    g_free(startup_message);

	    return;
	}

	/* Print message */
	FormatManagerAppendMessage(
	    fm, fm->text_font, NULL, NULL,
	    startup_message,
	    TRUE
	);
	g_free(startup_message);
	startup_message = NULL;


	/* Generate tempory names for stdout and stderr paths */
	s = g_getenv("TMPDIR");
	if(STRISEMPTY(s))
#ifdef P_tmpdir
	    s = P_tmpdir;
#else
	    s = "/tmp";
#endif

	g_free(fm->stdout_path);
	fm->stdout_path = STRDUP(PrefixPaths(s, "formatXXXXXX"));
	mkstemp(fm->stdout_path);

	g_free(fm->stderr_path);
	fm->stderr_path = STRDUP(PrefixPaths(s, "formatXXXXXX"));
	mkstemp(fm->stderr_path);


	/* Execute format or mkfs command */
	fm->format_pid = p = ExecOE(
	    cmd,
	    fm->stdout_path,
	    fm->stderr_path
	);
	if(p == 0)
	{
	    gchar *buf = g_strdup_printf(
#ifdef PROG_LANGUAGE_ENGLISH
"Unable to execute the command:\n\
\n\
    %s",
#endif
#ifdef PROG_LANGUAGE_SPANISH
"Incapaz de ejecutar la orden:\n\
\n\
    %s",
#endif
#ifdef PROG_LANGUAGE_FRENCH
"Impossible d'exécuter la commande:\n\
\n\
    %s",
#endif
		cmd
	    );
	    EDVPlaySoundWarning(fm->ctx);
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
#ifdef PROG_LANGUAGE_ENGLISH
		"Format Failed",
		buf,
		NULL,
#endif
#ifdef PROG_LANGUAGE_SPANISH
		"El Format Falló",
		buf,
		NULL,
#endif
#ifdef PROG_LANGUAGE_FRENCH
		"Le Formattage a Echoué",
		buf,
		NULL,
#endif
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    g_free(buf);

	    /* Execute failed */
	    UNLINK(fm->stdout_path);
	    g_free(fm->stdout_path);
	    fm->stdout_path = NULL;

	    UNLINK(fm->stderr_path);
	    g_free(fm->stderr_path);
	    fm->stderr_path = NULL;

	    g_free(cmd);
	    g_free(prog);

	    return;
	}

	/* Open stdout and stderr paths */
	FClose(fm->stdout_fp);
	fm->stdout_fp = FOpen(fm->stdout_path, "rb");

	FClose(fm->stderr_fp);
	fm->stderr_fp = FOpen(fm->stderr_path, "rb");


	/* Schedual timeout callback to monitor this process */
	fm->format_toid = toid = gtk_timeout_add(
	    50l,			/* ms */
	    FormatManagerFormatTOCB, fm
	);



	FormatManagerUpdateMenus(fm);

	g_free(cmd);
	g_free(prog);
}


/*
 *      Start callback.
 */
void FormatManagerStartCB(GtkWidget *widget, gpointer data)
{
	gint status, dev_num;
	GList *glist;
	GtkWidget *toplevel;
	GtkCList *clist;
	format_manager_struct *fm = FORMAT_MANAGER(data);
	if(fm == NULL)
	    return;

	toplevel = fm->toplevel;
	clist = (GtkCList *)fm->devices_clist;
	if(clist == NULL)
	    return;

	/* Process already running? */
	if(fm->format_pid != 0)
	    return;

	/* Query user for one last confirmation about formatting
	 * media and loosing any existing data in the media
	 */
	EDVPlaySoundQuestion(fm->ctx);
	CDialogSetTransientFor(toplevel);
	status = CDialogGetResponse(
#ifdef PROG_LANGUAGE_ENGLISH
	    "Confirm Format",
"Formatting the media will erase all existing data\n\
on the media, are you sure you want to continue\n\
formatting?",
	    NULL,
#endif
#ifdef PROG_LANGUAGE_SPANISH
	    "Confirme Formato",
"¿Formatear los medios borrarán todos datos existentes\n\
en los medios, usted está seguro que usted quiere\n\
continuar formatear?",
	    NULL,
#endif
#ifdef PROG_LANGUAGE_FRENCH
	    "Confirmer Le Formattage",
"Le formattage détruira toutes les données\n\
existantes sur le disque, êtes vous sûr de vouloir continuer ?",

	    NULL,
#endif
	    CDIALOG_ICON_WARNING,
	    CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,
	    CDIALOG_BTNFLAG_NO
	);
	CDialogSetTransientFor(NULL);
	if(status != CDIALOG_RESPONSE_YES)
	    return;


	/* Update the mount states of each device */
	EDVDevicesListUpdateMountStates(
	    fm->device, fm->total_devices
	);

	/* Clear output */
	FormatManagerClearCB(fm->clear_btn, fm);

	/* Clear old queued devices list to format */
	if(fm->queued_device_to_format != NULL)
	{
	    g_list_free(fm->queued_device_to_format);
	    fm->queued_device_to_format = NULL;
	}

	/* Copy list of selected rows on the devices clist as a list
	 * of queued devices to format
	 */
	if(clist->selection != NULL)
	    fm->queued_device_to_format = g_list_copy(clist->selection);

	/* Start formatting the first queued device */
	glist = fm->queued_device_to_format;
	if(glist != NULL)
	{
	    dev_num = (gint)gtk_clist_get_row_data(
		clist,
		(gint)glist->data	/* Row index */
	    );
	    fm->format_stage = FORMAT_STAGE_FORMAT;
	    FormatManagerDoStart(fm, dev_num);
	}
}

/*
 *	Stop callback.
 */
void FormatManagerStopCB(GtkWidget *widget, gpointer data)
{
	format_manager_struct *fm = FORMAT_MANAGER(data);
	if(fm == NULL)
	    return;

	fm->stop_count++;
}

/*
 *	Clear callback.
 */
void FormatManagerClearCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *w;
	GtkEditable *editable;
	GtkText *text;
	format_manager_struct *fm = FORMAT_MANAGER(data);
	if(fm == NULL)
	    return;

	w = fm->output_text;
	if(w == NULL)
	    return;

	editable = (GtkEditable *)w;
	text = (GtkText *)w;

	gtk_editable_delete_text(editable, 0, -1);
}

/*
 *	Close button callback.
 */
void FormatManagerCloseBtnCB(GtkWidget *widget, gpointer data)
{
	format_manager_struct *fm = FORMAT_MANAGER(data);
	if(fm == NULL)
	    return;

	FormatManagerUnmap(fm);
}


/*
 *	Devices clist "select_row" signal callback.
 */
void FormatManagerSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	format_manager_struct *fm = FORMAT_MANAGER(data);
	if(fm == NULL)
	    return;

	/* Check which clist this event is for */
	if(GTK_WIDGET(clist) == fm->devices_clist)
	{
	    /* Check if selected row is fully visible, if not then
	     * adjust scroll position to try and make it visible
	     */
	    if(gtk_clist_row_is_visible(clist, row) !=
		GTK_VISIBILITY_FULL
	    )
		gtk_clist_moveto(
		    clist,
		    row, column,	/* Row, column */
		    0.5f, 0.0f		/* Row, column */
		);

	    FormatManagerUpdateMenus(fm);
	}
}

/*
 *      Devices clist "unselect_row" signal callback.
 */
void FormatManagerUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	format_manager_struct *fm = FORMAT_MANAGER(data);
	if(fm == NULL)
	    return;

	/* Check which clist this event is for */
	if(GTK_WIDGET(clist) == fm->devices_clist)
	{
	    FormatManagerUpdateMenus(fm);
	}
}
