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

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

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

#include "edv_types.h"
#include "cfg.h"
#include "edv_recycled_obj.h"
#include "edv_recbin_index.h"
#include "edv_recbin_stat.h"
#include "edv_recbin_recover.h"
#include "endeavour2.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"

#include "images/pdi_file01_20x20.xpm"
#include "images/pdi_file02_20x20.xpm"
#include "images/pdi_file03_20x20.xpm"
#include "images/pdi_file04_20x20.xpm"
#include "images/pdi_file05_20x20.xpm"
#include "images/pdi_file06_20x20.xpm"
#include "images/pdi_folder_32x32.xpm"
#include "images/pdi_folderfile_32x32.xpm"

#include "images/icon_trash_32x32.xpm"


/*
 *	Return values legend:
 *
 *	0	Success.
 *	-1	General error.
 *	-2	Invalid value.
 *	-3	Systems error; out of memory or out of disk space.
 *	-4	User responded with "Cancel".
 *	-5	User responded with "No" or response was not available.
 *	-6	Call would cause reentry.
 */


const gchar *EDVRecBinRecoverGetError(void);

static void EDVRecBinRecoverMapProgressDialog(
	const gchar *label, const gfloat progress_value,
	GtkWidget *toplevel,
	const gboolean force_remap
);

static gint EDVRecBinRecoverProgressCB(
	gpointer data, const gulong pos, const gulong total
);

gint EDVRecBinRecover(
	edv_core_struct *core,
	const guint index,
	const gchar *alternate_recovery_path,
 	gchar **new_obj_rtn,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);


static const 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 a statically allocated string describing the last
 *	error message (or NULL if there was no error) since the last
 *	call to a EDVRecBinFOP*() function.
 */
const gchar *EDVRecBinRecoverGetError(void)
{
	return(last_error);
}


/*
 *      Maps the progress dialog as needed in animation mode for recover.
 */
static void EDVRecBinRecoverMapProgressDialog(
	const gchar *label, const gfloat progress_value,
	GtkWidget *toplevel,
	const gboolean force_remap
)
{
	guint8	**start_icon_data[3],
		**icon_data[6],
		**end_icon_data[3];

	/* Already mapped? */
	if(ProgressDialogIsQuery())
	{
	    /* Check if the progress dialog needs to be unmapped and
	     * remapped again
	     */
	    if(force_remap)
	    {
		ProgressDialogBreakQuery(FALSE);
	    }
	    else
	    {
		/* Already mapped and does not need unmapping, so just
		 * update the progress message
		 */
		ProgressDialogUpdate(
		    NULL, label, NULL, NULL,
		    progress_value, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		return;
	    }
	}

	ProgressDialogSetTransientFor(toplevel);

	start_icon_data[0] = (guint8 **)icon_trash_32x32_xpm;
	start_icon_data[1] = (guint8 **)icon_trash_32x32_xpm;
	start_icon_data[2] = (guint8 **)icon_trash_32x32_xpm;
	icon_data[0] = (guint8 **)pdi_file01_20x20_xpm;
	icon_data[1] = (guint8 **)pdi_file02_20x20_xpm;
	icon_data[2] = (guint8 **)pdi_file03_20x20_xpm;
	icon_data[3] = (guint8 **)pdi_file04_20x20_xpm;
	icon_data[4] = (guint8 **)pdi_file05_20x20_xpm;
	icon_data[5] = (guint8 **)pdi_file06_20x20_xpm;
	end_icon_data[0] = (guint8 **)pdi_folder_32x32_xpm;
	end_icon_data[1] = (guint8 **)pdi_folder_32x32_xpm;
	end_icon_data[2] = (guint8 **)pdi_folderfile_32x32_xpm;

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Convaleciente",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Retrouver",
	    label,
	    "Arręt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Wiedererlangen",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Ricuperare",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Terugkrijgen",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Recuperar",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Utvinning",
	    label,
	    "Stans",
#else
	    "Recovering",
	    label,
	    "Stop",
#endif
	    start_icon_data, 3,
	    icon_data, 6,
	    end_icon_data, 3,
	    EDV_DEF_PROGRESS_DLG_ANIM_INT,
	    EDV_DEF_PROGRESS_DLG_ANIM_INC
	);
	ProgressDialogUpdate(
	    NULL, NULL, NULL, NULL,
	    progress_value, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	);

	/* Flush output so dialog gets mapped and we catch the beginning
	 * of the operation (some WM need this)
	 */
	gdk_flush();
}


/*
 *	Recycle recover or delete progress callback.
 */
static gint EDVRecBinRecoverProgressCB(
	gpointer data, const gulong pos, const gulong total
)
{
	gint status = 0;
/*	edv_core_struct *core = EDV_CORE(data); */

	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		((total > 0) && (pos >= 0)) ?
		    ((gfloat)pos / (gfloat)total) : -1.0f,
		EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		status = -1;
	}

	return(status);
}


/*
 *	Recovers the recycled object.
 *
 *	The index specifies the recycled object to recover.
 *
 *	The alternate_recovery_path specifies the full path to the
 *	directory that is to be used as the alternate recovery location
 *	for the recycled object. If alternate_recovery_path is NULL
 *	then the recycled object's original location will be used
 *	as the recovery location.
 *
 *	The new_obj_rtn will contain the full path to the recovered
 *	object if the recovery was successful. The calling function
 *	must delete the returned string.
 */
gint EDVRecBinRecover(
	edv_core_struct *core,
	const guint index,
	const gchar *alternate_recovery_path,
 	gchar **new_obj_rtn,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	const gulong time_start = (gulong)time(NULL);
	struct stat lstat_buf;
	gint status;
	const gchar *recycled_index_file;
	gchar *recovered_path = NULL;
	const cfg_item_struct *cfg_list;
	edv_recycled_object_struct *obj = NULL;

	last_error = NULL;

	if((core == NULL) || (index == 0))
	{
	    last_error = "Invalid value";
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(recovered_path);	\
 EDVRecycledObjectDelete(obj);	\
}

	cfg_list = core->cfg_list;

	/* Get path to recycled objects index file */
	recycled_index_file = EDV_GET_S(
	    EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX
	);
	if(recycled_index_file == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get the recycled objects index file";
	    return(-1);
	}

	/* Get the statistics of recycled object */
	obj = EDVRecBinObjectStat(recycled_index_file, index);
	if(obj == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to get the recycled object's statistics";
	    return(-1);
	}


	/* Generate the full path of the recovered object
	 *
	 * Alternate recovery location specified?
	 */
	if(alternate_recovery_path != NULL)
	{
	    recovered_path = STRDUP(PrefixPaths(
		(const char *)alternate_recovery_path, (const char *)obj->name
	    ));
	}
	else
	{
	    /* Use the original location */
	    if((obj->original_path == NULL) || (obj->name == NULL))
	    {
		DO_FREE_LOCALS
		last_error =
"Unable to get original path of recycled object";
		return(-1);
	    }

	    recovered_path = STRDUP(PrefixPaths(
		(const char *)obj->original_path, (const char *)obj->name
	    ));
	}
	if(recovered_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error =
"Unable to generate the path to the recovered object";
	    return(-1);
	}


	/* The target object must not exist */
	if(!lstat((const char *)recovered_path, &lstat_buf))
	{
	    DO_FREE_LOCALS
	    last_error =
"An object with the same name already exists at the specified recovery location";
	    return(-1);
	}


	if(show_progress)
	{
	    gchar *p1 = EDVShortenPath(
		obj->name, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *p2 = EDVShortenPath(
		recovered_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *msg = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Convaleciente:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Retrouver:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Wiedererlangen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Ricuperare:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Terugkrijgen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Recuperar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Utvinning:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Recovering:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
		, p1, p2
	    );

	    EDVRecBinRecoverMapProgressDialog(
		msg, 0.0f, toplevel, FALSE
	    );

	    g_free(msg);
	    g_free(p1);
	    g_free(p2);
	}


	/* Recover */
	status = EDVRecBinDiskObjectRecover(
	    recycled_index_file,
	    index,
	    recovered_path,
	    show_progress ? EDVRecBinRecoverProgressCB : NULL,
	    core
	);
	if(status == -4)
	{
	    /* User aborted recovery */
	}
	else if(status)
	{
	    /* Recovery failed */
	    last_error = EDVRecBinIndexGetError();
	}
	else
	{
	    /* Remove recycled object entry from the recycled objects
	     * index file.
	     */
	    EDVRecBinIndexRemove(
		recycled_index_file, index
	    );

	    /* Set the path of the recovered object */
	    if(new_obj_rtn != NULL)
	    {
		g_free(*new_obj_rtn);
		*new_obj_rtn = STRDUP(recovered_path);
	    }
	}

	/* Record history */
	EDVAppendHistory(
	    core,
	    EDV_HISTORY_RECYCLED_OBJECT_RECOVER,
	    time_start, (gulong)time(NULL),
	    status,
	    obj->name,		/* Source */
	    recovered_path,	/* Target */
	    last_error		/* Use last_error as comment if not NULL */
	);

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}
