#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_purge.h"
#include "endeavour2.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"

#include "images/pdi_trash01_20x20.xpm"
#include "images/pdi_trash02_20x20.xpm"
#include "images/pdi_trash03_20x20.xpm"
#include "images/pdi_trash04_20x20.xpm"
#include "images/pdi_trash05_20x20.xpm"
#include "images/pdi_trash06_20x20.xpm"

#include "images/icon_trash_32x32.xpm"
#include "images/icon_trash_empty_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 *EDVRecBinPurgeGetError(void);

static void EDVRecBinPurgeMapProgressDialog(
	const gchar *label, const gfloat progress_value,
	GtkWidget *toplevel,
	const gboolean force_remap
);
static gint EDVRecBinPurgeProgressPercentCB(
	gpointer data, const gfloat percent
);
static gint EDVRecBinPurgeProgressCB(
	gpointer data, const gulong pos, const gulong total
);

gint EDVRecBinPurge(
	edv_core_struct *core,
	const guint index,		/* Index of the recycled object */
	GtkWidget *toplevel,
	const gfloat progress_value,	/* Can be -1.0 */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVRecBinPurgeAll(
	edv_core_struct *core,
	GtkWidget *toplevel,		/* Can be NULL */
	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 *EDVRecBinPurgeGetError(void)
{
	return(last_error);
}


/*
 *      Maps the progress dialog as needed in animation mode for purging.
 */
static void EDVRecBinPurgeMapProgressDialog(
	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_empty_32x32_xpm;
	start_icon_data[2] = (guint8 **)icon_trash_empty_32x32_xpm;
	icon_data[0] = (guint8 **)pdi_trash01_20x20_xpm;
	icon_data[1] = (guint8 **)pdi_trash02_20x20_xpm;
	icon_data[2] = (guint8 **)pdi_trash03_20x20_xpm;
	icon_data[3] = (guint8 **)pdi_trash04_20x20_xpm;
	icon_data[4] = (guint8 **)pdi_trash05_20x20_xpm;
	icon_data[5] = (guint8 **)pdi_trash06_20x20_xpm;
	end_icon_data[0] = (guint8 **)NULL;
	end_icon_data[1] = (guint8 **)NULL;
	end_icon_data[2] = (guint8 **)NULL;

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Purgando",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Purger",
	    label,
	    "Arrêt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Reinigung",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Prosciogliere",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Reinigen",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Purging",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Rensing",
	    label,
	    "Stans",
#else
	    "Purging",
	    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 EDVRecBinPurgeProgressPercentCB(
	gpointer data, const gfloat percent
)
{
	gint status = 0;
/*	edv_core_struct *core = EDV_CORE(data); */

	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		percent,
		EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		status = -1;
	}

	return(status);
}

static gint EDVRecBinPurgeProgressCB(
	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);
}


/*
 *	Purges the given recycled object specified by index from the
 *	recycled objects directory.
 *
 *	If and only if the progress_value is non negative, then the 
 *	progress dialog will be updated with an internally calculated
 *	value, otherwise the given progress_value is passed to the
 *	progress dialog.
 */
gint EDVRecBinPurge(
	edv_core_struct *core,
	const guint index,		/* Index of the recycled object */
	GtkWidget *toplevel,
	const gfloat progress_value,	/* Can be -1.0 */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	static gint status;
	const gchar *recycled_index_file;
	gulong time_start = (gulong)time(NULL);
	edv_recycled_object_struct *obj = NULL;
	const cfg_item_struct *cfg_list;

	/* Reset last error message */
	last_error = NULL;

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

#define DO_FREE_LOCALS	{	\
 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);

	/* Map the progress dialog? */
	if(show_progress)
	{
	    gchar *msg;

	    if((obj != NULL) ? (obj->name != NULL) : FALSE)
	    {
		gchar *p1 = EDVShortenPath(
		    obj->name, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		);
		msg = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Purgando:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Purger:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Reinigung:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Prosciogliere:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Reinigen:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Descartar:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Rensing:\n\
\n\
    %s\n"
#else
"Purging:\n\
\n\
    %s\n"
#endif
		    , p1
		);
		g_free(p1);
	    }
	    else
	    {
		msg = g_strdup_printf(
"Purging:\n\
\n\
    #%i\n",
		    index
		);
	    }

	    EDVRecBinPurgeMapProgressDialog(
		msg, MAX(progress_value, 0.0f), toplevel, FALSE
	    );

	    g_free(msg);

	    /* If the given progress value is not negative then explicitly
	     * call the progress callback with values simulating the given
	     * progress value
	     */
	    if(progress_value >= 0.0f)
	    {
		if(EDVRecBinPurgeProgressPercentCB(
		    core, progress_value
		))
		{
		    DO_FREE_LOCALS
		    status = -4;
		    return(status);
		}
	    }
	}


	/* Remove the recycled object from the recycle bin */
	status = EDVRecBinDiskObjectPurge(
	    recycled_index_file,
	    index,
	    ((progress_value < 0.0f) && show_progress) ?
		EDVRecBinPurgeProgressCB : NULL,
 	    core
	);
	if(status)
	{
	    last_error = EDVRecBinIndexGetError();
	}
	else
	{
	    /* Remove recycled object entry from the recycled objects
	     * index file
	     */
	    EDVRecBinIndexRemove(
		recycled_index_file, index
	    );
	}

	/* Record history */
	EDVAppendHistory(
	    core,
	    EDV_HISTORY_RECYCLED_OBJECT_PURGE,
	    time_start, (gulong)time(NULL),
	    status,
	    obj->name,
	    NULL,		/* No target */
	    last_error
	);

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}


/*
 *      Purges all recycled objects from the recycled objects directory
 *	including the recycled objects index file.
 *
 *      If and only if the progress_value is non negative, then the
 *      progress dialog will be updated with an internally calculated
 *      value, otherwise the given progress_value is passed to the
 *      progress dialog.
 */
gint EDVRecBinPurgeAll(
	edv_core_struct *core,
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	static gint status;
	const gchar *recycled_index_file;
	const gulong time_start = (gulong)time(NULL);
	const cfg_item_struct *cfg_list;

	last_error = NULL;

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

#define DO_FREE_LOCALS  \
{ \
}

	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);
	}


	if(show_progress)
	{
#if defined(PROG_LANGUAGE_SPANISH)
	    gchar *msg = STRDUP(
"Purgando todo contenido del Cajón de la Recirculación...\n"
	    );
#elif defined(PROG_LANGUAGE_FRENCH)
	    gchar *msg = STRDUP(
"Purger tous contenus du Recycle l'Huche...\n"
	    );
#elif defined(PROG_LANGUAGE_GERMAN)
	    gchar *msg = STRDUP(
"Reinigung alles Inhalts vom Verwertet Behälter wieder...\n"
	    );
#elif defined(PROG_LANGUAGE_ITALIAN)
	    gchar *msg = STRDUP(
"Prosciogliere tutto il contenuto dal Contenitore per\n\
la raccolta differenziata...\n"
	    );
#elif defined(PROG_LANGUAGE_DUTCH)
	    gchar *msg = STRDUP(
"Reinigen van alle inhoud van het Recyclt Bak...\n"
	    );
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    gchar *msg = STRDUP(
"O Purging todo conteúdo da Caixa de Recycle...\n"
	    );
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    gchar *msg = STRDUP(
"Rensing av all innhold fra Recycle Bin...\n"
	    );
#else
	    gchar *msg = STRDUP(
"Purging all contents from the Recycle Bin...\n"
	    );
#endif
	    EDVRecBinPurgeMapProgressDialog(
		msg, 0.0f, toplevel, FALSE
	    );

	    g_free(msg);
	}


	/* Purge all objects found in the recycled objects directory,
	 * including the recycled objects index file.
	 */
	status = EDVRecBinDiskObjectPurgeAll(
	    recycled_index_file, 
	    show_progress ? EDVRecBinPurgeProgressCB : NULL,
	    core
	);
	if(status)
	    last_error = EDVRecBinIndexGetError();


	/* Record history */
	EDVAppendHistory(
	    core,
	    EDV_HISTORY_RECYCLED_OBJECT_PURGE_ALL,
	    time_start, (gulong)time(NULL),
	    status,
	    NULL,		/* No source */
	    NULL,		/* No target */
	    last_error
	);

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}
