#include <glib.h>
#include <unistd.h>
#include "../../include/prochandle.h"

#include "edv_types.h"
#include "edv_device.h"
#include "edv_device_mount.h"
#include "edv_context.h"
#include "edv_notify.h"
#include "config.h"


const gchar *EDVDeviceMountGetError(void);
gint EDVDeviceMount(
	edv_context_struct *ctx, edv_device_struct *d,
	gboolean notify
);
gint EDVDeviceUnmount(
	edv_context_struct *ctx, edv_device_struct *d,
	gboolean notify
);
gint EDVDeviceEject(
	edv_context_struct *ctx, edv_device_struct *d
);


static const gchar *last_error = NULL;


#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)


/*
 *	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 *EDVDeviceMountGetError(void)
{
	return(last_error);
}
const gchar *EDVMountGetError(void)
{
	return(EDVDeviceMountGetError());
}

/*
 *	Mounts the specified device.
 *
 *	If notify is set to TRUE then a "object_mounted_notify" will
 *	be queued on the context (only on success).
 *
 *	Returns 0 on success and non-zero on error.
 */
gint EDVDeviceMount(
	edv_context_struct *ctx, edv_device_struct *d,
	gboolean notify
)
{
	gint pid, status;
	const gchar *fs_name;
	gchar	*cmd = NULL,
		*mount_path = NULL,
		*device_path = NULL;

	last_error = NULL;		/* Reset last error  */

	if(d == NULL)
	{
	    last_error = "No device specified";
	    return(-2);
	}

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

	/* Already mounted? */
	if(EDV_DEVICE_IS_MOUNTED(d))
	{
	    last_error = "Device is already mounted";
	    return(-2);
	}

	/* Get copy of mount path */
	mount_path = STRDUP(d->mount_path);
	if(mount_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Mount path not specified by the device";
	    return(-2);
	}

	/* Get copy of device path */
	device_path = STRDUP(d->device_path);
	if(device_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Device path not specified by the device";
	    return(-2);
	}

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

	/* Format mount command */
	if(!STRISEMPTY(d->command_mount))
	    cmd = STRDUP(d->command_mount);
	else
#if 1
	    cmd = g_strdup_printf(
		"/bin/mount \"%s\" %s",
		mount_path,
		EDV_DEVICE_IS_READ_ONLY(d) ? "-r" : ""
	    );
#else
/* Does not work for non-root users */
	    cmd = g_strdup_printf(
		"/bin/mount -t %s \"%s\" \"%s\" %s",
		fs_name,
		device_path,
		mount_path,
		EDV_DEVICE_IS_READ_ONLY(d) ? "-r" : ""
	    );
#endif

	/* Execute command */
	pid = ExecB(cmd);
	if(pid == 0)
	{
	    last_error = "Execution of mount command failed";
	    status = -1;
	}
	else
	{
/*	    d->last_mount_time = (gulong)time(NULL); */
	    if(notify)
		EDVNotifyQueueObjectMounted(ctx, mount_path);
	    status = 0;
	}

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

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}
gint EDVMount(
	edv_context_struct *ctx, edv_device_struct *d,
	gboolean notify
)
{
	return(EDVDeviceMount(ctx, d, notify));
}

/*
 *	Unmounts the specified device.
 *
 *	If notify is set to TRUE then a "object_unmounted_notify" will
 *	be queued on the context (only on success).
 *
 *	Returns 0 on success and non-zero on error.
 */
gint EDVDeviceUnmount(
	edv_context_struct *ctx, edv_device_struct *d,
	gboolean notify
)
{
	gint pid, status;
	gchar	*cmd = NULL,
		*mount_path = NULL;


	last_error = NULL;		/* Reset last error */

	if(d == NULL)
	{
	    last_error = "No device specified";
	    return(-2);
	}

	/* Device not allowed to be unmounted? */
	if(EDV_DEVICE_IS_NO_UNMOUNT(d))
	{
	    last_error = "Device does not permit unmounting";
	    return(-2);
	}

	/* Not mounted? */
	if(!EDV_DEVICE_IS_MOUNTED(d))
	{
	    last_error = "Device is not mounted";
	    return(-2);
	}

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

	/* Get copy of mount path */
	mount_path = STRDUP(d->mount_path);
	if(mount_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Mount path not specified by the device";
	    return(-2);
	}

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

	/* Execute command */
	pid = ExecB(cmd);
	if(pid == 0)
	{
	    last_error = "Execution of unmount command failed";
	    status = -1;
	}
	else
	{
	    if(notify)
		EDVNotifyQueueObjectUnmounted(ctx, mount_path);
	    status = 0;
	}

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

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}
gint EDVUnmount(
	edv_context_struct *ctx, edv_device_struct *d,
	gboolean notify
)
{
	return(EDVDeviceUnmount(ctx, d, notify));
}

/*
 *	Ejects the media from the specified device.
 *
 *	If the device is mounted then you must call EDVUnmount() to
 *	unmount the device before ejecting it.
 *
 *	Returns 0 on success and non-zero on error.
 */
gint EDVDeviceEject(
	edv_context_struct *ctx, edv_device_struct *d
)
{
	gint pid, status;
	gchar	*cmd = NULL,
		*device_path = NULL;

	last_error = NULL;		/* Reset last error */

	if(d == NULL)
	{
	    last_error = "No device specified";
	    return(-2);
	}

	/* Media not allowed to be ejected from device? */
	if(EDV_DEVICE_IS_NO_UNMOUNT(d))
	{
	    last_error = "Device does not permit ejecting";
	    return(-2);
	}

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

	/* Get copy of device path */
	device_path = STRDUP(d->device_path);
	if(device_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Device path not specified by the device";
	    return(-2);
	}

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

	/* Execute command */
	pid = ExecB(cmd);
	if(pid == 0)
	{
	    last_error = "Execution of eject command failed";
	    status = -1;
	}
	else
	{
	    status = 0;
	}

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

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}
gint EDVEject(
	edv_context_struct *ctx, edv_device_struct *d
)
{
	return(EDVDeviceEject(ctx, d));
}
