#include "xlib.h"

#define MAX_ARGS 14

static Object Sym_Else;
static Object Argl, Argv;

static struct event_desc {
    char *name;
    int argc;
} Event_Table[] = {
    { "event-0", 	     1 },
    { "event-1",             1 },
    { "key-press",          12 },
    { "key-release",        12 },
    { "button-press",       12 },
    { "button-release",     12 },
    { "motion-notify",      12 },
    { "enter-notify",       14 },
    { "leave-notify",       14 },
    { "focus-in",            4 },
    { "focus-out",           4 },
    { "keymap-notify",       3 },
    { "expose",              7 },
    { "graphics-expose",     9 },
    { "no-expose",           4 },
    { "visibility-notify",   3 },
    { "create-notify",       9 },
    { "destroy-notify",      3 },
    { "unmap-notify",        4 },
    { "map-notify",          4 },
    { "map-request",         3 },
    { "reparent-notify",     7 },
    { "configure-notify",   10 },
    { "configure-request",  11 },
    { "gravity-notify",      5 },
    { "resize-request",      4 },
    { "circulate-notify",    4 },
    { "circulate-request",   4 },
    { "property-notify",     5 },
    { "selection-clear",     4 },
    { "selection-request",   7 },
    { "selection-notify",    6 },
    { "colormap-notify",     5 },
    { "client-message",      1 },
    { "mapping-notify",      4 },
    { 0,                     0 }
};

/* (handle-events display clause...)
 * clause = (event function) or ((event...) function) or (else function)
 * loops/blocks until a function returns x != #f, then returns x.
 */

static Object P_Handle_Events (argl) Object argl; {
    Object disp, clause, func, ret, funcs[LASTEvent];
    register i;
    Display *dpy;
    Window win = None;
    XEvent e;
    char *errmsg = "event occurs more than once";
    GC_Node2; struct gcnode gcv;
    TC_Prolog;

    TC_Disable;
    clause = Null;
    GC_Link2 (argl, clause);
    disp = Eval (Car (argl));
    if (TYPE(disp) == T_Display) {
	dpy = DISPLAY(disp)->dpy;
    } else if (TYPE(disp) == T_Window) {
	dpy = WINDOW(disp)->dpy;
	win = WINDOW(disp)->win;
    } else Wrong_Type_Combination (disp, "display or window");
    for (i = 0; i < 32; i++)
	funcs[i] = Null;
    gcv.gclen = 1 + 32; gcv.gcobj = funcs; gcv.next = &gc2; GC_List = &gcv;
    for (argl = Cdr (argl); !Nullp (argl); argl = Cdr (argl)) {
	clause = Car (argl);
	Check_List (clause);
	if (Internal_Length (clause) != 2)
	    Primitive_Error ("badly formed event clause");
	func = Eval (Car (Cdr (clause)));
	Check_Procedure (func);
	clause = Car (clause);
	if (EQ(clause, Sym_Else)) {
	    for (i = 0; i < 32; i++)
		if (Nullp (funcs[i])) funcs[i] = func;
	} else {
	    if (TYPE(clause) == T_Pair) {
		for (; !Nullp (clause); clause = Cdr (clause)) {
		    i = Encode_Event (Car (clause));
		    if (!Nullp (funcs[i]))
			Primitive_Error (errmsg);
		    funcs[i] = func;
		}
	    } else {
		i = Encode_Event (clause);
		if (!Nullp (funcs[i]))
		    Primitive_Error (errmsg);
		funcs[i] = func;
	    }
	}
    }
    ret = False;
    while (!Truep (ret)) {
	if (win == None)
	    XNextEvent (dpy, &e);
	else
	    XWindowEvent (dpy, win, ~0L, &e);
	if ((i = e.type) < LASTEvent && !Nullp (funcs[i])) {
	    Object args = Get_Event_Args (&e);
	    ret = Funcall (funcs[i], args, 0);
	    /*
	     * The argument vector is cleared to destroy all references
	     * to the arguments (so that a GC can throw away the objects):
	     */
	    Destroy_Event_Args (args);
	}
    }
    GC_Unlink;
    TC_Enable;
    return ret;
}

Object Process_Event (ep, argl) XEvent *ep; Object argl; {
    Object disp, clause, func, ret, funcs[LASTEvent];
    register i;
    Display *dpy;
    Window win = None;
    char *errmsg = "event occurs more than once";
    GC_Node2; struct gcnode gcv;
    TC_Prolog;

    TC_Disable;
    clause = Null;
    GC_Link2 (argl, clause);
    disp = Eval (Car (argl));
    if (TYPE(disp) == T_Display) {
	dpy = DISPLAY(disp)->dpy;
    } else if (TYPE(disp) == T_Window) {
	dpy = WINDOW(disp)->dpy;
	win = WINDOW(disp)->win;
    } else Wrong_Type_Combination (disp, "display or window");
    for (i = 0; i < 32; i++)
	funcs[i] = Null;
    gcv.gclen = 1 + 32; gcv.gcobj = funcs; gcv.next = &gc2; GC_List = &gcv;
    for (argl = Cdr (argl); !Nullp (argl); argl = Cdr (argl)) {
	clause = Car (argl);
	Check_List (clause);
	if (Internal_Length (clause) != 2)
	    Primitive_Error ("badly formed event clause");
	func = Eval (Car (Cdr (clause)));
	Check_Procedure (func);
	clause = Car (clause);
	if (EQ(clause, Sym_Else)) {
	    for (i = 0; i < 32; i++)
		if (Nullp (funcs[i])) funcs[i] = func;
	} else {
	    if (TYPE(clause) == T_Pair) {
		for (; !Nullp (clause); clause = Cdr (clause)) {
		    i = Encode_Event (Car (clause));
		    if (!Nullp (funcs[i]))
			Primitive_Error (errmsg);
		    funcs[i] = func;
		}
	    } else {
		i = Encode_Event (clause);
		if (!Nullp (funcs[i]))
		    Primitive_Error (errmsg);
		funcs[i] = func;
	    }
	}
    }
    ret = False;
    if ((i = ep->type) < LASTEvent && !Nullp (funcs[i])) {
	Object args = Get_Event_Args (ep);
	ret = Funcall (funcs[i], args, 0);
	/*
	 * The argument vector is cleared to destroy all references
	 * to the arguments (so that a GC can throw away the objects):
	 */
	Destroy_Event_Args (args);
    }
    GC_Unlink;
    TC_Enable;
    return ret;
}

static Object Get_Time_Arg (t) Time t; {
    return t == CurrentTime ? Sym_Now : Make_Unsigned ((unsigned)t);
}

Object Get_Event_Args (ep) XEvent *ep; {
    Object tmpargs[MAX_ARGS];
    register e, i;
    register Object *a, *vp;
    struct gcnode gcv;
    Object dummy;
    GC_Node;

    e = ep->type;
    dummy = Null;
    a = tmpargs;
    for (i = 0; i < MAX_ARGS; i++)
	a[i] = Null;
    GC_Link (dummy);
    gcv.gclen = 1 + MAX_ARGS; gcv.gcobj = a; gcv.next = &gc1; GC_List = &gcv;
    switch (e) {
    case KeyPress: case KeyRelease:
    case ButtonPress: case ButtonRelease:
    case MotionNotify:
    case EnterNotify: case LeaveNotify: {
	register XKeyEvent *p = (XKeyEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Make_Window (0, p->display, p->root);
	a[3] = Make_Window (0, p->display, p->subwindow);
	a[4] = Get_Time_Arg (p->time);
	a[5] = Make_Fixnum (p->x);
	a[6] = Make_Fixnum (p->y);
	a[7] = Make_Fixnum (p->x_root);
	a[8] = Make_Fixnum (p->y_root);
	if (e == KeyPress || e == KeyRelease) {
	    a[9] = Bits_To_Symbols ((unsigned long)p->state, 1, State_Syms);
	    a[10] = Make_Fixnum (p->keycode);
	    a[11] = p->same_screen ? True : False;
	} else if (e == ButtonPress || e == ButtonRelease) {
	    register XButtonEvent *q = (XButtonEvent *)ep;
	    a[9] = Bits_To_Symbols ((unsigned long)q->state, 1, State_Syms);
	    a[10] = Bits_To_Symbols ((unsigned long)q->button, 0, Button_Syms);
	    a[11] = q->same_screen ? True : False;
	} else if (e == MotionNotify) {
	    register XMotionEvent *q = (XMotionEvent *)ep;
	    a[9] = Bits_To_Symbols ((unsigned long)q->state, 1, State_Syms);
	    a[10] = q->is_hint ? True : False;
	    a[11] = q->same_screen ? True : False;
	} else {
	    register XCrossingEvent *q = (XCrossingEvent *)ep;
	    a[9] = Bits_To_Symbols ((unsigned long)q->mode, 0, Cross_Mode_Syms);
	    a[10] = Bits_To_Symbols ((unsigned long)q->detail, 0,
		Cross_Detail_Syms);
	    a[11] = q->same_screen ? True : False;
	    a[12] = q->focus ? True : False;
	    a[13] = Bits_To_Symbols ((unsigned long)q->state, 1, Button_Syms);
	}
    } break;
    case FocusIn: case FocusOut: {
	register XFocusChangeEvent *p = (XFocusChangeEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Bits_To_Symbols ((unsigned long)p->mode, 0, Cross_Mode_Syms);
	a[3] = Bits_To_Symbols ((unsigned long)p->detail, 0, Focus_Detail_Syms);
    } break;
    case KeymapNotify: {
	register XKeymapEvent *p = (XKeymapEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Make_String (p->key_vector, 32);
    } break;
    case Expose: {
	register XExposeEvent *p = (XExposeEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Make_Fixnum (p->x);
	a[3] = Make_Fixnum (p->y);
	a[4] = Make_Fixnum (p->width);
	a[5] = Make_Fixnum (p->height);
	a[6] = Make_Fixnum (p->count);
    } break;
    case GraphicsExpose: {
	register XGraphicsExposeEvent *p = (XGraphicsExposeEvent *)ep;
	a[1] = Make_Window (0, p->display, p->drawable);
	a[2] = Make_Fixnum (p->x);
	a[3] = Make_Fixnum (p->y);
	a[4] = Make_Fixnum (p->width);
	a[5] = Make_Fixnum (p->height);
	a[6] = Make_Fixnum (p->count);
	a[7] = Make_Fixnum (p->major_code);
	a[8] = Make_Fixnum (p->minor_code);
    } break;
    case NoExpose: {
	register XNoExposeEvent *p = (XNoExposeEvent *)ep;
	a[1] = Make_Window (0, p->display, p->drawable);
	a[2] = Make_Fixnum (p->major_code);
	a[3] = Make_Fixnum (p->minor_code);
    } break;
    case VisibilityNotify: {
	register XVisibilityEvent *p = (XVisibilityEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Bits_To_Symbols ((unsigned long)p->state, 0, Visibility_Syms);
    } break;
    case CreateNotify: {
	register XCreateWindowEvent *p = (XCreateWindowEvent *)ep;
	a[1] = Make_Window (0, p->display, p->parent);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = Make_Fixnum (p->x);
	a[4] = Make_Fixnum (p->y);
	a[5] = Make_Fixnum (p->width);
	a[6] = Make_Fixnum (p->height);
	a[7] = Make_Fixnum (p->border_width);
	a[8] = p->override_redirect ? True : False;
    } break;
    case DestroyNotify: {
	register XDestroyWindowEvent *p = (XDestroyWindowEvent *)ep;
	a[1] = Make_Window (0, p->display, p->event);
	a[2] = Make_Window (0, p->display, p->window);
    } break;
    case UnmapNotify: {
	register XUnmapEvent *p = (XUnmapEvent *)ep;
	a[1] = Make_Window (0, p->display, p->event);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = p->from_configure ? True : False;
    } break;
    case MapNotify: {
	register XMapEvent *p = (XMapEvent *)ep;
	a[1] = Make_Window (0, p->display, p->event);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = p->override_redirect ? True : False;
    } break;
    case MapRequest: {
	register XMapRequestEvent *p = (XMapRequestEvent *)ep;
	a[1] = Make_Window (0, p->display, p->parent);
	a[2] = Make_Window (0, p->display, p->window);
    } break;
    case ReparentNotify: {
	register XReparentEvent *p = (XReparentEvent *)ep;
	a[1] = Make_Window (0, p->display, p->event);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = Make_Window (0, p->display, p->parent);
	a[4] = Make_Fixnum (p->x);
	a[5] = Make_Fixnum (p->y);
	a[6] = p->override_redirect ? True : False;
    } break;
    case ConfigureNotify: {
	register XConfigureEvent *p = (XConfigureEvent *)ep;
	a[1] = Make_Window (0, p->display, p->event);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = Make_Fixnum (p->x);
	a[4] = Make_Fixnum (p->y);
	a[5] = Make_Fixnum (p->width);
	a[6] = Make_Fixnum (p->height);
	a[7] = Make_Fixnum (p->border_width);
	a[8] = Make_Window (0, p->display, p->above);
	a[9] = p->override_redirect ? True : False;
    } break;
    case ConfigureRequest: {
	register XConfigureRequestEvent *p = (XConfigureRequestEvent *)ep;
	a[1] = Make_Window (0, p->display, p->parent);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = Make_Fixnum (p->x);
	a[4] = Make_Fixnum (p->y);
	a[5] = Make_Fixnum (p->width);
	a[6] = Make_Fixnum (p->height);
	a[7] = Make_Fixnum (p->border_width);
	a[8] = Make_Window (0, p->display, p->above);
	a[9] = Bits_To_Symbols ((unsigned long)p->detail, 0, Stack_Mode_Syms);
	a[10] = Make_Unsigned ((unsigned)p->value_mask);
    } break;
    case GravityNotify: {
	register XGravityEvent *p = (XGravityEvent *)ep;
	a[1] = Make_Window (0, p->display, p->event);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = Make_Fixnum (p->x);
	a[4] = Make_Fixnum (p->y);
    } break;
    case ResizeRequest: {
	register XResizeRequestEvent *p = (XResizeRequestEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Make_Fixnum (p->width);
	a[3] = Make_Fixnum (p->height);
    } break;
    case CirculateNotify: {
	register XCirculateEvent *p = (XCirculateEvent *)ep;
	a[1] = Make_Window (0, p->display, p->event);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = Bits_To_Symbols ((unsigned long)p->place, 0, Place_Syms);
    } break;
    case CirculateRequest: {
	register XCirculateRequestEvent *p = (XCirculateRequestEvent *)ep;
	a[1] = Make_Window (0, p->display, p->parent);
	a[2] = Make_Window (0, p->display, p->window);
	a[3] = Bits_To_Symbols ((unsigned long)p->place, 0, Place_Syms);
    } break;
    case PropertyNotify: {
	register XPropertyEvent *p = (XPropertyEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Make_Atom (p->atom);
	a[3] = Get_Time_Arg (p->time);
	a[4] = Bits_To_Symbols ((unsigned long)p->state, 0, Prop_Syms);
    } break;
    case SelectionClear: {
	register XSelectionClearEvent *p = (XSelectionClearEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Make_Atom (p->selection);
	a[3] = Get_Time_Arg (p->time);
    } break;
    case SelectionRequest: {
	register XSelectionRequestEvent *p = (XSelectionRequestEvent *)ep;
	a[1] = Make_Window (0, p->display, p->owner);
	a[2] = Make_Window (0, p->display, p->requestor);
	a[3] = Make_Atom (p->selection);
	a[4] = Make_Atom (p->target);
	a[5] = Make_Atom (p->property);
	a[6] = Get_Time_Arg (p->time);
    } break;
    case SelectionNotify: {
	register XSelectionEvent *p = (XSelectionEvent *)ep;
	a[1] = Make_Window (0, p->display, p->requestor);
	a[2] = Make_Atom (p->selection);
	a[3] = Make_Atom (p->target);
	a[4] = Make_Atom (p->property);
	a[5] = Get_Time_Arg (p->time);
    } break;
    case ColormapNotify: {
	register XColormapEvent *p = (XColormapEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Make_Colormap (0, p->display, p->colormap);
	a[3] = p->new ? True : False;
	a[4] = p->state == ColormapInstalled ? True : False;
    } break;
    case ClientMessage: {
    } break;
    case MappingNotify: {
	register XMappingEvent *p = (XMappingEvent *)ep;
	a[1] = Make_Window (0, p->display, p->window);
	a[2] = Bits_To_Symbols ((unsigned long)p->request, 0, Mapping_Syms);
	a[3] = Make_Fixnum (p->first_keycode);
	a[4] = Make_Fixnum (p->count);
    } break;
    }
    a[0] = Intern (Event_Table[e].name);
    for (vp = VECTOR(Argv)->data, i = 0; i < Event_Table[e].argc; i++) {
	if (i) vp++;
	Car (*vp) = a[i];
	Cdr (*vp) = vp[1];
    }
    Cdr (*vp) = Null;
    GC_Unlink;
    return Argl;
}

Destroy_Event_Args (args) Object args; {
    Object t;

    for (t = args; !Nullp (t); t = Cdr (t))
	Car (t) = Null;
}

Encode_Event (e) Object e; {
    Object s;
    register char *p;
    register struct event_desc *ep;
    register n;

    Check_Type (e, T_Symbol);
    s = SYMBOL(e)->name;
    p = STRING(s)->data;
    n = STRING(s)->size;
    for (ep = Event_Table; ep->name; ep++)
	if (n && strncmp (ep->name, p, n) == 0) break;
    if (ep->name == 0)
	Primitive_Error ("no such event: ~s", e);
    return ep-Event_Table;
}

init_xlib_event () {
    Object t;
    register i;

    Argl = P_Make_List (Make_Fixnum (MAX_ARGS), Null);
    Global_GC_Link (Argl);
    Argv = Make_Vector (MAX_ARGS, Null);
    Global_GC_Link (Argv);
    for (i = 0, t = Argl; i < MAX_ARGS; i++, t = Cdr (t))
	VECTOR(Argv)->data[i] = t;
    Define_Symbol (&Sym_Else, "else");
    Define_Primitive (P_Handle_Events,   "handle-events",     2, MANY, NOEVAL);
}
