/*
 *	Recycles objects by placing them into the recycle bin.
 *
 *	Use "rls" to get a list of the recycle bin and "recover" to
 *	recover a recycled object.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <glib.h>
#include <endeavour2.h>
#include "../config.h"


static void print_help(const gchar *prog_name);
static gboolean recycle_query_user_recycle_object(const gchar *name);
static gint recycle_progress_cb(
	gpointer data, const gulong i, const gulong m
);
static guint recycle_object_iterate(
	edv_context_struct *ctx,
	const gchar *path,
	const gboolean interactive,
	const gboolean verbose,
	gint *status
);


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


/*
 *	Prints the help message.
 */
static void print_help(const gchar *prog_name)
{
	g_print(
"Usage: %s <object(s)...> [options]\n",
	    prog_name
	);

	g_print("\n\
    The <object(s)...> specifies one or more object(s) to recycle.\n\
\n\
    The [options] can be any of the following:\n\
\n\
	--interactive           Prompt before recycling an object.\n\
	-i                      Same as --interactive.\n\
	--quiet                 Do not print any messages to stdout.\n\
	-q                      Same as --quiet.\n\
	--help                  Prints (this) help screen and exits.\n\
	--version               Prints version information and exits.\n\
\n\
    Return values:\n\
\n\
	0       Success.\n\
	1       General error.\n\
	2       Invalid value.\n\
	3       Systems error or memory allocation error.\n\
	4       User aborted.\n\
\n\
    To list recycled objects, use \"rls\".\n\
    To recover recycled objects, use \"recover\".\n\
    To purge recycled objects, use \"purge\".\n\
\n"
	);
}


/*
 *	Queries the user to recycle the object specified by name.
 *
 *	Returns TRUE if the user responded with yes.
 */
static gboolean recycle_query_user_recycle_object(const gchar *name)
{
	gint c;

	g_print(
	    "Recycle `%s'? ",
	    name
	);
	c = (gint)fgetc(stdin);
	while(fgetc(stdin) != '\n');
	if((c == 'y') || (c == 'Y'))
	    return(TRUE);
	else
	    return(FALSE);
}

/*
 *	Recycle progress callback.
 *
 *	Prints a line stating the object being recycled and the progress
 *	bar to stdout.
 *
 *	No new line character is printed, so this prints on to the
 *	current line only.
 */
static gint recycle_progress_cb(
	gpointer data, const gulong i, const gulong m
)
{
	gint _i, n, _m;
	gchar *s;
	const gchar *path = (const gchar *)data;
	gfloat coeff = (m > 0) ? ((gfloat)i / (gfloat)m) : 0.0f;

	/* Print name */
	s = g_strdup_printf("\rRecycling %s", path);
	_i = strlen(s);
	printf(s);
	g_free(s);

	/* Print spacing between name and progress bar */
	n = 50;				/* Column to stop at */
	for(; _i < n; _i++)
	    fputc(' ', stdout);

	/* Print progress bar */
	fputc('[', stdout);		/* Left start bracket */
	_m = 23;			/* Width of bar - 2 */
	n = (int)(_m * coeff) - 1;
	for(_i = 0; _i < n; _i++)
	    fputc('=', stdout);
	fputc((coeff >= 1.0f) ? '=' : '>', stdout);
	_i++;
	for(; _i < _m; _i++)
	    fputc('-', stdout);
	fputc(']', stdout);		/* Right end bracket */

	fflush(stdout);			/* Needed since no newline */

	return(0);
}

/*
 *	Recycles the object specified by path.
 *
 *	If the path is a directory then its child objects will be
 *	recycled too.
 *
 *	Returns the index or 0 on error.
 */
static guint recycle_object_iterate(
	edv_context_struct *ctx,
	const gchar *path,
	const gboolean interactive,
	const gboolean verbose,
	gint *status
)
{
	struct stat stat_buf;
	const gchar *short_name;
	gint recycle_status;
	guint index;
	gulong time_start;

	if((path != NULL) ? lstat(path, &stat_buf) : TRUE)
	{
	    *status = 1;
	    return(0);
	}

	/* Get object's name without any path prefix */
	short_name = strrchr(path, G_DIR_SEPARATOR);
	if(short_name != NULL)
	{
	    short_name++;
	    if(*short_name == '\0')
		short_name = path;
	}
	else
	{
	    short_name = path;
	}

	/* Is this object a directory? */
	if(S_ISDIR(stat_buf.st_mode))
	{
	    const char *name;
	    gchar *full_path;
	    const struct dirent *de;

	    /* Open directory and get listing, recycle each object
	     * in that directory
	     */
	    DIR *dir = opendir(path);
	    if(dir != NULL)
	    {
		if(verbose)
		    g_print(
"Recycling all entries of directory `%s'\n",
			short_name
		    );
	    }
	    else
	    {
		const gint error_code = errno;
		g_printerr(
"%s: Unable to open directory: %s\n",
		    short_name, g_strerror(error_code)
		);
		*status = 1;
		return(0);
	    }

	    /* Iterate through objects in this directory */
	    while(TRUE)
	    {
		/* Get next directory entry */
		de = (const struct dirent *)readdir(dir);
		name = (de != NULL) ? de->d_name : NULL;
		if(name == NULL)
		    break;

		/* Skip special notations */
		if(!g_strcasecmp(name, ".") ||
		   !g_strcasecmp(name, "..")
		)
		    continue;

		/* Generate full path to this child object */
		full_path = g_strdup_printf(
		    "%s/%s",
		    path, name
		);

		/* Recycle this child object (recurse if it is
		 * a directory)
		 */
		index = recycle_object_iterate(
		    ctx, full_path, interactive, verbose, status
		);

		g_free(full_path);
	    }

	    closedir(dir);

	    if(verbose)
		g_print(
"Recycling directory itself `%s'\n",
		    short_name
		);
	}

	/* Do confirmation */
	if(interactive ? !recycle_query_user_recycle_object(short_name) : FALSE)
	{
	    *status = 4;
	    return(0);
	}

	time_start = (gulong)time(NULL);

	/* Recycle this object */
	index = EDVRecycle(
	    ctx,			/* Context */
	    path,			/* Object */
	    TRUE,			/* Notify Endeavour */
	    verbose ? recycle_progress_cb : NULL,	/* Progress callback */
	    (gpointer)short_name	/* Data */
	);
	recycle_status = (index > 0) ? 0 : -1;

	/* Record history */
	EDVHistoryAppend(
	    ctx,
	    EDV_HISTORY_DISK_OBJECT_DELETE,
	    time_start, (gulong)time(NULL),
	    recycle_status,
	    path,
	    NULL,               /* No target */
	    EDVRecycleGetError(ctx)
	);
	EDVContextSync(ctx);

	/* Success? */
	if(recycle_status == 0)
	{
	    if(verbose)
		printf("\n");
	    EDVContextFlush(ctx);
	    return(index);
	}
	else
	{
	    /* Recycle failed, print error */
	    g_printerr(
		"%s%s: %s.\n",
		verbose ? "\n" : "",
		path,
		EDVRecycleGetError(ctx)
	    );
	    *status = 1;
	    return(0);
	}
}


int main(int argc, char *argv[])
{
	gboolean	interactive = FALSE,
			verbose = TRUE;
	gint status = 0;
	edv_context_struct *ctx = EDVContextNew();

	EDVContextLoadConfigurationFile(ctx, NULL);

#define DO_FREE_LOCALS	{	\
 EDVContextDelete(ctx);		\
 ctx = NULL;			\
}

	if(argc >= 2)
	{
	    gint i;
	    guint index;
	    const gchar *arg;

	    /* Parse arguments */
	    for(i = 1; i < argc; i++)
	    {
		arg = argv[i];
		if(arg == NULL)
		    continue;

		/* Help */
		if(!g_strcasecmp(arg, "--help") ||
		   !g_strcasecmp(arg, "-help") ||
		   !g_strcasecmp(arg, "--h") ||
		   !g_strcasecmp(arg, "-h")
		) 
		{ 
		    print_help(argv[0]);
		    DO_FREE_LOCALS
		    return(0);
		}
		/* Version */
		else if(!g_strcasecmp(arg, "--version") ||
			!g_strcasecmp(arg, "-version")
		)
		{ 
		    g_print(
"Endeavour Mark II Recycle Version " PROG_VERSION "\n"
PROG_COPYRIGHT
		    );
		    DO_FREE_LOCALS
		    return(0); 
		}
		/* Interactive */
		else if(!g_strcasecmp(arg, "--interactive") ||
			!g_strcasecmp(arg, "-i")
		)
		{
		    interactive = TRUE;
		}
		/* Quiet */
		else if(!g_strcasecmp(arg, "--quiet") ||
			!g_strcasecmp(arg, "-q")
		)
		{
		    verbose = FALSE;
		}
		/* Verbose */
		else if(!g_strcasecmp(arg, "--verbose") ||
			!g_strcasecmp(arg, "-v")
		)
		{
		    verbose = TRUE;
		}
		/* All else check if invalid option */
		else if((*arg == '-') || (*arg == '+'))
		{
		    g_printerr(
"%s: Unsupported option.\n",
			arg
		    );
		    DO_FREE_LOCALS
		    return(2);
		}
	    }

	    /* Iterate through arguments and recycle objects */
	    for(i = 1; i < argc; i++)
	    {
		arg = argv[i];
		if(arg == NULL)
		    continue;

		/* Skip options */
		if((*arg == '-') || (*arg == '+'))
		    continue;

		/* Recycle the object specified by arg, if it is a 
		 * directory then this call will recurse and recycle the
		 * child objects in the directory
		 */
		index = recycle_object_iterate(
		    ctx,		/* Endeavour Context */
		    arg,		/* Object */
		    interactive,	/* Interactive */
		    verbose,		/* Verbose */
		    &status		/* Status Return */
		);
	    }
	}
	else
	{
	    print_help(argv[0]);
	    DO_FREE_LOCALS
	    return(2);
	}

	EDVContextSync(ctx);

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}
