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

#include "../include/prochandle.h"

#include "cdialog.h"
#include "progressdialog.h"

#include "edv_types.h"
#include "edv_device.h"
#include "edv_device_mount.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"

#include "images/icon_mount_32x32.xpm"
#include "images/icon_unmount_32x32.xpm"
#include "images/icon_eject_32x32.xpm"


static const gchar *last_error = NULL;


static gboolean FILE_EXISTS_NON_ZERO(const gchar *path);

const gchar *EDVDeviceUnmountGetError(void);

gint EDVDeviceMount(
	edv_core_struct *core, edv_device_struct *dev,
	const gboolean show_progress, const gboolean verbose,
	GtkWidget *toplevel
);
gint EDVDeviceUnmount(
	edv_core_struct *core, edv_device_struct *dev,
	const gboolean show_progress, const gboolean verbose,
	GtkWidget *toplevel
);
gint EDVDeviceEject(
	edv_core_struct *core, edv_device_struct *dev,
	const gboolean show_progress, const gboolean verbose,
	GtkWidget *toplevel
);


#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 UNLINK(p)	(((p) != NULL) ? (gint)unlink((const char *)(p)) : -1)


/*
 *	Checks if the file exists and is a non-zero size.
 */
static gboolean FILE_EXISTS_NON_ZERO(const gchar *path)
{
	struct stat stat_buf;

	if(stat((const char *)path, &stat_buf))
	    return(FALSE);

	return((stat_buf.st_size > 0l) ? TRUE : FALSE);
}


/*
 *	Returns a string describing the last error encountered or NULL
 *	if there was no error.
 *
 *	The returned pointer must not be modified or deleted.
 */
const gchar *EDVDeviceUnmountGetError(void)
{
	return(last_error);
}


/*
 *	Mounts the specified device.
 *
 *	Any warnings and errors that occure will be printed.
 */
gint EDVDeviceMount(
	edv_core_struct *core, edv_device_struct *dev,
	const gboolean show_progress, const gboolean verbose,
	GtkWidget *toplevel
)
{
	gint p;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL,
		*mount_path = NULL,
		*device_path = NULL;
	const gchar *fs_type_str;

	last_error = NULL;

	if(dev == NULL)
	{
	    last_error = "Invalid value";
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(cmd);			\
 cmd = NULL;			\
 g_free(stdout_path);		\
 stdout_path = NULL;		\
 g_free(stderr_path);		\
 stderr_path = NULL;		\
				\
 g_free(mount_path);		\
 mount_path = NULL;		\
 g_free(device_path);		\
 device_path = NULL;		\
}

	/* Get copy of mount path */
	mount_path = STRDUP(dev->mount_path);
	if(mount_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get mount path";
	    return(-2);
	}

	/* Get copy of device path */
	device_path = STRDUP(dev->device_path);
	if(device_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get device path";
	    return(-2);
	}

	/* Get file system type as a string */
	fs_type_str = EDVDeviceGetFSNameFromType(dev->fs_type);
	if(fs_type_str == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get file system type string";
	    return(-2);
	}

	/* Create mount command string */
	if(!STRISEMPTY(dev->command_mount))
	    cmd = STRDUP(dev->command_mount);
	else
	    cmd = g_strdup_printf(
		"/bin/mount \"%s\" %s",
		mount_path,
		EDV_DEVICE_IS_READ_ONLY(dev) ? "-r" : ""
	    );

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute command string */
	p = (gint)ExecOE(
	    (const char *)cmd,
	    (const char *)stdout_path, (const char *)stderr_path
	);
	if(p <= 0)
	{
	    UNLINK(stdout_path);
	    UNLINK(stderr_path);
	    DO_FREE_LOCALS
	    last_error = "Execution of the mount command failed";
	    return(-1);
	}
	else
	{
	    dev->last_mount_time = (gulong)time(NULL);
	}

	/* Map the progress dialog? */
	if(show_progress)
	{
	    gchar *s = g_strdup_printf(
"Mounting \"%s\" to:\n\
\n\
    %s\n",
		(dev->name != NULL) ? dev->name : device_path,
		mount_path
	    );
	    ProgressDialogSetTransientFor(toplevel);
	    ProgressDialogMap(
		"Mounting",
		s,
		(const guint8 **)icon_mount_32x32_xpm,
		"Stop"
	    );
	    g_free(s);
	    ProgressDialogUpdateUnknown(NULL, NULL, NULL, NULL, TRUE);
	    gdk_flush();
	}

	/* Monitor the mount process */
	while(EDVProcessIsRunning(p))
	{
	    /* Show progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdateUnknown(NULL, NULL, NULL, NULL, TRUE);
		if(ProgressDialogStopCount() > 0)
		{
		    kill((int)p, SIGINT);
		    break;
		}
	    }
	    usleep(8000);
	}

	/* Unmap the progress dialog? */
	if(show_progress)
	{
	    ProgressDialogBreakQuery(TRUE);
	    ProgressDialogSetTransientFor(NULL);
	}

	/* Show mount stderr and stdout messages (if any) */
	if(verbose && FILE_EXISTS_NON_ZERO(stderr_path))
	{
	    EDVPlaySoundWarning(core);
	    EDVMessageFromFile(
		stderr_path,
		"Mount Warning",
		CDIALOG_ICON_WARNING,
		toplevel
	    );
	}
	if(verbose && FILE_EXISTS_NON_ZERO(stdout_path))
	{
	    EDVPlaySoundInfo(core);
	    EDVMessageFromFile(
		stdout_path,
		"Mount Message",
		CDIALOG_ICON_INFO,
		toplevel
	    );
	}

	/* Remove the output file paths */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS

	return(0);
#undef DO_FREE_LOCALS
}

/*
 *	Unmounts the specified device.
 *
 *	Any warnings and errors that occure will be printed.
 */
gint EDVDeviceUnmount(
	edv_core_struct *core, edv_device_struct *dev,
	const gboolean show_progress, const gboolean verbose,
	GtkWidget *toplevel
)
{
	gint p;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL,
		*mount_path = NULL,
		*device_path = NULL;

	last_error = NULL;

	if(dev == NULL)
	{
	    last_error = "Invalid value";
	    return(-1);
	}

	/* Device not allowed to be unmounted? */
	if(EDV_DEVICE_IS_NO_UNMOUNT(dev))
	{
	    last_error = "Unable to unmount the device, device is marked \"no unmount\"";
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(cmd);			\
 cmd = NULL;			\
 g_free(stdout_path);		\
 stdout_path = NULL;		\
 g_free(stderr_path);		\
 stderr_path = NULL;		\
				\
 g_free(mount_path);		\
 mount_path = NULL;		\
 g_free(device_path);		\
 device_path = NULL;		\
}

	/* Get copy of mount path */
	mount_path = STRDUP(dev->mount_path);
	if(mount_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get mount path";
	    return(-2);
	}

	/* Get copy of device path */
	device_path = STRDUP(dev->device_path);
	if(device_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get device path";
	    return(-2);
	}

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Format the unmount command */
	if(!STRISEMPTY(dev->command_unmount))
	    cmd = STRDUP(dev->command_unmount);
	else
	    cmd = g_strdup_printf(
		"/bin/umount \"%s\"",
		mount_path
	    );

	/* Execute the unmount command */
	p = (gint)ExecOE(
	    (const char *)cmd,
	    (const char *)stdout_path, (const char *)stderr_path
	);
	if(p == 0)
	{
	    UNLINK(stdout_path);
	    UNLINK(stderr_path);
	    DO_FREE_LOCALS
	    last_error = "Execution of the unmount command failed";
	    return(-1);
	}

	/* Map the progress dialog? */
	if(show_progress)
	{
	    gchar *s = g_strdup_printf(
"Unmounting \"%s\" from:\n\
\n\
    %s\n",
		(dev->name != NULL) ? dev->name : device_path,
		mount_path
	    );
	    ProgressDialogSetTransientFor(toplevel);
	    ProgressDialogMap(
		"Unmounting",
		s,
		(const guint8 **)icon_unmount_32x32_xpm,
		"Stop"
	    );
	    g_free(s);
	    ProgressDialogUpdateUnknown(NULL, NULL, NULL, NULL, TRUE);
	    gdk_flush();
	}

	/* Monitor the unmount process */
	while(EDVProcessIsRunning(p))
	{
	    /* Show progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdateUnknown(NULL, NULL, NULL, NULL, TRUE);
		if(ProgressDialogStopCount() > 0)
		{
		    kill((int)p, SIGINT);
		    break;
		}
	    }
	    usleep(8000);
	}

	/* Unmap the progress dialog? */
	if(show_progress)
	{
	    ProgressDialogBreakQuery(TRUE);
	    ProgressDialogSetTransientFor(NULL);
	}

	/* Show unmount stderr and stdout messages (if any) */
	if(verbose && FILE_EXISTS_NON_ZERO(stderr_path))
	{
	    EDVPlaySoundWarning(core);
	    EDVMessageFromFile(
		stderr_path,
		"Unmount Warning",
		CDIALOG_ICON_WARNING,
		toplevel
	    );
	}
	if(verbose && FILE_EXISTS_NON_ZERO(stdout_path))
	{
	    EDVPlaySoundInfo(core);
	    EDVMessageFromFile(
		stdout_path,
		"Unmount Message",
		CDIALOG_ICON_INFO,
		toplevel
	    );
	}

	/* Remove the output file paths */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS

	return(0);
#undef DO_FREE_LOCALS
}

/*
 *	Ejects the media from the specified device.
 *
 *	Any warnings and errors that occure will be printed.
 *
 *	Note: Call EDVDeviceUnmount() as needed to unmount the device
 *	first.
 */
gint EDVDeviceEject(
	edv_core_struct *core, edv_device_struct *dev,
	const gboolean show_progress, const gboolean verbose,
	GtkWidget *toplevel
)
{
	gint p;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL,
		*device_path = NULL;

	last_error = NULL;

	if(dev == NULL)
	{
	    last_error = "Invalid value";
	    return(-1);
	}

	/* Media not allowed to be ejected from device? */
	if(EDV_DEVICE_IS_NO_UNMOUNT(dev))
	{
	    last_error = "Unable to eject the media, device is marked \"no unmount\"";
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(cmd);			\
 cmd = NULL;			\
 g_free(stdout_path);		\
 stdout_path = NULL;		\
 g_free(stderr_path);		\
 stderr_path = NULL;		\
				\
 g_free(device_path);		\
 device_path = NULL;		\
}

	/* Get copy of device path */
	device_path = STRDUP(dev->device_path);
	if(device_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get device path";
	    return(-2);
	}

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Create eject command string */
	if(!STRISEMPTY(dev->command_eject))
	    cmd = STRDUP(dev->command_eject);
	else
	    cmd = g_strdup_printf(
		"/usr/bin/eject \"%s\"",
		device_path
	    );

	/* Execute the eject command */
	p = (gint)ExecOE(
	    (const char *)cmd,
	    (const char *)stdout_path, (const char *)stderr_path
	);
	if(p == 0)
	{
	    UNLINK(stdout_path);
	    UNLINK(stderr_path);
	    DO_FREE_LOCALS
	    last_error = "Execution of the eject command failed";
	    return(-1);
	}

	/* Map the progress dialog? */
	if(show_progress)
	{
	    gchar *s = g_strdup_printf(
"Ejecting \"%s\"\n",
		(dev->name != NULL) ? dev->name : device_path
	    );
	    ProgressDialogSetTransientFor(toplevel);
	    ProgressDialogMap(
		"Ejecting",
		s,
		(const guint8 **)icon_eject_32x32_xpm,
		"Stop"
	    );
	    g_free(s);
	    ProgressDialogUpdateUnknown(NULL, NULL, NULL, NULL, TRUE);
	    gdk_flush();
	}

	/* Monitor the eject process */
	while(EDVProcessIsRunning(p))
	{
	    /* Show progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdateUnknown(NULL, NULL, NULL, NULL, TRUE);
		if(ProgressDialogStopCount() > 0)
		{
		    kill((int)p, SIGINT);
		    break;
		}
	    }
	    usleep(8000);
	}

	/* Unmap the progress dialog? */
	if(show_progress)
	{
	    ProgressDialogBreakQuery(TRUE);
	    ProgressDialogSetTransientFor(NULL);
	}

	/* Show eject error and output message (if any) */
	if(verbose && FILE_EXISTS_NON_ZERO(stderr_path))
	{
	    EDVPlaySoundWarning(core);
	    EDVMessageFromFile(
		stderr_path,
		"Eject Warning",
		CDIALOG_ICON_WARNING,
		toplevel
	    );
	}
	if(verbose && FILE_EXISTS_NON_ZERO(stdout_path))
	{
	    EDVPlaySoundInfo(core);
	    EDVMessageFromFile(
		stdout_path,
		"Eject Message",
		CDIALOG_ICON_INFO,
		toplevel
	    );
	}

	/* Remove the output file paths */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS

	return(0);
#undef DO_FREE_LOCALS
}
