/* event.c
 *
 * $Id$
 *
 * Copyright 1990, 1991, 1992, 1993, 1994, 1995, Oliver Laumann, Berlin
 * Copyright 2002, 2003 Sam Hocevar <sam@zoy.org>, Paris
 *
 * This software was derived from Elk 1.2, which was Copyright 1987, 1988,
 * 1989, Nixdorf Computer AG and TELES GmbH, Berlin (Elk 1.2 has been written
 * by Oliver Laumann for TELES Telematic Services, Berlin, in a joint project
 * between TELES and Nixdorf Microprocessor Engineering, Berlin).
 *
 * Oliver Laumann, TELES GmbH, Nixdorf Computer AG and Sam Hocevar, as co-
 * owners or individual owners of copyright in this software, grant to any
 * person or company a worldwide, royalty free, license to
 *
 *    i) copy this software,
 *   ii) prepare derivative works based on this software,
 *  iii) distribute copies of this software or derivative works,
 *   iv) perform this software, or
 *    v) display this software,
 *
 * provided that this notice is not removed and that neither Oliver Laumann
 * nor Teles nor Nixdorf are deemed to have made any representations as to
 * the suitability of this software for any purpose nor are held responsible
 * for any defects of this software.
 *
 * THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE.
 */

#include "xlib.h"

#include <string.h>

#define MAX_ARGS 14

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",      4 },
    { "mapping-notify",      4 },
    { 0,                     0 }
};

struct predicate_arg {
    Object *funcs;
    Object *ret;
};

/*ARGSUSED*/
static int Event_Predicate (Display *dpy, XEvent *ep,
#ifdef XLIB_RELEASE_5_OR_LATER
                XPointer ptr) {
#else
                char *ptr) {
#endif
    struct predicate_arg *ap = (struct predicate_arg *)ptr;
    register int i;
    Object args;
    GC_Node;

    if ((i = ep->type) < LASTEvent && !Nullp (ap->funcs[i])) {
        args = Get_Event_Args (ep);
        GC_Link (args);
        *ap->ret = Funcall (ap->funcs[i], args, 0);
        Destroy_Event_Args (args);
        GC_Unlink;
    }
    return Truep (*ap->ret);
}

/* (handle-events display discard? peek? clause...)
 * clause = (event function) or ((event...) function) or (else function)
 * loops/blocks until a function returns x != #f, then returns x.
 * discard?: discard unprocessed events.
 * peek?: don't discard processed events.
 */

static Object P_Handle_Events (Object argl) {
    Object next, clause, func, ret, funcs[LASTEvent], args;
    register int i, discard, peek;
    Display *dpy;
    char *errmsg = "event occurs more than once";
    GC_Node3; struct gcnode gcv;
    TC_Prolog;

    TC_Disable;
    clause = args = Null;
    GC_Link3 (argl, clause, args);
    next = Eval (Car (argl));
    Check_Type (next, T_Display);
    dpy = DISPLAY(next)->dpy;
    argl = Cdr (argl);
    next = Eval (Car (argl));
    Check_Type (next, T_Boolean);
    discard = Truep (next);
    argl = Cdr (argl);
    next = Eval (Car (argl));
    Check_Type (next, T_Boolean);
    peek = Truep (next);
    for (i = 0; i < LASTEvent; i++)
        funcs[i] = Null;
    gcv.gclen = 1+LASTEvent; gcv.gcobj = funcs; gcv.next = &gc3; GC_List = &gcv;
    for (argl = Cdr (argl); !Nullp (argl); argl = Cdr (argl)) {
        clause = Car (argl);
        Check_List (clause);
        if (Fast_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 < LASTEvent; 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)) {
        XEvent e;
        if (discard) {
            (peek ? XPeekEvent : XNextEvent) (dpy, &e);
            if ((i = e.type) < LASTEvent && !Nullp (funcs[i])) {
                args = Get_Event_Args (&e);
                ret = Funcall (funcs[i], args, 0);
                Destroy_Event_Args (args);
            } else {
                if (peek)
                    XNextEvent (dpy, &e);  /* discard it */
            }
        } else {
            struct predicate_arg a;
            a.funcs = funcs;
            a.ret = &ret;
            (peek ? XPeekIfEvent : XIfEvent) (dpy, &e, Event_Predicate,
#ifdef XLIB_RELEASE_5_OR_LATER
                (XPointer)&a);
#else
                (char *)&a);
#endif
        }
    }
    GC_Unlink;
    TC_Enable;
    return ret;
}

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

Object Get_Event_Args (XEvent *ep) {
    Object tmpargs[MAX_ARGS];
    register int 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_Integer (p->x);
        a[6] = Make_Integer (p->y);
        a[7] = Make_Integer (p->x_root);
        a[8] = Make_Integer (p->y_root);
        if (e == KeyPress || e == KeyRelease) {
            a[9] = Bits_To_Symbols ((unsigned long)p->state, 1, State_Syms);
            a[10] = Make_Integer (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_Integer (p->x);
        a[3] = Make_Integer (p->y);
        a[4] = Make_Integer (p->width);
        a[5] = Make_Integer (p->height);
        a[6] = Make_Integer (p->count);
    } break;
    case GraphicsExpose: {
        register XGraphicsExposeEvent *p = (XGraphicsExposeEvent *)ep;
        a[1] = Make_Window (0, p->display, p->drawable);
        a[2] = Make_Integer (p->x);
        a[3] = Make_Integer (p->y);
        a[4] = Make_Integer (p->width);
        a[5] = Make_Integer (p->height);
        a[6] = Make_Integer (p->count);
        a[7] = Make_Integer (p->major_code);
        a[8] = Make_Integer (p->minor_code);
    } break;
    case NoExpose: {
        register XNoExposeEvent *p = (XNoExposeEvent *)ep;
        a[1] = Make_Window (0, p->display, p->drawable);
        a[2] = Make_Integer (p->major_code);
        a[3] = Make_Integer (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_Integer (p->x);
        a[4] = Make_Integer (p->y);
        a[5] = Make_Integer (p->width);
        a[6] = Make_Integer (p->height);
        a[7] = Make_Integer (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_Integer (p->x);
        a[5] = Make_Integer (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_Integer (p->x);
        a[4] = Make_Integer (p->y);
        a[5] = Make_Integer (p->width);
        a[6] = Make_Integer (p->height);
        a[7] = Make_Integer (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_Integer (p->x);
        a[4] = Make_Integer (p->y);
        a[5] = Make_Integer (p->width);
        a[6] = Make_Integer (p->height);
        a[7] = Make_Integer (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_Long (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_Integer (p->x);
        a[4] = Make_Integer (p->y);
    } break;
    case ResizeRequest: {
        register XResizeRequestEvent *p = (XResizeRequestEvent *)ep;
        a[1] = Make_Window (0, p->display, p->window);
        a[2] = Make_Integer (p->width);
        a[3] = Make_Integer (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: {
        register XClientMessageEvent *p = (XClientMessageEvent *)ep;
        register int i;

        a[1] = Make_Window (0, p->display, p->window);
        a[2] = Make_Atom (p->message_type);
        switch (p->format) {
        case 8:
            a[3] = Make_String (p->data.b, 20);
            break;
        case 16:
            a[3] = Make_Vector (10, Null);
            for (i = 0; i < 10; i++)
                VECTOR(a[3])->data[i] = Make_Integer (p->data.s[i]);
            break;
        case 32:
            a[3] = Make_Vector (5, Null);
            for (i = 0; i < 5; i++)
                VECTOR(a[3])->data[i] = Make_Long (p->data.l[i]);
            break;
        default:
            a[3] = Make_Integer (p->format);   /* ??? */
        }
    } 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_Integer (p->first_keycode);
        a[4] = Make_Integer (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;
}

void Destroy_Event_Args (Object args) {
    Object t;

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

int Encode_Event (Object e) {
    Object s;
    register char *p;
    register struct event_desc *ep;
    register int 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;
}

static Object P_Get_Motion_Events (Object w, Object from, Object to) {
    XTimeCoord *p;
    int n;
    register int i;
    Object e, ret;
    GC_Node2;

    Check_Type (w, T_Window);
    p = XGetMotionEvents (WINDOW(w)->dpy, WINDOW(w)->win, Get_Time (from),
        Get_Time (to), &n);
    e = ret = Make_Vector (n, Null);
    GC_Link2 (ret, e);
    for (i = 0; i < n; i++) {
        e = P_Make_List (Make_Integer (3), Null);
        VECTOR(ret)->data[i] = e;
        Car (e) = Get_Time_Arg (p[i].time); e = Cdr (e);
        Car (e) = Make_Integer (p[i].x); e = Cdr (e);
        Car (e) = Make_Integer (p[i].y);
    }
    GC_Unlink;
    XFree ((char *)p);
    return ret;
}

static Object P_Event_Listen (Object d, Object wait_flag) {
    Display *dpy;
    register int n;
    XEvent e;

    Check_Type (d, T_Display);
    Check_Type (wait_flag, T_Boolean);
    dpy = DISPLAY(d)->dpy;
    n = XPending (dpy);
    if (n == 0 && EQ(wait_flag, True)) {
        XPeekEvent (dpy, &e);
        n = XPending (dpy);
    }
    return Make_Integer (n);
}

void elk_init_xlib_event () {
    Object t;
    register int i;

    Argl = P_Make_List (Make_Integer (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_Primitive (P_Handle_Events,   "handle-events",     3, MANY, NOEVAL);
    Define_Primitive (P_Get_Motion_Events,
                        "get-motion-events",                  3, 3, EVAL);
    Define_Primitive (P_Event_Listen,    "event-listen",      2, 2, EVAL);
}