/*
 *	Purges recycled objects from the recycle bin.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <glib.h>
#include <endeavour2.h>
#include "../config.h"


static void print_help(const gchar *prog_name);

static gboolean purge_query_user_purge_object(
	const gchar *name, const guint index
);

static gint purge_progress_cb(
	gpointer data, const gulong i, const gulong m
);


#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 <index(ies)...> [options]\n\
       %s all [options]\n",
	    prog_name, prog_name
	);
	g_print("\n\
    The <index(ies)...> specifies the index(ies) of one or more\n\
    recycled object(s) to recover. You may need to enclose each\n\
    index in double quotes or prefix it with a backslash in order\n\
    for the shell to process it correctly (e.g. \"#12345\" or \\#12345).\n\
\n\
    You may also specify \"all\" to purge all the recycled objects.\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 recycle objects, use \"recycle\".\n\
\n"
	);
}


/*
 *	Queries the user to purge the recycled object specified by name.
 *
 *	Returns TRUE if the user responded with yes.
 */
static gboolean purge_query_user_purge_object(
	const gchar *name, const guint index
)
{
	gint c;

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


/*
 *	Purge 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 purge_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("\rPurging %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);
}








int main(int argc, char *argv[])
{
	gboolean	interactive = FALSE,
			verbose = TRUE,
			purge_all = FALSE;
	gint status = 0, purge_status;
	GList *glist, *indicies_list = NULL;
	guint index;
	gulong time_start;
	gchar *path_rtn;
	const gchar *name;
	edv_context_struct *ctx = EDVContextNew();

	EDVContextLoadConfigurationFile(ctx, NULL);

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


	if(argc >= 2)
	{
	    gint i;
	    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 Purge 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 get the list of recycled
	     * object indicies
	     */
	    for(i = 1; i < argc; i++)
	    {
		arg = argv[i];
		if(arg == NULL)
		    continue;

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

		/* Index? */
		if(*arg == '#')
		{
		    const guint index = (guint)ATOI(arg + 1);
		    indicies_list = g_list_append(
			indicies_list, (gpointer)index
		    );
		}
		/* All? */
		else if(!g_strcasecmp(arg, "all"))
		{
		    purge_all = TRUE;
		}
	    }
	}    

	/* No recycled object indicies specified? */
	if((indicies_list == NULL) && !purge_all)
	{
	    print_help(argv[0]);
	    DO_FREE_LOCALS
	    return(2);
	}

	if(purge_all)
	{
	    guint *indicies_list;
	    gchar **paths_list;
	    gint nobjs;

	    if(EDVRecycledObjectStatAll(
		ctx, &paths_list, &indicies_list, NULL, &nobjs
	    ))
	    {
		g_printerr(
"Unable to obtain the recycled objects list.\n"
		);
		status = 1;
	    }
	    else
	    {
		gint i;

		for(i = 0; i < nobjs; i++)
		{
		    path_rtn = paths_list[i];
		    if(path_rtn == NULL)
			continue;

		    index = indicies_list[i];

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

                    /* Do confirmation */
                    if(interactive ?
                        !purge_query_user_purge_object(name, index) :
                        FALSE
                    )
                    {
                        g_free(path_rtn);
                        continue;
                    }

                    /* Purge this object */
                    if(EDVPurge(
                        ctx, index,
                        TRUE,		/* Notify Endeavour */
                        verbose ? purge_progress_cb : NULL,
                        (gpointer)name
                    ) == 0)
                    {
                        if(verbose)
                            printf("\n");
                        EDVContextFlush(ctx);
                    }
                    else
                    {
                        /* Purge failed, print error */
                        g_printerr(
                            "%s%s (#%u): %s.\n",
                            verbose ? "\n" : "",
                            name, index,
                            EDVRecycleGetError(ctx)
                        );
                        status = 1;
                    }   

                    g_free(path_rtn);
 		}
		g_free(paths_list);
		g_free(indicies_list);
	    }
	}
	else
	{
	    /* Iterate through each recycled object index */
	    for(glist = indicies_list;
	        glist != NULL;
	        glist = g_list_next(glist)
	    )
	    {
		index = (guint)glist->data;

		if(EDVRecycledObjectStat(
		    ctx, index,
		    &path_rtn,
		    NULL
		) != 0)
		{
		    g_printerr(
"Unable to purge `#%u': No such recycled object.\n",
			index
		    );
		    status = 1;
		    continue;
		}
		if(path_rtn == NULL)
		{
		     g_printerr(
"Unable to purge `#%u': Unable to obtain the recycled object's statisics.\n",
			index
		    );
		    status = 1;
		    continue;
		}

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

		/* Do confirmation */
		if(interactive ?
		    !purge_query_user_purge_object(name, index) :
		    FALSE
		)
		{
		    g_free(path_rtn);
		    continue;
		}

		time_start = (gulong)time(NULL);

		/* Purge this object */
		purge_status = EDVPurge(
		    ctx, index,
		    TRUE,		/* Notify Endeavour */
		    verbose ? purge_progress_cb : NULL,
		    (gpointer)name
		);

	        /* Record history */
	        EDVHistoryAppend(
	            ctx,
	            EDV_HISTORY_RECYCLED_OBJECT_PURGE,
	            time_start, (gulong)time(NULL),
	            purge_status,
	            name,
	            NULL,		/* No target */
	            EDVRecycleGetError(ctx)
	        );
		EDVContextSync(ctx);

		/* Purged successfully? */
		if(purge_status == 0)
		{
		    if(verbose)
			printf("\n");
		    EDVContextFlush(ctx);
		}
		else
		{
		    /* Purge failed, print error */
		    g_printerr(
			"%s%s (#%u): %s.\n",
			verbose ? "\n" : "",
			name, index,
			EDVRecycleGetError(ctx)
		    );
		    status = 1;
		}

		g_free(path_rtn);
	    }
	}

	EDVContextSync(ctx); 

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}
