#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <unistd.h>

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

#include "../lib/endeavour2.h"
#include "../edv_utils.h"

#include "ziptoolio.h"
#include "config.h"


static void ZipToolLoadFileToString(
	const gchar *path, gchar *buf, gint buf_len
);
static gchar *ZipToolGetZipToolProg(void);

gchar *ZipToolLastError(void);

gint ZipToolMount(edv_device_struct *dev_ptr);
gint ZipToolUnmount(edv_device_struct *dev_ptr);

zip_tool_lock_state ZipToolDeviceIsProtected(edv_device_struct *dev_ptr);

gint ZipToolLock(
	edv_device_struct *dev_ptr, const gchar *password
);
gint ZipToolUnlock(
	edv_device_struct *dev_ptr, const gchar *password
);

gint ZipToolSpinDown(edv_device_struct *dev_ptr);
gint ZipToolEject(edv_device_struct *dev_ptr);


#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)	(STRISEMPTY(p) ? -1 : unlink(p))

#define DEV_NULL_PATH		"/dev/null"


static gchar last_error[1024];


/*
 *	Reads the contents of the file into the specified string.
 */
static void ZipToolLoadFileToString(
	const gchar *path, gchar *buf, gint buf_len
)
{
	FILE *fp = !STRISEMPTY(path) ? FOpen(path, "rb") : NULL;
	if(fp == NULL)
	{
	    if((buf != NULL) && (buf_len > 0))
		*buf = '\0';
	    return;
	}

	if(buf != NULL)
	{
	    gint bytes_read = (gint)fread(
		(void *)buf, (size_t)sizeof(gchar), (size_t)buf_len, fp
	    );
 	    if(bytes_read >= buf_len)
		bytes_read = buf_len - 1;
	    if(bytes_read >= 0)
		buf[bytes_read] = '\0';
	    else
		*buf = '\0';
	}

	FClose(fp);
}

/*
 *	Returns the full path to the ZipTool program or NULL on error.
 *
 *	The last_error will be updated if the ZipTool program is not
 *	found.
 *
 *	The calling function must deleted the returned string.
 */
static gchar *ZipToolGetZipToolProg(void)
{
	gchar *prog = EDVWhich(ZIPTOOL_PROG);
	if(prog == NULL)
	    g_snprintf(
		last_error, sizeof(last_error),
"Unable to find the program \"%s\".",
		ZIPTOOL_PROG
	    );
	return(prog);
}

/*
 *	Returns a statically allocated string describing the last
 *	error or NULL if there was no error.
 */
gchar *ZipToolLastError(void)
{
	return(STRISEMPTY(last_error) ? NULL : last_error);
}

/*
 *	Mounts the device.
 *
 *      Returns:
 *
 *      0       Success (or device is already mounted).
 *      -1      General error
 *      -2      Bad or ambiguous value
 *      -3      Systems error or unable to execute command
 */
gint ZipToolMount(edv_device_struct *dev_ptr)
{
	pid_t p;
	gint fd;
	const gchar *tmp_dir;
	gchar *cmd, *prog, *stderr_path;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);
	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);
	if(STRISEMPTY(dev_ptr->mount_path))
	    return(-2);

	/* Get the ZipTool program */
	prog = ZipToolGetZipToolProg();
	if(prog == NULL)
	    return(-1);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))      
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp"; 
#endif

	/* Generate tempory output file paths */
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	fd = (gint)mkstemp(stderr_path);
	if(fd > -1)
	    close((gint)fd);

	/* Format and execute mount command */
	cmd = g_strdup_printf(
	    "%s -m %s %s",
	    prog,
	    dev_ptr->device_path, dev_ptr->mount_path
	);
	p = ExecBOE(cmd, DEV_NULL_PATH, stderr_path);
	g_free(cmd);

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)  
	);

	/* Remove tempory output files */
	UNLINK(stderr_path);
	g_free(stderr_path);

	g_free(prog);

	return((p == 0) ? -3 : 0);
}

/*
 *	Unmounts the device.
 *
 *      Returns:
 *
 *      0       Success (or device is already unmounted).
 *      -1      General error
 *      -2      Bad or ambiguous value
 *      -3      Systems error or unable to execute command
 */
gint ZipToolUnmount(edv_device_struct *dev_ptr)
{
	pid_t p;
	gint fd;
	const gchar *tmp_dir;
	gchar *cmd, *prog, *stderr_path;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);
	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Get the ZipTool program */
	prog = ZipToolGetZipToolProg();
	if(prog == NULL)
	    return(-1);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))      
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp"; 
#endif

	/* Generate tempory output file paths */   
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	fd = (gint)mkstemp(stderr_path);
	if(fd > -1)
	    close((gint)fd);

	/* Format and execute unmount command */
	cmd = g_strdup_printf(
	    "%s -u %s",
	    prog,
	    dev_ptr->device_path
	);
	p = ExecBOE(cmd, DEV_NULL_PATH, stderr_path);
	g_free(cmd);

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)
	);

	/* Remove tempory output files */
	UNLINK(stderr_path);
	g_free(stderr_path);

	g_free(prog);

	return((p == 0) ? -3 : 0);
}

/*
 *	Checks if the device is unlocked, locked, or locked with
 *	password.
 */
zip_tool_lock_state ZipToolDeviceIsProtected(edv_device_struct *dev_ptr)
{
	zip_tool_lock_state status = ZIP_TOOL_LOCK_STATE_UNLOCKED;
	pid_t p;
	gint fd;
	const gchar *tmp_dir;
	gchar *cmd, *prog, *stdout_path, *stderr_path;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(status);
	if(STRISEMPTY(dev_ptr->device_path))
	    return(status);

	/* Get the ZipTool program */
	prog = ZipToolGetZipToolProg();
	if(prog == NULL)
	    return(status);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))      
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp"; 
#endif

	/* Generate tempory output file paths */
	stdout_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	fd = (gint)mkstemp(stdout_path);
	if(fd > -1)
	    close((gint)fd);
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	fd = (gint)mkstemp(stderr_path);
	if(fd > -1)
	    close((gint)fd);

	/* Format and execute device status command */
	cmd = g_strdup_printf(
	    "%s -s %s",
	    prog,
	    dev_ptr->device_path
	);
	p = ExecBOE(cmd, stdout_path, stderr_path);
	g_free(cmd);
	if(p == 0)
	{
	    /* Error */
	    UNLINK(stdout_path);
	    g_free(stdout_path);
	    UNLINK(stderr_path);
	    g_free(stderr_path);
	    g_free(prog);
	    return(status);
	}
	else
	{
	    /* Success, read first line of output file */
	    FILE *stdout_fp = FOpen(stdout_path, "rb");
	    gchar *buf = FGetStringLiteral(stdout_fp);
	    FClose(stdout_fp);

	    /* Got first line of output file? */
	    if(buf != NULL)
	    {
		if(strstr(
		    buf,
		    "password write-protected"
		) != NULL)
		    status = ZIP_TOOL_LOCK_STATE_LOCKED_PASSWORD;
		else if(strstr(
		    buf,
		    "write-protected"
		) != NULL)
		    status = ZIP_TOOL_LOCK_STATE_LOCKED;
		else if(strstr(
		    buf,
		    "not protected"
		) != NULL)
		    status = ZIP_TOOL_LOCK_STATE_UNLOCKED;

		/* All else assume not protected */

		g_free(buf);
	    }
	}

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)
	);

	/* Remove tempory output files */
	UNLINK(stdout_path);
	g_free(stdout_path);
	UNLINK(stderr_path);
	g_free(stderr_path);

	g_free(prog);

	return(status);
}


/*
 *	Locks the device (must be currently unlocked). If password
 *	is not NULL then the device will be locked with password.
 *
 *	Returns:
 *
 *	0	Success
 *	-1	General error
 *	-2	Bad or ambiguous value
 *	-3	Systems error or unable to execute command
 *	-4	Unable to lock
 *	-5	Device is already locked (with or without password),
 *		must unlock before locking again
 */
gint ZipToolLock(
	edv_device_struct *dev_ptr, const gchar *password
)
{
	gchar *prog;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);
	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Get the ZipTool program */
	prog = ZipToolGetZipToolProg();
	if(prog == NULL)
	    return(-1);

	/* Device is already locked? */
	if(ZipToolDeviceIsProtected(dev_ptr) != ZIP_TOOL_LOCK_STATE_UNLOCKED)
	{
	    g_free(prog);
	    return(-5);
	}

	/* Lock with password? */
	if(!STRISEMPTY(password))
	{
	    /* Format and execute lock device with password command */
	    gchar *cmd = g_strdup_printf(
		"%s -rp %s",
		prog,
		dev_ptr->device_path
	    );
	    FILE *fp = popen(cmd, "w");
	    g_free(cmd);
	    if(fp == NULL)
	    {
		g_free(prog);
		return(-3);
	    }

	    /* Send password */
	    fprintf(fp, "%s\n", password);

	    pclose(fp);
	}
	else
	{
	    /* Format and execute lock device command */
	    gchar *cmd = g_strdup_printf(
		"%s -ro %s",
		prog,
		dev_ptr->device_path
	    );
	    FILE *fp = popen(cmd, "w");
	    g_free(cmd);
	    if(fp == NULL)
	    {
		g_free(prog);
		return(-3);
	    }

	    /* Do not send any password */

	    pclose(fp);
	}

	g_free(prog);

#if 0
	/* Device was not successfully locked? */
/* This does not work quite well, because the device is ejected after
 * locking and there is no way to see if it was locked correctly
 */
	if(ZipToolDeviceIsProtected(dev_ptr) == ZIP_TOOL_LOCK_STATE_UNLOCKED)
	    return(0);
	else
	    return(-4);
#endif
	return(0);
}


/*
 *	Unlocks the device (if it is already locked) and uses the
 *	given password.
 *
 *	Returns:
 *
 *	0	Success (or device is already unlocked)
 *	-1	General error
 *	-2	Bad or ambiguous value
 *	-3	Systems error or unable to execute command
 *	-4	Bad password or unable to unlock
 */
gint ZipToolUnlock(
	edv_device_struct *dev_ptr, const gchar *password
)
{
	gchar *cmd, *prog;
	FILE *fp;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);
	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Get the ZipTool program */
	prog = ZipToolGetZipToolProg();
	if(prog == NULL)
	    return(-1);

	/* Device is not locked? */
	if(ZipToolDeviceIsProtected(dev_ptr) == ZIP_TOOL_LOCK_STATE_UNLOCKED)
	{
	    g_free(prog);
	    return(0);
	}

	if(password == NULL)
	    password = "";

	/* Format and execute unlock device command */
	cmd = g_strdup_printf(
	    "%s -rw %s",
	    prog,
	    dev_ptr->device_path
	);
	fp = popen(cmd, "w");
	g_free(cmd);
	if(fp == NULL)
	{
	    g_free(prog);
	    return(-3);
	}

	/* Send password */
	fprintf(fp, "%s\n", password);

	pclose(fp);

	g_free(prog);

	/* Device was not successfully unlocked? */
	return((ZipToolDeviceIsProtected(dev_ptr) != ZIP_TOOL_LOCK_STATE_UNLOCKED) ?
	    -4 : 0
	);
}

/*
 *	Spins down the device
 *
 *	Returns:
 *
 *	0	Success (or device already spinned down)
 *	-1	General error
 *	-2	Bad or ambiguous value
 *	-3	Systems error or unable to execute command
 */
gint ZipToolSpinDown(edv_device_struct *dev_ptr)
{
	pid_t p;
	gint fd;
	const gchar *tmp_dir;
	gchar *cmd, *prog, *stderr_path;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);
	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Get the ZipTool program */
	prog = ZipToolGetZipToolProg();
	if(prog == NULL)
	    return(-1);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp";
#endif

	/* Generate tempory output file paths */
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	fd = (gint)mkstemp(stderr_path);
	if(fd > -1)
	    close((gint)fd);

	/* Format and execute spin down command */
	cmd = g_strdup_printf(
	    "%s -p %s",
	    prog,
	    dev_ptr->device_path
	);
	p = ExecBOE(cmd, DEV_NULL_PATH, stderr_path);
	g_free(cmd);

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)
	);
	  
	/* Remove tempory output files */   
	UNLINK(stderr_path);
	g_free(stderr_path);

	g_free(prog);

	return((p == 0) ? -3 : 0);
}

/*
 *	Ejects the media from the device
 *
 *      Returns:
 *
 *      0       Success (or media is already ejected)
 *      -1      General error
 *      -2      Bad or ambiguous value
 *      -3      Systems error or unable to execute command
 *      -4      Device is currently mounted
 */
gint ZipToolEject(edv_device_struct *dev_ptr)
{
	pid_t p;
	gint fd;
	const gchar *tmp_dir;
	gchar *cmd, *prog,  *stderr_path;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);
	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Get the ZipTool program */
	prog = ZipToolGetZipToolProg();
	if(prog == NULL)
	    return(-1);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp";
#endif

	/* Generate tempory output file paths */
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	fd = (gint)mkstemp(stderr_path);
	if(fd > -1)
	    close((gint)fd);

	/* Format and execute eject command */
	cmd = g_strdup_printf(
	    "%s -e %s",
	    prog,
	    dev_ptr->device_path
	);
	p = ExecBOE(cmd, DEV_NULL_PATH, stderr_path);
	g_free(cmd);

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)
	);

	/* Remove tempory output files */   
	UNLINK(stderr_path);
	g_free(stderr_path);

	g_free(prog);

	return((p == 0) ? -3 : 0);
}
