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

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

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

#include "edvtypes.h"
#include "endeavour.h"
#include "edvfcreate.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "config.h"


/*
 *	Return values have the following error meanings:
 *
 *	0	Success (no error)
 *	-1	General error
 *	-2	Ambiguous, not found, other error
 *	-3	Systems error (out of memory/out of disk space)
 *	-4	User responded with "Cancel"
 *	-5	User responded with "No" or response is not available
 *	-6	Call would cause reentry
 */

const gchar *EDVFCreateGetError();

static const gchar *EDVFCreateGetNameFromType(gint type);
static gchar *EDVFCreateGenerateNewPath(
	const gchar *path, gint type
);

static gint EDVFCreateNexus(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all, edv_object_type type
);

gint EDVFCreateFile(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFCreateDirectory(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,		/* Dynamically allocated */
	GtkWidget *toplevel,		/* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFCreateLink(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,		/* Dynamically allocated */
	GtkWidget *toplevel,		/* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFCreateFifo(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,		/* Dynamically allocated */
	GtkWidget *toplevel,		/* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFCreateDeviceBlock(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,		/* Dynamically allocated */
	GtkWidget *toplevel,		/* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFCreateDeviceCharacter(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,		/* Dynamically allocated */
	GtkWidget *toplevel,		/* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFCreateSocket(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,		/* Dynamically allocated */
	GtkWidget *toplevel,		/* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);


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

/*
 *	Returns a statically allocated string describing (without spaces)
 *	the given type which can be one of EDV_OBJECT_TYPE_*.
 */
static const gchar *EDVFCreateGetNameFromType(gint type)
{
	switch(type)
	{
	  case EDV_OBJECT_TYPE_UNKNOWN:
	    return("object");
	    break;
	  case EDV_OBJECT_TYPE_FILE:
	    return("file");
	    break;
	  case EDV_OBJECT_TYPE_DIRECTORY:
	    return("directory");
	    break;
	  case EDV_OBJECT_TYPE_LINK:
	    return("link");
	    break;
	  case EDV_OBJECT_TYPE_FIFO:
	    return("fifo");
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_BLOCK:
	    return("block_device");
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
	    return("character_device");
	    break;
	  case EDV_OBJECT_TYPE_SOCKET:
	    return("socket");
	    break;
	}

	return("object");
}

/*
 *	Generates a full path to a new object with a generic name
 *	based on the given path.
 *
 *	The returned string is dynamically allocated and must be 
 *	deallocated by the calling function.
 */
static gchar *EDVFCreateGenerateNewPath(
	const gchar *path, gint type
)
{
	guint16 i;
	const gchar *type_str;
	gchar *new_path;


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

	/* Get string containing the description of the given type */
	type_str = EDVFCreateGetNameFromType(type);
	if(type_str == NULL)
	    return(NULL);

	/* Generate new path based on the given path and type, but do
	 * not postfix a number on the first try. If any only this path
	 * exists will a different new path be generated with a
	 * postfixed number
	 */
	new_path = g_strdup_printf(
	    "%s%cnew_%s",
	    path, G_DIR_SEPARATOR, type_str
	);
	if(new_path == NULL)
	    return(NULL);

	/* Generated new_path does not exist? */
	if(access((const char *)new_path, F_OK))
	    return(new_path);

	/* If this point is reached then it means an object already
	 * exists with a generic new name
	 *
	 * Try and find other non-existent objects by postfixing a
	 * number (starting from 1) to the new name and test it
	 */
	for(i = 1; i < (guint16)-1; i++)
	{
	    /* Deallocate previous new_path and generate a new one with a
	     * number postfixed to it.
	     */
	    g_free(new_path);
	    new_path = g_strdup_printf(
		"%s%cnew_%s%i",
		path, G_DIR_SEPARATOR, type_str, i
	    );

	    /* Generated new_path does not exist? */
	    if(access((const char *)new_path, F_OK))
		return(new_path);
	}

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

/*
 *      Procedure create a new object specified by type at the location
 *	specified by path.
 *
 *      If the given path reffers to a non-existent object then the new
 *      object will be created as that name. Otherwise if path reffers to
 *      an existing location (which must be a directory) then a
 *      generic name will be used for the new object.
 *
 *      If new_obj_rtn is not NULL then a dynamically allocated string
 *      containing the full path of the new object will be returned.
 *      The calling function needs to deallocate it.
 *
 *      If show_progress is TRUE then the progress dialog will be updated
 *      (and mapped as needed). The calling function needs to unmap the
 *      progress dialog.
 *
 *      Returns non-zero on error.
 */
static gint EDVFCreateNexus(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all, edv_object_type type
)
{
	static gboolean reenterent = FALSE;
	gint status;
	gulong time_start = (gulong)time(NULL);
	gchar *lpath = NULL, *new_path = NULL;
	struct stat stat_buf;


	/* Reset returns */
	if(new_obj_rtn != NULL)
	    *new_obj_rtn = NULL;
	/* Leave yes_to_all as whatever value the calling function set it */

	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

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

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

	if((core_ptr == NULL) || STRISEMPTY(path) || (yes_to_all == NULL))
	{
	    last_error = "Bad input value";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-1);
	}

	/* Make a copy of the given path */
	lpath = STRDUP(path);

	/* Simplify local coppies of the paths */
	EDVSimplifyPath(lpath);


	/* Attempt to stat the given path's destination */
	if(stat((const char *)lpath, &stat_buf))
	{
	    /* Unable to stat destination, check if there is a fatal
	     * reason
	     *
	     * Skip errors for non-existant object
	     */
	    switch(errno)
	    {
	      case ENOTDIR:
		last_error = "A compoent of the path is not a directory";
		status = -2;
	      case ELOOP:
		last_error = "Too many loops encountered in path";
		status = -2;
	      case EFAULT:
		last_error = "Path memory address violation";
		status = -3;
	      case EACCES:
		last_error = "Permission denied";
		status = -2;
	      case ENOMEM:
		last_error = "System is out of memory";
		status = -3;
	      case ENAMETOOLONG:
		last_error = "Path is too long";
		status = -2;

		/* All of the above cases are fatal errors, let them
		 * fall through to this point where we return the error.
		 */
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(status);
		break;
	    }
	    /* If this point is reached then it means the given path does
	     * not exist so we can use it directly as the new object's
	     * name
	     */
	    new_path = STRDUP(lpath);
	}
	else
	{
	    /* Given path exists, now check if its destination is not a
	     * directory. If destination is not a directory then a new
	     * object cannot be created.
	     */
	    if(!S_ISDIR(stat_buf.st_mode))
	    {
		last_error =
"The location to create the new object at is not a directory";
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(-2);
	    }

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

	/* At this point new_path specifies the path to the new object
	 * that is to be created. It is gauranteed that the object
	 * specified by new_path does not exist yet.
	 */

	status = 0;

	/* Begin creating by type */
	/* File? */
	if(type == EDV_OBJECT_TYPE_FILE)
	{
	    FILE *fp = FOpen((const char *)new_path, "wb");
	    if(fp == NULL)
	    {
		last_error =
"Unable to create new file at the specified path";
		status = -2;
	    }
	    else
	    {
		/* Opened new file for writing
		 *
		 * Do not put anything into it, leave it empty and
		 * just close it
		 */
		FClose(fp);
	    }
	}
	/* Directory */
	else if(type == EDV_OBJECT_TYPE_DIRECTORY)
	{
	    mode_t m = S_IRWXU | S_IRWXG | S_IRWXO;

	    if(mkdir((const char *)new_path, m))
	    {
		last_error =
"Unable to create new directory at the specified path";
		status = -2;
	    }
	    else
	    {

	    }
	}
	/* Link */
	else if(type == EDV_OBJECT_TYPE_LINK)
	{
	    if(symlink("undefined", (const char *)new_path))
	    {
		last_error =
"Unable to create new symbolic link at the specified path";
		status = -2;
	    }
	    else
	    {

	    }
	}

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


	/* Record history */
	EDVAppendHistory(
	    core_ptr,
	    EDV_HISTORY_DISK_OBJECT_CREATE,
	    time_start, (gulong)time(NULL),
	    status,
	    path,		/* Source full path */
	    (new_obj_rtn != NULL) ? *new_obj_rtn : new_path,
	    last_error          /* Use last_error as comment if not NULL */
	);

	DO_FREE_LOCALS

	reenterent = FALSE;

	return(status);
#undef DO_FREE_LOCALS
}


/*
 *	Front end to create a file.
 */
gint EDVFCreateFile(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFCreateNexus(
	    core_ptr, path, new_obj_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all, EDV_OBJECT_TYPE_FILE
	));
}

/*
 *      Front end to create a directory.
 */
gint EDVFCreateDirectory(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFCreateNexus(
	    core_ptr, path, new_obj_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all, EDV_OBJECT_TYPE_DIRECTORY
	));
}

/*
 *      Front end to create a symbolic link.
 */
gint EDVFCreateLink(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFCreateNexus(
	    core_ptr, path, new_obj_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all, EDV_OBJECT_TYPE_LINK
	));
}

/*
 *      Front end to create a FIFO pipe.
 */
gint EDVFCreateFifo(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFCreateNexus(
	    core_ptr, path, new_obj_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all, EDV_OBJECT_TYPE_FIFO
	));
}

/*
 *      Front end to create a block device.
 */
gint EDVFCreateDeviceBlock(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFCreateNexus(
	    core_ptr, path, new_obj_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all, EDV_OBJECT_TYPE_DEVICE_BLOCK
	));
}

/*
 *      Front end to create a character device.
 */
gint EDVFCreateDeviceCharacter(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFCreateNexus(
	    core_ptr, path, new_obj_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all, EDV_OBJECT_TYPE_DEVICE_CHARACTER
	));
}

/*
 *      Front end to create a socket.
 */
gint EDVFCreateSocket(
	edv_core_struct *core_ptr,
	const gchar *path,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFCreateNexus(
	    core_ptr, path, new_obj_rtn,
	    toplevel, show_progress, interactive,
	    yes_to_all, EDV_OBJECT_TYPE_SOCKET
	));
}
