

#include <stdlib.h>

#include <X11/Xatom.h>

#include "WINGsP.h"

#define MAX_PROPERTY_SIZE 8*1024


typedef struct SelectionHandler {
    WMWidget *widget;
    Atom selection;
    Time timestamp;
    WMConvertSelectionProc *convProc;
    WMLoseSelectionProc *loseProc;
    WMSelectionDoneProc *doneProc;

    struct {
	unsigned delete_pending:1;
	unsigned done_pending:1;
    } flags;

    struct SelectionHandler *next;
} SelectionHandler;


static SelectionHandler *selHandlers = NULL;


void
WMDeleteSelectionHandler(WMWidget *widget, Atom selection)
{
    SelectionHandler *handler, *tmp;
    Display *dpy = WMWidgetScreen(widget)->display;
    Window win = WMWidgetXID(widget);
    Time timestamp;

    if (!selHandlers)
        return;

    tmp = selHandlers;

    if (tmp->widget == widget) {

	if (tmp->flags.done_pending) {
	    tmp->flags.delete_pending = 1;
	    return;
	}
        selHandlers = tmp->next;
	timestamp = tmp->timestamp;
        wfree(tmp);
    } else {
        while (tmp->next) {
            if (tmp->next->widget == widget) {

		if (tmp->next->flags.done_pending) {
		    tmp->next->flags.delete_pending = 1;
		    return;
		}
                handler = tmp->next;
                tmp->next = handler->next;
		timestamp = handler->timestamp;
                wfree(handler);
                break;
            }
            tmp = tmp->next;
        }
    }

    XGrabServer(dpy);
    if (XGetSelectionOwner(dpy, selection) == win) {
	XSetSelectionOwner(dpy, selection, None, timestamp);
    }
    XUngrabServer(dpy);
}


static Bool gotError = 0;
/*
static int
errorHandler(XErrorEvent *error)
{
    return 0;
}
*/

static Bool
writeSelection(Display *dpy, Window requestor, Atom property, Atom type,
	       void *value, long length, int format)
{
/*
    printf("write to %x: %s\n", requestor, XGetAtomName(dpy, property));
*/
    gotError = False;

#ifndef __sgi
    if (!XChangeProperty(dpy, requestor, property, type, format,
                         PropModeReplace, value, length))
        return False;
#else
    /* in sgi seems this seems to return void */
    XChangeProperty(dpy, requestor, property, type, format,
		    PropModeReplace, value, length);
#endif

    XFlush(dpy);

    return !gotError;
}


static void
notifySelection(XEvent *event, Atom prop)
{
    XEvent ev;
/*
    printf("envent to %x\n", event->xselectionrequest.requestor);
*/
    ev.xselection.type = SelectionNotify;
    ev.xselection.serial = 0;
    ev.xselection.send_event = True;
    ev.xselection.display = event->xselectionrequest.display;
    ev.xselection.requestor = event->xselectionrequest.requestor;
    ev.xselection.target = event->xselectionrequest.target;
    ev.xselection.selection = event->xselectionrequest.selection;
    ev.xselection.property = prop;
    ev.xselection.time = event->xselectionrequest.time;
    
    XSendEvent(event->xany.display, event->xselectionrequest.requestor, 
	       False, 0, &ev);
    XFlush(event->xany.display);
}


void
W_HandleSelectionEvent(XEvent *event)
{
    SelectionHandler *handler;

    handler = selHandlers;

    while (handler) {
	if (WMWidgetXID(handler->widget)==event->xany.window
/*	    && handler->selection == event->selection*/) {

	    switch (event->type) {
	     case SelectionClear:
		if (handler->loseProc)
		    (*handler->loseProc)(handler->widget, handler->selection);
		break;

	     case SelectionRequest:
		if (handler->convProc) {
		    Atom atom;
		    void *data;
		    unsigned length;
		    int format;
		    Atom prop;

		    /* they're requesting for something old */
		    if (event->xselectionrequest.time < handler->timestamp
			&& event->xselectionrequest.time != CurrentTime) {

			notifySelection(event, None);
			break;
		    }

		    handler->flags.done_pending = 1;

		    if (!(*handler->convProc)(handler->widget,
					     handler->selection,
					     event->xselectionrequest.target,
					     &atom, &data, &length, &format)) {

			notifySelection(event, None);
			break;
		    }

		    
		    prop = event->xselectionrequest.property;
		    /* obsolete clients that don't set the property field */
		    if (prop == None)
			prop = event->xselectionrequest.target;

		    if (!writeSelection(event->xselectionrequest.display,
					event->xselectionrequest.requestor,
					prop, atom, data, length, format)) {

			wfree(data);
			notifySelection(event, None);
			break;
		    }
		    wfree(data);

		    notifySelection(event, prop);

		    if (handler->doneProc) {
			(*handler->doneProc)(handler->widget, 
					     handler->selection,
					     event->xselectionrequest.target);
		    }

		    handler->flags.done_pending = 0;

		    /* in case the handler was deleted from some
		     * callback */
		    if (handler->flags.delete_pending) {
			WMDeleteSelectionHandler(handler->widget,
						 handler->selection);
		    }
		}
		break;

	     case SelectionNotify:
		
		break;
	    }
	}

	handler = handler->next;
    }
}




Bool
WMCreateSelectionHandler(WMWidget *w, Atom selection, Time timestamp,
			 WMConvertSelectionProc *convProc,
			 WMLoseSelectionProc *loseProc,
			 WMSelectionDoneProc *doneProc)
{
    SelectionHandler *handler, *tmp;
    Display *dpy = WMWidgetScreen(w)->display;

    XSetSelectionOwner(dpy, selection, WMWidgetXID(w), timestamp);
    if (XGetSelectionOwner(dpy, selection) != WMWidgetXID(w))
	return False;

    handler = malloc(sizeof(SelectionHandler));
    if (!handler)
	return False;

    handler->widget = w;
    handler->selection = selection;
    handler->timestamp = timestamp;
    handler->convProc = convProc;
    handler->loseProc = loseProc;
    handler->doneProc = doneProc;
    memset(&handler->flags, 0, sizeof(handler->flags));

    if (!selHandlers) {
	/* first in the queue */
	handler->next = selHandlers;
	selHandlers = handler;
    } else {
	tmp = selHandlers;
	while (tmp->next) {
	    tmp = tmp->next;
	}
	handler->next = tmp->next;
	tmp->next = handler;
    }

    return True;
}




static void
timeoutHandler(void *data)
{
    *(int*)data = 1;
}


static Bool
getInternalSelection(WMScreen *scr, Atom selection, Atom target,
		     void **data, unsigned *length)
{
    Window owner;
    SelectionHandler *handler;

    /*
     * Check if the selection is owned by this application and if so,
     * do the conversion directly.
     */

    *data = NULL;

    owner = XGetSelectionOwner(scr->display, selection);
    if (!owner)
	return False;

    handler = selHandlers;

    while (handler) {
	if (WMWidgetXID(handler->widget) == owner
	    /*	    && handler->selection == event->selection*/) {
	    break;
	}
	handler = handler->next;
    }

    if (!handler)
	return False;

    if (handler->convProc) {
	Atom atom;
	int format;

	if (!(*handler->convProc)(handler->widget, handler->selection, 
				  target, &atom, data, length, &format)) {
	    return True;
	}

	if (handler->doneProc) {
	    (*handler->doneProc)(handler->widget, handler->selection, target);
	}
    }

    return True;
}


char*
W_GetTextSelection(WMScreen *scr, Atom selection)
{
    int buffer = -1;
    
    switch (selection) {
     case XA_CUT_BUFFER0:
	buffer = 0;
	break;
     case XA_CUT_BUFFER1:
	buffer = 1;
	break;
     case XA_CUT_BUFFER2:
	buffer = 2;
	break;
     case XA_CUT_BUFFER3:
	buffer = 3;
	break;
     case XA_CUT_BUFFER4:
	buffer = 4;
	break;
     case XA_CUT_BUFFER5:
	buffer = 5;
	break;
     case XA_CUT_BUFFER6:
	buffer = 6;
	break;
     case XA_CUT_BUFFER7:
	buffer = 7;
	break;
    }
    if (buffer >= 0) {
	char *data;
	int size;

	data = XFetchBuffer(scr->display, &size, buffer);

	return data;
    } else {
	char *data;
	int bits;
	Atom rtype;
	unsigned long len, bytes;
	WMHandlerID timer;
	int timeout = 0;
	XEvent ev;
	unsigned length;
	
	XDeleteProperty(scr->display, scr->groupLeader, scr->clipboardAtom);

	if (getInternalSelection(scr, selection, XA_STRING, (void**)&data,
				 &length)) {

	    return data;
	}
	
	XConvertSelection(scr->display, selection, XA_STRING,
			  scr->clipboardAtom, scr->groupLeader,
			  scr->lastEventTime);
	
	timer = WMAddTimerHandler(1000, timeoutHandler, &timeout);
	
	while (!XCheckTypedWindowEvent(scr->display, scr->groupLeader,
				       SelectionNotify, &ev) && !timeout);
	
	if (!timeout) {
	    WMDeleteTimerHandler(timer);
	} else {
	    wwarning("selection retrieval timed out");
	    return NULL;
	}

	/* nobody owns the selection or the current owner has
	 * nothing to do with what we need */
	if (ev.xselection.property == None) {
	    return NULL;
	}

	if (XGetWindowProperty(scr->display, scr->groupLeader, 
			       scr->clipboardAtom, 0, MAX_PROPERTY_SIZE, 
			       False, XA_STRING, &rtype, &bits, &len,
			       &bytes, (unsigned char**)&data)!=Success) {
	    return NULL;
	}
	if (rtype!=XA_STRING || bits!=8) {
	    wwarning("invalid data in text selection");
	    if (data)
		XFree(data);
	    return NULL;
	}
	return data;
    }
}

