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

#include "edvtypes.h"
#include "edvdevices.h"
#include "edvcontext.h"
#include "edvnotify.h"
#include "edvmount.h"
#include "config.h"


const gchar *EDVMountGetError(void);
gint EDVMount(
	edv_context_struct *ctx, edv_device_struct *dev_ptr,
	gboolean notify
);
gint EDVUnmount(
	edv_context_struct *ctx, edv_device_struct *dev_ptr,
	gboolean notify
);
gint EDVEject(
	edv_context_struct *ctx, edv_device_struct *dev_ptr
);


static const gchar *last_error;


#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 *EDVMountGetError(void)
{
	return(last_error);
}


/*
 *	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 EDVMount(
	edv_context_struct *ctx, edv_device_struct *dev_ptr,
	gboolean notify
)
{
	gint status;
	pid_t pid;
	gchar	*cmd = NULL,
		*mount_path = NULL,
		*device_path = NULL;
	const gchar *fs_type_str;


	last_error = NULL;		/* Reset last error  */

	if(dev_ptr == 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(dev_ptr->is_mounted)
	{
            last_error = "Device is already mounted";
            return(-2);
	}

	/* Get copy of mount path */
	mount_path = STRDUP(dev_ptr->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(dev_ptr->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_type_str = EDVDeviceGetFSStringFromNumber(
	    dev_ptr->fs_type
	);
	if(fs_type_str == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get file system type";
	    return(-2);
	}

	/* Format mount command */
	if(!STRISEMPTY(dev_ptr->command_mount))
	    cmd = STRDUP(dev_ptr->command_mount);
	else
	    cmd = g_strdup_printf(
		"/bin/mount -t %s \"%s\" \"%s\" %s",
		fs_type_str,
		device_path,
		mount_path,
		dev_ptr->read_only ? "-r" : ""
	    );

	/* Execute command */
	pid = ExecB(cmd);
	if(pid == 0)
	{
	    last_error = "Execution of mount command failed";
	    status = -1;
	}
	else
	{
/*	    dev_ptr->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
}

/*
 *	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 EDVUnmount(
	edv_context_struct *ctx, edv_device_struct *dev_ptr,
	gboolean notify
)
{
	gint status;
	pid_t pid;
	gchar	*cmd = NULL,
		*mount_path = NULL;


	last_error = NULL;		/* Reset last error */

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

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

        /* Not mounted? */
        if(!dev_ptr->is_mounted)
        {
            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(dev_ptr->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(dev_ptr->command_unmount))
	    cmd = STRDUP(dev_ptr->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
}

/*
 *	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 EDVEject(
	edv_context_struct *ctx, edv_device_struct *dev_ptr
)
{
	gint status;
	pid_t pid;
	gchar	*cmd = NULL,
		*device_path = NULL;


	last_error = NULL;		/* Reset last error */

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

	/* Media not allowed to be ejected from device? */
	if(dev_ptr->no_unmount)
	{
	    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(dev_ptr->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(dev_ptr->command_eject))
	    cmd = STRDUP(dev_ptr->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
}
