/*
 *	Recycles the objects specified on the command line, placing them
 *	into the Recycle Bin.
 *
 *	Use "rls" to see what is currently recycled and "recover" to
 *	recover a recycled object.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.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_usage(void);
static gboolean query_user_recycle_object(const gchar *name);
static gint recycle_progress_cb(
	gpointer data, gulong pos, gulong total
);
static guint recycle_object_iterate(
	edv_context_struct *ctx,
	const gchar *path,
	gboolean interactive,
	gboolean verbose,
	gint *status
);


/*
 *	Prints the program usage message.
 */
static void print_usage(void)
{
	g_print(
"Usage: recycle [options] [object(s)...]\n\
\n\
    Where [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       Ambiguous or invalid value.\n\
        3       Systems error.\n\
        4       User aborted.\n\
\n"
	);
}


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

	g_print(
	    "recycle `%s'? ",
	    name
	);
	c = fgetc(stdin);
	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, gulong pos, gulong total
)
{
	gint i, n, m;
	gchar *s;
	const gchar *path = (const gchar *)data;
	gfloat coeff = (total > 0) ? ((gfloat)pos / (gfloat)total) : 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,
	gboolean interactive,
	gboolean verbose,
	gint *status
)
{
	const gchar *short_name;
	guint index;
	struct stat stat_buf;
	if((path != NULL) ? lstat(path, &stat_buf) : 1)
	{
	    *status = 1;
	    return(0);
	}

	/* Get object's name without any path prefix */
	short_name = strrchr(path, '/');
	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
	    {
		g_printerr(
"%s: Unable to open directory: %s\n",
		    short_name, strerror(errno)
		);
		*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 ? !query_user_recycle_object(short_name) : FALSE)
	{
	    *status = 4;
	    return(0);
	}

	/* Recycle this object */
	index = EDVRecycle(
	    ctx,			/* Context */
	    path,			/* Object */
	    TRUE,			/* Notify Endeavour */
	    verbose ? recycle_progress_cb : NULL,	/* Progress callback */
	    (gpointer)short_name	/* Data */
	);
	if(index > 0)
	{
	    if(verbose)
		printf("\n");
	    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);

	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;

		/* Interactive */
		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;
                }
		/* Help */
                else if(!g_strcasecmp(arg, "--help") ||
                        !g_strcasecmp(arg, "-h")
                ) 
                { 
                    print_usage();
		    return(0);
                }
		/* Version */
                else if(!g_strcasecmp(arg, "--version"))
                { 
                    g_print(
"Endeavour Recycle Version " PROG_VERSION "\n"
PROG_COPYRIGHT
		    );
                    return(0); 
                }
		/* All else check if invalid option */
		else if(*arg == '-')
		{
		    g_printerr(
"%s: Invalid option.\n",
			arg
		    );
		    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 == '-')
		    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_usage();
	}

	EDVContextSync(ctx);
	EDVContextDelete(ctx);

	return(status);
}
