#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <errno.h>
#include <sys/stat.h>
#include <gtk/gtk.h>
#include <fcntl.h>
#include <unistd.h>

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

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

#include "edv_types.h"
#include "edv_obj_create.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "config.h"


/*
 *	Return values legend:
 *
 *	0	Success.                        
 *	-1	General error.
 *	-2	Invalid value.
 *	-3	Systems error; out of memory or out of disk space.    
 *	-4	User responded with "Cancel".                     
 *	-5	User responded with "No" or response was not available.
 *	-6	Call would cause reentry.    
 */


/* Error Message */  
const gchar *EDVObjCreateGetError(void);
static void EDVObjCreateCopyErrorMessage(const gchar *msg);

/* New Path */
static gchar *EDVObjCreateGenerateNewPath(
	const gchar *path, const edv_object_type type
);

static gint EDVObjCreateNexus(
	edv_core_struct *core,
	const gchar *path,
	const edv_object_type type,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);

gint EDVObjCreateFile(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjCreateDirectory(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjCreateLink(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjCreateFifo(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjCreateDeviceBlock(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjCreateDeviceCharacter(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjCreateSocket(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);


static const gchar *last_error = NULL;
static gchar last_error_buf[256];


#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 the last error message or NULL if there was no error.
 *
 *      The returned pointer must not be deallocated.
 */
const gchar *EDVObjCreateGetError(void)
{
	return(last_error);
}

/*
 *	Coppies the error message specified by msg to the last error
 *	message buffer and sets last_error to point to that buffer.
 */
static void EDVObjCreateCopyErrorMessage(const gchar *msg)
{
	if(msg == NULL)
	    return;

	strncpy(last_error_buf, msg, sizeof(last_error_buf));
	last_error_buf[sizeof(last_error_buf) - 1] = '\0';
	last_error = last_error_buf;
}


/*
 *	Generates a path to a new object.
 *
 *	The path specifies the full path to the directory at where
 *	the new object is to be created.
 *
 *	The type specifies the type of object to be created. The type
 *	will be used as part of the generated path's name.
 *
 *	Returns a dynamically allocated string describing the full
 *	path to the new object.
 */
static gchar *EDVObjCreateGenerateNewPath(
	const gchar *path, const edv_object_type type
)
{
	guint16 i;
	gchar *type_str, *new_path;

	if(STRISEMPTY(path))
	    return(NULL);

	/* Get the type string from the type code */
	type_str = STRDUP(EDVObjectGetTypeName(type));
	if(type_str == NULL)
	    return(NULL);

	strtolower(type_str);

	/* Generate a new path */
	new_path = g_strconcat(
	    path,
	    G_DIR_SEPARATOR_S,
	    "new_",
	    type_str,
	    NULL
	);

	if(new_path == NULL)
	{
	    g_free(type_str);
	    return(NULL);
	}

	/* Does the new path not refer to an existing object? */
	if(access((const char *)new_path, F_OK))
	{
	    g_free(type_str);
	    return(new_path);
	}

	/* An object already exists at the generated new path, so
	 * regenerate a new pth with a number appended to it and
	 * regenerate in the same way as needed until a path is
	 * generated that does not refer to an existing object
	 */
	for(i = 2; i < (guint16)-1; i++)
	{
	    /* Delete the previously generated new path */
	    g_free(new_path);

	    /* Generate a new path with a number appended to it */
	    new_path = g_strdup_printf(
		"%s%cnew_%s%u",
		path, G_DIR_SEPARATOR, type_str, i
	    );

	    /* Does the new path not refer to an existing object? */
	    if(access((const char *)new_path, F_OK))
	    {
		g_free(type_str);
		return(new_path);
	    }
	}

	/* If this point is reached then we failed to generate a new
	 * path to a non-existent object
	 *
	 * Delete the last generated new path and return NULL
	 */
	g_free(new_path);
	g_free(type_str);

	return(NULL);
}

/*
 *	Creates a new object of the specified type.
 *
 *	The path specifies the full path to either a non-existant
 *	object to be created or an existing directory at which to
 *	create the new object in.
 *
 *	If new_path_rtn is not NULL then a dynamically allocated
 *	string describing the full path to the new object will be set
 *	to it.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success and non-zero on error.
 */
static gint EDVObjCreateNexus(
	edv_core_struct *core,
	const gchar *path,
	const edv_object_type type,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	const gulong time_start = (gulong)time(NULL);
	struct stat stat_buf;
	gint status;
	gchar *lpath = NULL, *new_path = NULL;

	if(new_path_rtn != NULL)
	    *new_path_rtn = NULL;

	/* Leave the value of yes_to_all as is */

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

#define DO_FREE_LOCALS	{	\
 g_free(lpath);			\
 g_free(new_path);		\
}

	if((core == NULL) || STRISEMPTY(path) ||
	   (yes_to_all == NULL)
	)
	{
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-2);
	}

	/* Copy the specified path */
	lpath = STRDUP(path);

	/* Simplify the path */
	EDVSimplifyPath(lpath);

	/* Get the statistics of the object at the specified path */
	if(stat((const char *)lpath, &stat_buf))
	{
	    const gint error_code = (gint)errno;
	    if(error_code == ENOENT)
	    {
		/* No such object exists at the specified path's
		 * location so use it as the path to the new object
		 */
		new_path = STRDUP(lpath);
	    }
	    else
	    {
		gchar *s = g_strconcat(
		    "Unable to create a new ",
		    EDVObjectGetTypeNameLower(type),
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjCreateCopyErrorMessage(s);
		g_free(s);
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(-1);
	    }
	}
	else
	{
	    /* The specified path exists
	     *
	     * Check if its destination is not a directory, in which
	     * case no new object can be created
	     */
#ifdef S_ISDIR
	    if(!S_ISDIR(stat_buf.st_mode))
#else
	    if(TRUE)
#endif
	    {
		last_error =
"The location to create the new object at is not a directory";
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(-1);
	    }

	    /* Generate a new name based on the specified path */
	    new_path = EDVObjCreateGenerateNewPath(lpath, type);
	}
	/* Unable to generate the full path to the new object? */
	if(new_path == NULL)
	{
	    last_error = "Unable to generate the full path for the new object";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-2);
	}

	/* At this point new_path specifies the full path to the new
	 * object that is to be created
	 *
	 * Begin creating by type
	 */
	status = 0;

	/* File? */
	if(type == EDV_OBJECT_TYPE_FILE)
	{
	    FILE *fp = fopen((const char *)new_path, "wb");
	    if(fp != NULL)
	    {
		fclose(fp);
	    }
	    else
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to create a new ",
		    EDVObjectGetTypeNameLower(type),
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjCreateCopyErrorMessage(s);
		g_free(s);
		status = -1;
	    }
	}
	/* Directory */
	else if(type == EDV_OBJECT_TYPE_DIRECTORY)
	{
	    const guint m = EDVGetUMask();
	    if(mkdir(
		(const char *)new_path,
		(mode_t)(~m) &
		    (S_IRUSR | S_IWUSR | S_IXUSR |
		     S_IRGRP | S_IWGRP | S_IXGRP |
		     S_IROTH | S_IWOTH | S_IXOTH)
	    ))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to create a new ",
		    EDVObjectGetTypeNameLower(type),
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjCreateCopyErrorMessage(s);
		g_free(s);
		status = -1;
	    }
	}
	/* Link */
	else if(type == EDV_OBJECT_TYPE_LINK)
	{
	    if(symlink("undefined", (const char *)new_path))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to create a new ",
		    EDVObjectGetTypeNameLower(type),
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjCreateCopyErrorMessage(s);
		g_free(s);
		status = -1;
	    }
	}
	/* FIFO Pipe */
	else if(type == EDV_OBJECT_TYPE_FIFO)
	{
#if defined(S_IFFIFO) || defined(S_IFIFO)
	    const guint m = EDVGetUMask();
	    if(mknod(
		(const char *)new_path,
#if defined(S_IFFIFO)
		S_IFFIFO | ((mode_t)(~m) &
		    (S_IRUSR | S_IWUSR |
		     S_IRGRP | S_IWGRP |
		     S_IROTH | S_IWOTH)
		),
#else
		S_IFIFO | ((mode_t)(~m) &
		    (S_IRUSR | S_IWUSR |
		     S_IRGRP | S_IWGRP |
		     S_IROTH | S_IWOTH)
		),
#endif
		0
	    ))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to create a new ",
		    EDVObjectGetTypeNameLower(type),
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjCreateCopyErrorMessage(s);
		g_free(s);
		status = -1;
	    }
#else
	    last_error = "Unsupported object type";
	    status = -2;
#endif
	}
	/* Block Device */
	else if(type == EDV_OBJECT_TYPE_DEVICE_BLOCK)
	{
#if defined(S_IFBLK)
	    const guint m = EDVGetUMask();
	    const gint dev_num = EDVFormatDeviceNumbers(1, 1);
	    if(mknod(
		(const char *)new_path,
		S_IFBLK | ((mode_t)(~m) &
		    (S_IRUSR | S_IWUSR |
		     S_IRGRP | S_IWGRP |
		     S_IROTH | S_IWOTH)
		),
		(dev_t)dev_num
	    ))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to create a new ",
		    EDVObjectGetTypeNameLower(type),
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjCreateCopyErrorMessage(s);
		g_free(s);
		status = -1;
	    }
#else
	    last_error = "Unsupported object type";
	    status = -2;
#endif
	}
	/* Character Device */
	else if(type == EDV_OBJECT_TYPE_DEVICE_CHARACTER)
	{
#if defined(S_IFCHR)
	    const guint m = EDVGetUMask();
	    const gint dev_num = EDVFormatDeviceNumbers(1, 1);
	    if(mknod(
		(const char *)new_path,
		S_IFCHR | ((mode_t)(~m) &
		    (S_IRUSR | S_IWUSR |
		     S_IRGRP | S_IWGRP |
		     S_IROTH | S_IWOTH)
		),
		(dev_t)dev_num
	    ))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to create a new ",
		    EDVObjectGetTypeNameLower(type),
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjCreateCopyErrorMessage(s);
		g_free(s);
		status = -1;
	    }
#else
	    last_error = "Unsupported object type";
	    status = -2;
#endif
	}
	/* Socket */
	else if(type == EDV_OBJECT_TYPE_SOCKET)
	{
#if defined(S_IFSOCK)
	    const guint m = EDVGetUMask();
	    if(mknod(
		(const char *)new_path,
		S_IFSOCK | ((mode_t)(~m) &
		    (S_IRUSR | S_IWUSR |
		     S_IRGRP | S_IWGRP |
		     S_IROTH | S_IWOTH)
		),
		0
	    ))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to create a new ",
		    EDVObjectGetTypeNameLower(type),
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjCreateCopyErrorMessage(s);
		g_free(s);
		status = -1;
	    }
#else
	    last_error = "Unsupported object type";
	    status = -2;
#endif
	}
	else
	{
	    last_error = "Unsupported object type";
	    status = -2;
	}

	/* Update new path return if creation of object was successful */
	if((status == 0) && (new_path_rtn != NULL))
	{
	    g_free(*new_path_rtn);
	    *new_path_rtn = STRDUP(new_path);
	}


	/* Record history */
	EDVAppendHistory(
	    core,
	    EDV_HISTORY_DISK_OBJECT_CREATE,
	    time_start, (gulong)time(NULL),
	    status,
	    path,		/* Source */
	    new_path,		/* Target */
	    last_error		/* Comment */
	);

	DO_FREE_LOCALS

	reenterent = FALSE;

	return(status);
#undef DO_FREE_LOCALS
}


/*
 *	Create a new file.
 */
gint EDVObjCreateFile(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjCreateNexus(
	    core,
	    path, EDV_OBJECT_TYPE_FILE,
	    new_path_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all
	));
}

/*
 *	Create a new directory.
 */
gint EDVObjCreateDirectory(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjCreateNexus(
	    core,
	    path, EDV_OBJECT_TYPE_DIRECTORY, new_path_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all
	));
}

/*
 *	Create a new link.
 */
gint EDVObjCreateLink(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjCreateNexus(
	    core,
	    path, EDV_OBJECT_TYPE_LINK,
	    new_path_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all
	));
}

/*
 *	Create a new FIFO pipe.
 */
gint EDVObjCreateFifo(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjCreateNexus(
	    core,
	    path, EDV_OBJECT_TYPE_FIFO,
	    new_path_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all
	));
}

/*
 *	Create a new block device.
 */
gint EDVObjCreateDeviceBlock(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjCreateNexus(
	    core, path,
	    EDV_OBJECT_TYPE_DEVICE_BLOCK,
	    new_path_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all
	));
}

/*
 *	Create a new character device.
 */
gint EDVObjCreateDeviceCharacter(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjCreateNexus(
	    core,
	    path, EDV_OBJECT_TYPE_DEVICE_CHARACTER,
	    new_path_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all
	));
}

/*
 *	Create a new socket.
 */
gint EDVObjCreateSocket(
	edv_core_struct *core,
	const gchar *path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjCreateNexus(
	    core,
	    path, EDV_OBJECT_TYPE_SOCKET,
	    new_path_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all
	));
}
