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

#include <gtk/gtk.h>

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

#include "cdialog.h"

#include "edvtypes.h"
#include "edvdevices.h"
#include "edvmount.h"
#include "edvutils.h"
#include "edvutilsgtk.h"


static const gchar *last_error;


static gboolean FILE_EXISTS_NON_ZERO(const gchar *path);

const gchar *EDVMountGetError(void);

gint EDVMountDoMount(
	edv_core_struct *core_ptr, edv_device_struct *dev_ptr,
	GtkWidget *toplevel
);
gint EDVMountDoUnmount(
	edv_core_struct *core_ptr, edv_device_struct *dev_ptr,
	GtkWidget *toplevel
);
gint EDVMountDoEject(
	edv_core_struct *core_ptr, edv_device_struct *dev_ptr,
	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 *EDVMountGetError(void)
{
	return(last_error);
}


/*
 *	Mounts the specified device.
 *
 *	Any warnings and errors that occure will be printed.
 */
gint EDVMountDoMount(
	edv_core_struct *core_ptr, edv_device_struct *dev_ptr,
	GtkWidget *toplevel
)
{
	gint pid, status;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL,
		*mount_path = NULL,
		*device_path = NULL;
	const gchar *fs_type_str;

	/* Reset last error string */
	last_error = NULL;

	if(dev_ptr == NULL)
	{
	    last_error = "Bad input 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_ptr->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_ptr->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 = EDVDeviceGetFSStringFromNumber(dev_ptr->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_ptr->command_mount))
	    cmd = STRDUP(dev_ptr->command_mount);
	else
#if 1
	    cmd = g_strdup_printf(
		"/bin/mount \"%s\" %s",
		mount_path,
		dev_ptr->read_only ? "-r" : ""
	    );
#else
/* Does not work for non-root users */
	    cmd = g_strdup_printf(
		"/bin/mount -t %s \"%s\" \"%s\" %s",
		fs_type_str,
		device_path,
		mount_path,
		dev_ptr->read_only ? "-r" : ""
	    );
#endif

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

	/* Execute command string */
#if 0
	pid = EDVSystemBlock(cmd, NULL);
#else
	pid = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path, (const char *)stderr_path
	);
#endif
	if(pid <= 0)
	{
	    last_error = "Execution of mount command failed";
	    status = -1;
	}
	else
	{
	    dev_ptr->last_mount_time = (gulong)time(NULL);
	    status = 0;
	}

	/* Wait for process to exit */
	while(EDVProcessIsRunning(pid))
	    usleep(8000);

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


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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Unmounts the specified device.
 *
 *	Any warnings and errors that occure will be printed.
 */
gint EDVMountDoUnmount(
	edv_core_struct *core_ptr, edv_device_struct *dev_ptr,
	GtkWidget *toplevel
)
{
	gint pid, status;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL,
		*mount_path = NULL;

	/* Reset last error string */
	last_error = NULL;

	if(dev_ptr == NULL)
	{
	    last_error = "Bad input value";
	    return(-1);
	}

	/* Device not allowed to be unmounted? */
	if(dev_ptr->no_unmount)
	{
	    last_error = "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;		\
}

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

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

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

	/* Execute command string */
#if 0
	pid = EDVSystemBlock(cmd, NULL);
#else  
	pid = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path, (const char *)stderr_path
	);
#endif
	if(pid == 0)
	{
	    last_error = "Execution of unmount command failed";
	    status = -1;
	}
	else
	{
	    status = 0;
	}

	/* Wait for process to exit */
	while(EDVProcessIsRunning(pid))
	    usleep(8000);

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

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Ejects the media from the specified device.
 *
 *	Any warnings and errors that occure will be printed.
 *
 *	Note: Call EDVMountDoUnmount() as needed to unmount the device
 *	first.
 */
gint EDVMountDoEject(
	edv_core_struct *core_ptr, edv_device_struct *dev_ptr,
	GtkWidget *toplevel
)
{
	gint pid, status;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL,
		*device_path = NULL;

	/* Reset last error string */
	last_error = NULL;

	if(dev_ptr == NULL)
	{
	    last_error = "Bad input value";
	    return(-1);
	}

	/* Media not allowed to be ejected from device? */
	if(dev_ptr->no_unmount)
	{
	    last_error = "Unable to eject 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_ptr->device_path);
	if(device_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get device path";
	    return(-2);
	}

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

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

	/* Execute command string */
#if 0
	pid = EDVSystemBlock(cmd, NULL);
#else
	pid = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path, (const char *)stderr_path
	);
#endif
	if(pid == 0)
	{
	    last_error = "Execution of eject command failed";
	    status = -1;
	}
	else
	{
	    status = 0;
	}

	/* Wait for process to exit */
	while(EDVProcessIsRunning(pid))
	    usleep(8000);

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

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}
