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

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

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

#include "cfg.h"
#include "url.h"
#include "edvtypes.h"
#include "edvobj.h"
#include "edvrecbin.h"
#include "edvconfirm.h"
#include "edvrecbinfio.h"
#include "edvrecbinfop.h"
#include "recbin.h"
#include "recbindnd.h"
#include "endeavour.h"
#include "edvop.h"
#include "edvcb.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"


static gint EDVRecBinDNDConfirm(
	edv_core_struct *core_ptr,
	gint gdk_action, guint info,
	GtkWidget *toplevel,
	GList *url_list
);
static void EDVRecBinDNDUpdateStatusBar(
	edv_recbin_struct *recbin,
	gint gdk_action, guint info,
	gint total_src_objects,
	gint total_objects_processed, gint status
);

static void EDVRecBinDragDataReceivedNexus(
	edv_core_struct *core_ptr,
	GdkDragContext *dc, guint info, GtkSelectionData *selection_data,
	edv_recbin_struct *recbin		/* Can be NULL */
);


void EDVRecBinContentsDNDSetIcon(
	edv_recbin_struct *recbin, gint row, gint column
);
gboolean EDVRecBinContentsDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y, guint t,
	gpointer data
);
gboolean EDVRecBinDeskIconDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y, guint t,
	gpointer data
);
void EDVRecBinContentsDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVRecBinContentsDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVRecBinDeskIconDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVRecBinContentsDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
);


#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)


/*
 *      All purpose initial operation confirmation procedure, prompts
 *      the user for response for confirmation of pending DND operation
 *      specified by the given inputs.
 *
 *      If the configuration will be checked first to se if confirmation
 *      is needed. If it is not needed then CDIALOG_RESPONSE_YES will
 *      be returned.
 *
 *      Returns a CDIALOG_RESPONSE_* code.
 */
static gint EDVRecBinDNDConfirm(
	edv_core_struct *core_ptr,
	gint gdk_action, guint info,
	GtkWidget *toplevel,
	GList *url_list
)
{
	const gint nurls = g_list_length(url_list);
	const gchar *src_path = NULL;

	if(nurls <= 0)
	    return(CDIALOG_RESPONSE_NOT_AVAILABLE);

	/* If there is exactly one URL then get src_path from it,
	 * otherwise leave src_path as NULL
	 */
	if(nurls == 1)
	{
	    const url_struct *url = URL(url_list->data);
	    if(url != NULL)
		src_path = url->path;
	}

	/* Do confirmation, handle by dnd target type */
	if((info == EDV_DND_TYPE_INFO_TEXT_PLAIN) ||
	   (info == EDV_DND_TYPE_INFO_TEXT_URI_LIST) ||
	   (info == EDV_DND_TYPE_INFO_STRING)
	)
	{
	    switch(gdk_action)
	    {
	      case GDK_ACTION_COPY:
	      case GDK_ACTION_MOVE:
	      case GDK_ACTION_LINK:
		return(EDVConfirmDelete(
		    core_ptr, toplevel,
		    src_path, nurls
		));
		break;
	    }
	}
	else if(info == EDV_DND_TYPE_INFO_RECYCLED_OBJECT)
	{
	    /* Recycled objects mat not be put into the Recycle Bin */
	}
	else if(info == EDV_DND_TYPE_INFO_ARCHIVE_OBJECT)
	{
	    /* Objects in archives may not be put into the Recycle Bin */
	}

	/* Unsupported DND target type or drag action, return response not
	 * available just to be on the safe side so that the operation
	 * does not continue
	 */
	return(CDIALOG_RESPONSE_NOT_AVAILABLE);
}

/*
 *	Updates the Recycle Bin Status Bar to indicate the operation
 */
static void EDVRecBinDNDUpdateStatusBar(
	edv_recbin_struct *recbin,
	gint gdk_action, guint info,
	gint total_src_objects,
	gint total_objects_processed, gint status
)
{
	gchar *buf = NULL;
	edv_status_bar_struct *sb = (recbin != NULL) ? 
	    recbin->status_bar : NULL;
	if(sb == NULL)
	    return;

	/* Begin formatting status message, handle by dnd target type */
	if((info == EDV_DND_TYPE_INFO_TEXT_PLAIN) ||
	   (info == EDV_DND_TYPE_INFO_TEXT_URI_LIST) ||
	   (info == EDV_DND_TYPE_INFO_STRING)
	)
	{
	    switch(gdk_action)
	    {
	      case GDK_ACTION_COPY:
	      case GDK_ACTION_MOVE:
	      case GDK_ACTION_LINK:
		switch(status)
		{
		  case 0: case -5:
		    buf = g_strdup_printf(
			"Deleted %i object%s",
			total_objects_processed,
			(total_objects_processed == 1) ? "" : "s"
		    );
		    break;
		  case -4:  /* Cancel */
		    buf = g_strdup_printf(
			"Delete operation canceled"
		    );
		    break;
		  default:  /* Error */
		    buf = g_strdup_printf(
			"Unable to delete object%s",
			(total_src_objects == 1) ? "" : "s"
		    );
		    break;
		}
		break;
	    }
	}
	else if(info == EDV_DND_TYPE_INFO_RECYCLED_OBJECT)
	{
	    /* Recycled objects may not be dragged back into the
	     * Recycle Bin
	     */
	}
	else if(info == EDV_DND_TYPE_INFO_ARCHIVE_OBJECT)
	{
	    /* Archive objects may not be recycled */
	}
	else
	{
	    /* Unsupported target type */
	}

	/* Set status bar message */
	EDVStatusBarMessage(sb, buf, FALSE);

	g_free(buf);
}


/*
 *	Recycle Bin drag data received nexus.
 *
 *	All inputs assumed valid.
 */
static void EDVRecBinDragDataReceivedNexus(
	edv_core_struct *core_ptr,
	GdkDragContext *dc, guint info, GtkSelectionData *selection_data,
	edv_recbin_struct *recbin		/* Can be NULL */
)
{
	gint initial_confirmation_result, nurls;
	GList *url_list;
	GtkWidget *toplevel = (recbin != NULL) ? recbin->toplevel : NULL;
	const url_struct *url;

#define DO_FREE_LOCALS	{				\
 /* Delete URL list */					\
 g_list_foreach(url_list, (GFunc)URLDelete, NULL);	\
 g_list_free(url_list);					\
 url_list = NULL;					\
}

	/* Decode the specified DDE buffer into a list of URLs */
	url_list = URLDecode(
	    (const guint8 *)selection_data->data,
	    selection_data->length
	);
	nurls = g_list_length(url_list);

	/* Make initial user confirmation querying to proceed with
	 * this operation
	 */
	initial_confirmation_result = EDVRecBinDNDConfirm(
	    core_ptr, (gint)dc->action, info,
	    toplevel,
	    url_list
	);
	/* User confirmed and dnd target type is a disk object? */
	if((initial_confirmation_result == CDIALOG_RESPONSE_YES) &&
	   ((info == EDV_DND_TYPE_INFO_TEXT_PLAIN) ||
	    (info == EDV_DND_TYPE_INFO_TEXT_URI_LIST) ||
	    (info == EDV_DND_TYPE_INFO_STRING)
	   )
	)
	{
	    gint status = -1;
	    gboolean yes_to_all = FALSE;
	    gint objects_deleted = 0;
	    guint *index;
	    gint total_indices;
	    const gchar *error_mesg;
	    GList *glist;

	    if(recbin != NULL)
	    {
		EDVRecBinSetBusy(recbin, TRUE);
		recbin->processing = TRUE;
	    }

	    /* Iterate through URL list */
	    for(glist = url_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		url = URL(glist->data);
		if(url == NULL)
		    continue;

		if(STRISEMPTY(url->path))
		    continue;

		/* Handle by drag action type */
		status = -1;
		index = NULL;
		total_indices = 0;
		error_mesg = NULL;
		switch((gint)dc->action)
		{
		  case GDK_ACTION_COPY:
		    error_mesg = "Objects may not be coppied to the Recycle Bin";
		    break;
		  case GDK_ACTION_MOVE:
		    status = EDVRecBinFOPDelete(
			core_ptr,
			url->path,
			&index, &total_indices,
			toplevel,
			TRUE, TRUE, &yes_to_all
		    );
		    break;
		  case GDK_ACTION_LINK:
		    error_mesg = "Objects may not be linked to the Recycle Bin";
		    break;
		  default:
		    error_mesg = "Unsupported drag operation";
		    break;
		}

		/* Get error message (if any) that might have occured
		 * in the above operation
		 */
		if(error_mesg == NULL)
		    error_mesg = EDVRecBinFOPGetError();
		if(!STRISEMPTY(error_mesg) && (status != -4))
		{
		    EDVPlaySoundError(core_ptr);
		    EDVMessageError(
			"Operation Error",
			error_mesg,
			NULL,
			toplevel
		    );
		}

		/* Was the object successfully deleted? */
		if((index != NULL) && !status)
		{
		    gint n;

		    if(recbin != NULL)
			recbin->processing = FALSE;

		    /* Report recycle object added for all non-zero
		     * indices
		     */
		    for(n = 0; n < total_indices; n++)
		    {
			if(index[n] != 0)
			    EDVRecycledObjectAddedEmit(core_ptr, index[n]);
		    }

		    if(recbin != NULL)
			recbin->processing = TRUE;

		    objects_deleted += total_indices;
		}

		/* Delete recycle objects index array */
		g_free(index);
		index = NULL;
		total_indices = 0;

		/* Skip handling of the rest of the objects on error
		 * (status != 0) and that the error was not a user
		 * response of no (status != -5)
		 */
		if(status && (status != -5))
		    break;
	    }

	    /* Update status bar message */
	    if(recbin != NULL)
		EDVRecBinDNDUpdateStatusBar(
		    recbin, (gint)dc->action, info,
		    nurls, objects_deleted, status
		);

	    /* Unmap progress dialog, it may have been mapped if any
	     * operations occured in the above loop
	     */
	    ProgressDialogBreakQuery(TRUE);
	    ProgressDialogSetTransientFor(NULL);

	    /* Play completed sound on success */
	    if(status == 0)
		EDVPlaySoundCompleted(core_ptr);

	    if(recbin != NULL)
	    {
		recbin->processing = FALSE;
		EDVRecBinSetBusy(recbin, FALSE);
	    }
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}


/*
 *	Sets the DND icon based on the Recycle Bin Contents List's
 *      selected cell specified by row and column
 */
void EDVRecBinContentsDNDSetIcon(
	edv_recbin_struct *recbin, gint row, gint column
)
{
	edv_recbin_object_struct *obj;
	GtkCList *clist = (GtkCList *)((recbin != NULL) ?
	    recbin->contents_clist : NULL
	);
	if(clist == NULL)
	    return;

	/* Get selected object */
	obj = EDV_RECBIN_OBJECT(
	    gtk_clist_get_row_data(clist, row)
	);
	if(obj != NULL)
	{
	    gint i;
	    gchar *text = NULL;
	    guint8 spacing = 0;
	    GdkPixmap *pixmap = NULL;
	    GdkBitmap *mask = NULL;

	    /* Iterate through each cell of this row, looking for a
	     * useable pixmap
	     */
	    for(i = 0; i < clist->columns; i++)
	    {
		switch(gtk_clist_get_cell_type(clist, row, i))
		{
		  case GTK_CELL_PIXMAP:
		    gtk_clist_get_pixmap(
			clist, row, i,
			&pixmap, &mask
		    );
		    break;
		  case GTK_CELL_PIXTEXT:
		    gtk_clist_get_pixtext(
			clist, row, i,
			&text, &spacing, &pixmap, &mask
		    );
		    break;
		  case GTK_CELL_TEXT:
		  case GTK_CELL_WIDGET:
		  case GTK_CELL_EMPTY:
		    break;
		}
		if(pixmap != NULL)
		    break;
	    }
	    if(pixmap != NULL)
	    {
		gint width = 15, height = 15;
		gdk_window_get_size(pixmap, &width, &height);
		GUIDNDSetDragIcon(
		    pixmap, mask,
		    width / 2, height / 2
		);
	    }
	}
}

/*
 *	Recycle Bin Contents List "drag_motion" signal callback.
 *
 *	This is used to constrain all drags (regardless of its type
 *	or source data type) to be a drag action of move.
 */
gboolean EDVRecBinContentsDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y, guint t,
	gpointer data
)
{
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if((dc == NULL) || (recbin == NULL))
	    return(FALSE);

	if(dc->actions & GDK_ACTION_MOVE)
	    gdk_drag_status(dc, GDK_ACTION_MOVE, t);
	else
	    gdk_drag_status(dc, 0, t);

	return(TRUE);
}

/*
 *	Recycle Bin Desktop Icon "drag_motion" signal callback.
 *
 *      This is used to constrain all drags (regardless of its type
 *      or source data type) to be a drag action of move.
 */
gboolean EDVRecBinDeskIconDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y, guint t,
	gpointer data
)
{
	edv_recbin_deskicon_struct *rbdi = EDV_RECBIN_DESKICON(data);
	if((dc == NULL) || (rbdi == NULL))
	    return(FALSE);

	if(dc->actions & GDK_ACTION_MOVE)
	    gdk_drag_status(dc, GDK_ACTION_MOVE, t);
	else
	    gdk_drag_status(dc, 0, t);

	return(TRUE);
}

/*
 *	Recycle Bin Contents List "drag_data_get" signal callback.
 */
void EDVRecBinContentsDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	gboolean data_sent = FALSE;
	gint row;
	GList *glist, *url_list = NULL;
	GtkWidget *w;
	GtkCList *clist;
	url_struct *url;
	edv_recbin_object_struct *obj;
	edv_core_struct *core_ptr;
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if((dc == NULL) || (recbin == NULL))
	    return;

	w = recbin->contents_clist;
	core_ptr = EDV_CORE(recbin->core_ptr);
	if((w == NULL) || (core_ptr == NULL))
	    return;

	clist = GTK_CLIST(w);

	EDVRecBinSyncData(recbin);

	/* Generate a list of URLs from the selected nodes */
	for(glist = clist->selection;                        
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    row = (gint)glist->data;
	    obj = EDV_RECBIN_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj == NULL)
		continue;

	    url = URLNew();
	    url->path = g_strdup_printf(
		"%i", obj->index
	    );
	    url_list = g_list_append(url_list, url);
	}

	/* Encode DDE buffer from the URL list */
	if(url_list != NULL) 
	{
	    gint buf_len;
	    guint8 *buf = URLEncode(url_list, &buf_len);
	    if(buf != NULL)
	    {
		/* Send out buffer */
		gtk_selection_data_set(
		    selection_data,
		    GDK_SELECTION_TYPE_STRING,
		    8,			/* Bits Per Character */
		    buf,		/* Data */
		    buf_len		/* Length */
		);
		data_sent = TRUE;
		g_free(buf);
	    }
	}

	/* Delete URL list */
	g_list_foreach(url_list, (GFunc)URLDelete, NULL);
	g_list_free(url_list);

	/* If failed to send out data then respond with error */
	if(!data_sent)
	{
	    const char *s = "Error";
	    gtk_selection_data_set(
		selection_data,
		GDK_SELECTION_TYPE_STRING,
		8,			/* Bits Per Character */
		s,			/* Data */
		STRLEN(s)		/* Length */
	    );
	    data_sent = TRUE;
	}
}

/*
 *	Recycle Bin Contents List "drag_data_received" signal callback.
 */
void EDVRecBinContentsDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	edv_core_struct *core_ptr;
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if((dc == NULL) || (recbin == NULL))
	    return;

	core_ptr = EDV_CORE(recbin->core_ptr);
	if(core_ptr == NULL)
	    return;

	/* Check if received data is not empty */
	if((selection_data != NULL) ? (selection_data->length <= 0) : TRUE)
	    return;

	/* Check and warn if write protect is enabled */
	if(EDVCheckWriteProtect(core_ptr, TRUE, recbin->toplevel))
	    return;

	EDVRecBinSyncData(recbin);

	/* Handle received drag data */
	EDVRecBinDragDataReceivedNexus(
	    core_ptr, dc, info, selection_data, recbin
	);
}

/*
 *      Recycle Bin Desktop Icon "drag_data_received" signal callback.
 */
void EDVRecBinDeskIconDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	edv_core_struct *core_ptr;
	edv_recbin_deskicon_struct *rbdi = EDV_RECBIN_DESKICON(data);
	if((dc == NULL) || (rbdi == NULL))
	    return;

	core_ptr = EDV_CORE(rbdi->core_ptr);
	if(core_ptr == NULL)
	    return;

	/* Check if received data is not empty */
	if((selection_data != NULL) ? (selection_data->length <= 0) : TRUE)
	    return;

	/* Check and warn if write protect is enabled */
	if(EDVCheckWriteProtect(core_ptr, TRUE, NULL))
	    return;

	/* Handle received drag data */
	EDVRecBinDragDataReceivedNexus(
	    core_ptr, dc, info, selection_data, NULL
	);
}


/*
 *	Recycle Bin Contents List "drag_data_delete" signal callback.
 */
void EDVRecBinContentsDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
)
{
	guint *index;
	gint i, row, total_indices;
	edv_core_struct *core_ptr;
	GList *glist;
	GtkWidget *w;
	GtkCList *clist;
	const gchar *recycled_index_file;
	edv_recbin_object_struct *obj;
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if((dc == NULL) || (recbin == NULL))
	    return;

	w = recbin->contents_clist;
	core_ptr = EDV_CORE(recbin->core_ptr);
	if((w == NULL) || (core_ptr == NULL))
	    return;

	clist = GTK_CLIST(w);

	/* Get path to recycled objects index file */
	recycled_index_file = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_FILE_RECYCLED_INDEX
	);
	if(STRISEMPTY(recycled_index_file))
	    return;

	/* Generate a list of recycled object indices based on the
	 * selected rows
	 */
	index = NULL;
	total_indices = 0;
	for(glist = clist->selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    row = (gint)glist->data;
	    obj = EDV_RECBIN_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj != NULL)
	    {
		i = total_indices;
		total_indices = i + 1;
		index = (guint *)g_realloc(
		    index, total_indices * sizeof(guint)
		);
		if(index == NULL)
		{
		    total_indices = 0;
		    break;
		}
		index[i] = obj->index;
	    }
	}

	/* Emit recycled object removed signal for each recycled
	 * object index
	 */
	for(i = 0; i < total_indices; i++)
	{
	    /* Get recycled object to check if it does not exist */
	    obj = EDVRecBinObjectStat(recycled_index_file, index[i]);
	    if(obj != NULL)
	    {
		/* Recycled object still exists, skip it */
		EDVRecBinObjectDelete(obj);
		continue;
	    }

	    /* Emit recycled object removed signal */
	    EDVRecycledObjectRemovedEmit(core_ptr, index[i]);
	}
	g_free(index);
}
