#include <stdlib.h>
#include <string.h>
#include <errno.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 "edv_types.h"
#include "edv_obj.h"
#include "edv_recycled_obj.h"
#include "edv_confirm.h"
#include "edv_recbin_stat.h"
#include "edv_recbin_delete.h"
#include "recbin.h"
#include "recbin_dnd.h"
#include "endeavour2.h"
#include "edv_op.h"
#include "edv_cb.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"


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

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


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,
	const gint gdk_action, const 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, 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,
	const gint gdk_action, const guint info,
	const gint total_src_objects,
	const gint total_objects_processed, const 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:
		if(status == -4)
		    buf = STRDUP(
			"Delete operation canceled"
		    );
		else if(total_objects_processed > 0)
		    buf = g_strdup_printf(
			"Deleted %i %s",
			total_objects_processed,
			(total_objects_processed == 1) ? "object" : "objects"
		    );
		else
		    buf = g_strdup_printf(
			"Unable to delete %s",
			(total_src_objects == 1) ? "object" : "objects"
		    );
		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 the status bar message */
	EDVStatusBarMessage(sb, buf, FALSE);

	g_free(buf);
}


/*
 *	Recycle Bin drag data received nexus.
 *
 *	The recbin specifies the Recycle Bin that the drag data is
 *	received relative to, it may be NULL if not applicable.
 *
 *	All inputs assumed valid.
 */
static void EDVRecBinDragDataReceivedNexus(
	edv_recbin_struct *recbin,		/* Can be NULL */
	edv_core_struct *core,
	GdkDragContext *dc, const guint info,
	GtkSelectionData *selection_data
)
{
	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, (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)
	   )
	)
	{
	    struct stat lstat_buf;
	    gint status = -1;
	    gboolean yes_to_all = FALSE;
	    gint objects_deleted = 0;
	    const gchar *path, *error_msg;
	    GList *glist, *index_list;

	    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;

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

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

		/* Check for and report any errors */
		if(error_msg == NULL)
		    error_msg = EDVRecBinDeleteObjectGetError();
		if(!STRISEMPTY(error_msg))
		{
		    EDVPlaySoundError(core);
		    EDVMessageObjectOPError(
			"Delete Object Error",
			error_msg,
			path,
			toplevel
		    );
		}

		/* Check if the object still exists */
		if(lstat(path, &lstat_buf))
		{
#ifdef ENOENT
		    /* Report the object being removed */
		    const gint error_code = (gint)errno;
		    if(error_code == ENOENT)
			EDVObjectRemovedEmit(core, path);
#endif
		}

		/* Any objects deleted? */
		if(index_list != NULL)
		{
		    guint index;
		    GList *glist;

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

		    /* Report recycled objects added */
		    for(glist = index_list; glist != NULL; glist = g_list_next(glist))
		    {
			index = (guint)glist->data;
			if(index != 0)
			    EDVRecycledObjectAddedEmit(core, index);

			objects_deleted++;
		    }

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

		/* Delete the recycle objects index list */
		g_list_free(index_list);

		/* User aborted? */
		if(status == -4)
		    break;
	    }

	    /* Update the 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);

	    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_recycled_object_struct *obj;
	GtkCList *clist = (GtkCList *)((recbin != NULL) ?
	    recbin->contents_clist : NULL
	);
	if(clist == NULL)
	    return;

	/* Get selected object */
	obj = EDV_RECYCLED_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_desktop_icon_struct *rbdi = EDV_RECBIN_DESKTOP_ICON(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_recycled_object_struct *obj;
	edv_core_struct *core;
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if((dc == NULL) || (recbin == NULL))
	    return;

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

	clist = GTK_CLIST(w);

	EDVRecBinSyncData(recbin);

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

	    url = URLNew();

	    /* For recycled objects, the path must be set as the
	     * recycled object's index value as a string
	     */
	    url->path = g_strdup_printf(
		"%i", obj->index
	    );

	    /* For recycled objects, the path argument must be set
	     * as the recycled object's original name
	     */
	    url->path_arg = STRDUP(obj->name);

	    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 the 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;
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if((dc == NULL) || (recbin == NULL))
	    return;

	core = recbin->core;
	if(core == 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, TRUE, recbin->toplevel))
	    return;

	EDVRecBinSyncData(recbin);

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

/*
 *      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;
	edv_recbin_desktop_icon_struct *rbdi = EDV_RECBIN_DESKTOP_ICON(data);
	if((dc == NULL) || (rbdi == NULL))
	    return;

	core = rbdi->core;
	if(core == 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, TRUE, NULL))
	    return;

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


/*
 *	Recycle Bin Contents List "drag_data_delete" signal callback.
 */
void EDVRecBinContentsDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
)
{

}
