/*
	Demo for Endeavour recycle procedure (no GTK or GUI code 
	involved).
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glib.h>
#include "../../include/disk.h"
#include "../edvrecbin.h"
#include "../edvrecbinfio.h"
#include "../config.h"


static gint RecycleProgressCB(
	gpointer client_data, gulong pos, gulong total
);

void EDVGetDeviceNumbers(dev_t rdev, gint *major, gint *minor);
dev_t EDVFormatDeviceNumbers(gint major, gint minor);

static gint RecycleDoList(const gchar *recycled_index_file);
static guint RecycleDoDelete(
	const gchar *recycled_index_file, const gchar *path
);
static gint RecycleDoRecover(
	const gchar *recycled_index_file, guint index
);
static gint RecycleDoPurge(
	const gchar *recycled_index_file, guint index
);




/*
 *	Progress callback passed to recycle bin file io functions, reports
 *	a percent progress of the file operation.
 */
static gint RecycleProgressCB(
	gpointer client_data, gulong pos, gulong total
)
{
	if(total > 0)
	{
	    printf("\r%.0f%% ", (gfloat)pos / (gfloat)total * 100.0);
	    fflush(stdout);
	}
	return(0);
}

/*
 *	From edvutils.c
 *
 *      Parses and returns the major and minor numbers for the device
 *      given by rdev which should be obtained from a prior call to
 *      *stat() in structure struct stat stat_buf.st_rdev.
 */
void EDVGetDeviceNumbers(dev_t rdev, gint *major, gint *minor)
{
	if(major != NULL)
	    *major = ((rdev >> 8) & 0xff);
	if(minor != NULL)
	    *minor = ((rdev >> 0) & 0xff);
}

/*
 *      From edvutils.c
 *
 *      Returns a formatted rdev value formatted from the given device
 *      major and minor numbers.
 */
dev_t EDVFormatDeviceNumbers(gint major, gint minor)
{
	return(((dev_t)major << 8) | ((dev_t)minor << 0));
}


/*
 *	Lists all contents in the recycle bin.
 */
static gint RecycleDoList(const gchar *recycled_index_file)
{
	edv_recbin_object_struct *obj;
	edv_recbin_index_struct *rbi_ptr;


	rbi_ptr = EDVRecBinIndexOpen(recycled_index_file);
	if(rbi_ptr == NULL)
	{
	    fprintf(
		stderr,
		"%s: Unable to open recycled index file\n",
		recycled_index_file
	    );
	    return(-1);
	}

	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    obj = rbi_ptr->obj;
	    if(obj != NULL)
	    {
		const gchar *cstrptr = PrefixPaths(
		    obj->original_path, obj->name
		);
		printf("%i\t%s\n", rbi_ptr->index, cstrptr);
	    }
	}

	EDVRecBinIndexClose(rbi_ptr);
	rbi_ptr = NULL;

	return(0);
}

/*
 *	Deletes an actual disk object and places it into the recycle
 *	bin.
 */
static guint RecycleDoDelete(
	const gchar *recycled_index_file, const gchar *path
)
{
	const gchar *cstrptr;
	gint status;
	guint index;
	edv_recbin_object_struct *obj;
	mode_t m;
	struct stat lstat_buf;


	if(path == NULL)
	    return(0);

	if(lstat(path, &lstat_buf))
	    return(0);

	m = lstat_buf.st_mode;


	/* Create a new tempory recycled object structure */
	obj = EDVRecBinObjectNew();

	/* Begin setting values from actual object's lstat_buf structure
	 * to the newly allocated recycled object structure.
	 */

	/* Name */
	cstrptr = strrchr(path, DIR_DELIMINATOR);
	if(cstrptr == NULL)
	    cstrptr = path;
	else
	    cstrptr++;
	g_free(obj->name);
	obj->name = g_strdup(cstrptr);

	/* Location of object (path without object's name) */
	cstrptr = GetParentDir(path);
	if(cstrptr == NULL)
	    cstrptr = "/";
	g_free(obj->original_path);
	obj->original_path = g_strdup(cstrptr);

	if(S_ISREG(m))
	    obj->type = EDV_OBJECT_TYPE_FILE;
	else if(S_ISDIR(m))
	    obj->type = EDV_OBJECT_TYPE_DIRECTORY;
	else if(S_ISLNK(m))
	    obj->type = EDV_OBJECT_TYPE_LINK;
	else if(S_ISBLK(m))
	    obj->type = EDV_OBJECT_TYPE_DEVICE_BLOCK;
	else if(S_ISCHR(m))
	    obj->type = EDV_OBJECT_TYPE_DEVICE_CHARACTER;
	else if(S_ISFIFO(m))
	    obj->type = EDV_OBJECT_TYPE_FIFO;
	else if(S_ISSOCK(m))
	    obj->type = EDV_OBJECT_TYPE_SOCKET;

	obj->permissions = 0;
	if(m & S_IXUSR)
	    obj->permissions |= EDV_PERMISSION_UEXECUTE;
	if(m & S_IRUSR)
	    obj->permissions |= EDV_PERMISSION_UREAD;
	if(m & S_IWUSR)
	    obj->permissions |= EDV_PERMISSION_UWRITE;
	if(m & S_IXGRP)
	    obj->permissions |= EDV_PERMISSION_GEXECUTE;
	if(m & S_IRGRP)
	    obj->permissions |= EDV_PERMISSION_GREAD;
	if(m & S_IWGRP)
	    obj->permissions |= EDV_PERMISSION_GWRITE;
	if(m & S_IXOTH)
	    obj->permissions |= EDV_PERMISSION_AEXECUTE;
	if(m & S_IROTH)
	    obj->permissions |= EDV_PERMISSION_AREAD;
	if(m & S_IWOTH)
	    obj->permissions |= EDV_PERMISSION_AWRITE;
	if(m & S_ISUID)
	    obj->permissions |= EDV_PERMISSION_SETUID;
	if(m & S_ISGID)
	    obj->permissions |= EDV_PERMISSION_SETGID;
	if(m & S_ISVTX)
	    obj->permissions |= EDV_PERMISSION_STICKY;

	obj->access_time = lstat_buf.st_atime;
	obj->modify_time = lstat_buf.st_mtime;
	obj->change_time = lstat_buf.st_ctime;

	obj->owner_id = lstat_buf.st_uid;
	obj->group_id = lstat_buf.st_gid;

	obj->size = lstat_buf.st_size;



	/* Record this object to recycled objects index file */
	index = EDVRecBinIndexAdd(
	     recycled_index_file, obj
	);
	if(index > 0)
	{
	    /* Remove actual disk object and place it into the recycled
	     * objects directory.
	     */
	    printf(
		"Recycling %s\n",
		path
	    );
	    status = EDVRecBinDiskObjectDelete(
		recycled_index_file,
		index,
		path,
/*		RecycleProgressCB, NULL */
		NULL, NULL
	    );
/*	    printf("\n"); */
	    if(status)
		fprintf(
		    stderr,
		    "%s\n",
		    EDVRecBinFIOGetError()
		);
	}

	/* Delete tempory recycled object structure, it is no longer
	 * needed.
	 */
	EDVRecBinObjectDelete(obj);
	obj = NULL;

	return(index);
}

/*
 *	Recovers an object from the recycled objects directory.
 */
static gint RecycleDoRecover(
	const gchar *recycled_index_file, guint index
)
{
	gint status;


	status = EDVRecBinDiskObjectRecover(
	    recycled_index_file,
	    index,
	    NULL,
	    RecycleProgressCB, NULL
	);
	printf("\n");
	if(status)
	{
	    fprintf(
		stderr,
		"%s\n",
		EDVRecBinFIOGetError()
	    );
	}
	else
	{
	    /* Remove recycled object entry from the recycled objects 
	     * index file.
	     */
	    EDVRecBinIndexRemove(
		recycled_index_file, index
	    );
	}

	return(status);
}

/*
 *	Permanently removes an object from the recycled objects directory.
 */
static gint RecycleDoPurge(
	const gchar *recycled_index_file, guint index
)
{
	gint status;


	/* Remove recycled object from the recycled objects directory */
	status = EDVRecBinDiskObjectPurge(
	    recycled_index_file,
	    index,
	    RecycleProgressCB, NULL
	);
	printf("\n");
	if(status)
	{
	    fprintf(
		stderr,
		"%s\n",
		EDVRecBinFIOGetError()
	    );
	}
	else
	{
	    /* Remove recycled object entry from the recycled objects
	     * index file.
	     */
	    EDVRecBinIndexRemove(
		recycled_index_file, index
	    );
	}

	return(status);
}


int main(int argc, char *argv[])
{
	const gchar *cstrptr;
	gchar recycled_index_file[PATH_MAX + NAME_MAX];


	/* Get value of environment variable HOME and prefix it to
	 * the standard path to the local recycled objects index file
	 * to obtain it's path as recycled_index_file.
	 */
	cstrptr = getenv("HOME");
	cstrptr = PrefixPaths(
	    (cstrptr != NULL) ? cstrptr : "/",
	    ".endeavour2/recycled/recycled.ini"
	);
	strncpy(
	    recycled_index_file,
	    (cstrptr != NULL) ? cstrptr : "/",
	    PATH_MAX + NAME_MAX
	);
	recycled_index_file[PATH_MAX + NAME_MAX - 1] = '\0';


	/* If no arguments then print list of recycled objects */
	if(argc < 2)
	{
	    RecycleDoList(recycled_index_file);
	}
	else
	{
	    /* More than one argument given, so handle by option */
	    const gchar *arg_ptr = argv[1];

	    /* Help? */
	    if(!strcmp(arg_ptr, "--help") ||
	       !strcmp(arg_ptr, "-help") ||
	       !strcmp(arg_ptr, "--h") ||
	       !strcmp(arg_ptr, "-h") ||
	       !strcmp(arg_ptr, "-?")
	    )
	    {
		printf(
"\
Usage: recycle <file>\n\
       recycle -p <index_to_purge>\n\
       recycle -r <index_to_recover>\n\
       recycle\n\
\n\
   Specifying no arguments will print list of all recycled objects.\n\
\n"
		);

		return(0);
	    }
	    /* Version? */
	    else if(!strcmp(arg_ptr, "--version") ||
		    !strcmp(arg_ptr, "-version")
	    )
	    {
		printf(
PROG_NAME " Recycle " PROG_VERSION "\n" PROG_COPYRIGHT
		);
	    }
	    /* Recover? */
	    else if(!strcmp(arg_ptr, "-r"))
	    {
		if(argc > 2)
		{
		    RecycleDoRecover(
			recycled_index_file, atoi(argv[2])
		    );
		}
	    }
	    /* Purge? */
	    else if(!strcmp(arg_ptr, "-p"))
	    {
		if(argc > 2)
		{
		    RecycleDoPurge(
			recycled_index_file, atoi(argv[2])
		    );
		}
	    }
	    /* All else just recycle (delete) object */
	    else
	    {
		gint i;
		gchar path[PATH_MAX + NAME_MAX];
		gchar cwd[PATH_MAX];

		/* Get current directory */
		if(getcwd(cwd, PATH_MAX) != NULL)
		    cwd[PATH_MAX - 1] = '\0';
		else
		    *cwd = '\0';

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

		    /* Get full path of the specified argument as the
		     * object to recycle.
		     */
		    if(ISPATHABSOLUTE(arg_ptr))
		    {
		        strncpy(
			    path,
			    arg_ptr,
			    PATH_MAX + NAME_MAX
		        );
		    }
		    else
		    {
			cstrptr = PrefixPaths(cwd, arg_ptr);
			strncpy(
			    path,
			    (cstrptr != NULL) ? cstrptr : arg_ptr,
			    PATH_MAX + NAME_MAX
			);
		    }
		    path[PATH_MAX + NAME_MAX - 1] = '\0';

		    RecycleDoDelete(
			recycled_index_file, path
		    );
		}
	    }
	}


	return(0);
}
