#include "xt.h"

typedef struct {
    PFX2S converter;
    int num;
} CLIENT_DATA;

Object Get_Callbackfun (c) XtPointer c; {
    register CLIENT_DATA *cd = (CLIENT_DATA *)c;
    return cd ? Get_Function (cd->num) : False;
}

static void Callback_Proc (w, client_data, call_data) Widget w;
	XtPointer client_data, call_data; {
    register CLIENT_DATA *cd = (CLIENT_DATA *)client_data;
    Object args = Null;
    GC_Node;

    GC_Link (args);
    if (cd->converter)
	args = Cons ((cd->converter)((XtArgVal)call_data), args);
    args = Cons (Make_Widget_Foreign (w), args);
    GC_Unlink;
    (void)Funcall (Get_Callbackfun (client_data), args, 0);
}

/*ARGSUSED*/
void Destroy_Callback_Proc (w, client_data, call_data) Widget w;
	XtPointer client_data, call_data; {
    Object x;

    x = Find_Object (T_Widget, (GENERIC)0, Match_Xt_Obj, w);
    if (Nullp (x) || WIDGET(x)->free)
	return;
    WIDGET(x)->free = 1;
    Remove_All_Callbacks (w);
    Deregister_Object (x);
}

/* The code assumes that callbacks are called in the order they
 * have been added.  The Destroy_Callback_Proc() must always be
 * the last callback in the destroy callback list of each widget.
 *
 * When the destroy callback list of a widget is modified
 * (via P_Add_Callbacks or P_Set_Values), Fiddle_Destroy_Callback()
 * must be called to remove the Destroy_Callback_Proc() and put
 * it back to the end of the callback list.
 */
void Fiddle_Destroy_Callback (w) Widget w; {
    XtRemoveCallback (w, XtNdestroyCallback, Destroy_Callback_Proc,
	(XtPointer)0);
    XtAddCallback (w, XtNdestroyCallback, Destroy_Callback_Proc, (XtPointer)0);
}

void Check_Callback_List (x) Object x; {
    Object tail;

    Check_List (x);
    for (tail = x; !Nullp (tail); tail = Cdr (tail))
	Check_Procedure (Car (tail));
}

static Object P_Add_Callbacks (w, name, cbl) Object w, name, cbl; {
    register char *s;
    register n;
    XtCallbackList callbacks;
    Declare_C_Strings;

    Check_Widget (w);
    Check_Callback_List (cbl);
    Make_C_String (name, s);
    Make_Resource_Name (s);
    n = Fast_Length (cbl);
    Alloca (callbacks, XtCallbackRec*, (n+1) * sizeof (XtCallbackRec));
    callbacks[n].callback = 0;
    callbacks[n].closure = 0;
    Fill_Callbacks (cbl, callbacks, n,
	Find_Callback_Converter (XtClass (WIDGET(w)->widget), s, name));
    XtAddCallbacks (WIDGET(w)->widget, s, callbacks);
    if (streq (s, XtNdestroyCallback))
	Fiddle_Destroy_Callback (WIDGET(w)->widget);
    Dispose_C_Strings;
    return Void;
}

void Fill_Callbacks (src, dst, n, conv) Object src; XtCallbackList dst;
	register n; PFX2S conv; {
    register CLIENT_DATA *cd;
    register i, j;
    Object tail;

    for (i = 0, tail = src; i < n; i++, tail = Cdr (tail)) {
	j = Register_Function (Car (tail));
	cd = (CLIENT_DATA *)XtMalloc (sizeof (CLIENT_DATA));
	cd->converter = conv;
	cd->num = j;
	dst[i].callback = (XtCallbackProc)Callback_Proc;
	dst[i].closure = (XtPointer)cd;
    }
}

Remove_All_Callbacks (w) Widget w; {
    Arg a[1];
    XtCallbackList c;
    XtResource *r;
    int nr, nc;
    register i, j;

    Get_All_Resources (0, w, XtClass (w), &r, &nr, &nc);
    for (j = 0; j < nr; j++) {
	if (streq (r[j].resource_type, XtRCallback)) {
	    XtSetArg (a[0], r[j].resource_name, &c);
	    XtGetValues (w, a, 1);
	    for (i = 0; c[i].callback; i++) {
		register CLIENT_DATA *cd = (CLIENT_DATA *)c[i].closure;
		if (c[i].callback == (XtCallbackProc)Callback_Proc && cd) {
		    Deregister_Function (cd->num);
		    XtFree ((char *)cd);
		}
	    }
	}
    }
    XtFree ((char *)r);
}

init_xt_callback () {
    Define_Primitive (P_Add_Callbacks, "add-callbacks", 3, 3, EVAL);
}
