/*
 * Copyright 2001-2003 Red Hat Inc., Durham, North Carolina.
 *
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation on the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT.  IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
 * Authors:
 *   David H. Dawes <dawes@xfree86.org>
 *   Kevin E. Martin <kem@redhat.com>
 *   Rickard E. (Rik) Faith <faith@redhat.com>
 */

/** \file
 * These routines support taking input from devices on the backend
 * (output) displays.  \see dmxcommon.c. */

#ifdef HAVE_DMX_CONFIG_H
#include <dmx-config.h>
#endif

#define DMX_BACKEND_DEBUG 0

#include "dmxinputinit.h"
#include "dmxbackend.h"
#include "dmxcommon.h"
#include "dmxconsole.h"
#include "dmxcursor.h"
#include "dmxprop.h"
#include "dmxsync.h"
#include "dmxcb.h"              /* For dmxGlobalWidth and dmxGlobalHeight */
#include "dmxevents.h"          /* For dmxGetGlobalPosition */
#include "ChkNotMaskEv.h"

#include "inputstr.h"
#include "input.h"
#include <X11/keysym.h>
#include "mipointer.h"
#include "scrnintstr.h"
#include "windowstr.h"

/* Private area for backend devices. */
typedef struct _myPrivate {
    DMX_COMMON_PRIVATE;
    int                     myScreen;
    DMXScreenInfo           *grabbedScreen;
    
    int                     lastX, lastY;
    int                     centerX, centerY;
    int                     relative;
    int                     newscreen;
    int                     initialized;
    DevicePtr               mou, kbd;
    int                     entered;
    int                     offX, offY;
} myPrivate;

#if DMX_BACKEND_DEBUG
#define DMXDBG0(f)                   dmxLog(dmxDebug,f)
#define DMXDBG1(f,a)                 dmxLog(dmxDebug,f,a)
#define DMXDBG2(f,a,b)               dmxLog(dmxDebug,f,a,b)
#define DMXDBG3(f,a,b,c)             dmxLog(dmxDebug,f,a,b,c)
#define DMXDBG4(f,a,b,c,d)           dmxLog(dmxDebug,f,a,b,c,d)
#define DMXDBG5(f,a,b,c,d,e)         dmxLog(dmxDebug,f,a,b,c,d,e)
#define DMXDBG6(f,a,b,c,d,e,g)       dmxLog(dmxDebug,f,a,b,c,d,e,g)
#define DMXDBG7(f,a,b,c,d,e,g,h)     dmxLog(dmxDebug,f,a,b,c,d,e,g,h)
#define DMXDBG8(f,a,b,c,d,e,g,h,i)   dmxLog(dmxDebug,f,a,b,c,d,e,g,h,i)
#define DMXDBG9(f,a,b,c,d,e,g,h,i,j) dmxLog(dmxDebug,f,a,b,c,d,e,g,h,i,j)
#else
#define DMXDBG0(f)
#define DMXDBG1(f,a)
#define DMXDBG2(f,a,b)
#define DMXDBG3(f,a,b,c)
#define DMXDBG4(f,a,b,c,d)
#define DMXDBG5(f,a,b,c,d,e)
#define DMXDBG6(f,a,b,c,d,e,g)
#define DMXDBG7(f,a,b,c,d,e,g,h)
#define DMXDBG8(f,a,b,c,d,e,g,h,i)
#define DMXDBG9(f,a,b,c,d,e,g,h,i,j)
#endif

/** Create and return a private data structure. */
pointer dmxBackendCreatePrivate(DeviceIntPtr pDevice)
{
    GETDMXLOCALFROMPDEVICE;
    myPrivate *priv = calloc(1, sizeof(*priv));
    priv->dmxLocal  = dmxLocal;
    return priv;
}

/** Destroy the private data structure.  No checking is performed to
 * verify that the structure was actually created by
 * #dmxBackendCreatePrivate. */
void dmxBackendDestroyPrivate(pointer private)
{
    free(private);
}

static void *dmxBackendTestScreen(DMXScreenInfo *dmxScreen, void *closure)
{
    long target = (long)closure;

    if (dmxScreen->index == target) return dmxScreen;
    return NULL;
}

/* Return non-zero if screen and priv->myScreen are on the same physical
 * backend display (1 if they are the same screen, 2 if they are
 * different screens).  Since this is a common operation, the results
 * are cached.  The cache is invalidated if \a priv is NULL (this should
 * be done with each server generation and reconfiguration). */
static int dmxBackendSameDisplay(myPrivate *priv, long screen)
{
    static myPrivate *oldpriv  = NULL;
    static int       oldscreen = -1;
    static int       retcode   = 0;

    if (priv == oldpriv && screen == oldscreen) return retcode;
    if (!priv) {                /* Invalidate cache */
        oldpriv   = NULL;
        oldscreen = -1;
        retcode   = 0;
        return 0;
    }

    if (screen == priv->myScreen)                     retcode = 1;
    else if (screen < 0 || screen >= dmxNumScreens)   retcode = 0;
    else if (dmxPropertyIterate(priv->be,
                                dmxBackendTestScreen,
                                (void *)screen))      retcode = 2;
    else                                              retcode = 0;

    oldpriv   = priv;
    oldscreen = screen;
    return retcode;
}

static void *dmxBackendTestEvents(DMXScreenInfo *dmxScreen, void *closure)
{
    XEvent *X = (XEvent *)closure;
    
    if (XCheckNotMaskEvent(dmxScreen->beDisplay, ExposureMask, X))
        return dmxScreen;
    return NULL;
}

static void *dmxBackendTestMotionEvent(DMXScreenInfo *dmxScreen, void *closure)
{
    XEvent *X = (XEvent *)closure;

    if (XCheckTypedEvent(dmxScreen->beDisplay, MotionNotify, X))
        return dmxScreen;
    return NULL;
}

static DMXScreenInfo *dmxBackendGetEvent(myPrivate *priv, XEvent *X)
{
    DMXScreenInfo *dmxScreen;

    if ((dmxScreen = dmxPropertyIterate(priv->be, dmxBackendTestEvents, X)))
        return dmxScreen;
    return NULL;
}

static DMXScreenInfo *dmxBackendPendingMotionEvent(myPrivate *priv, int save)
{
    DMXScreenInfo *dmxScreen;
    XEvent        N;

    if ((dmxScreen = dmxPropertyIterate(priv->be,
                                        dmxBackendTestMotionEvent, &N))) {
        if (save) XPutBackEvent(dmxScreen->beDisplay, &N);
        return dmxScreen;
    }
    return NULL;
}

static void *dmxBackendTestWindow(DMXScreenInfo *dmxScreen, void *closure)
{
    Window win = (Window)(long)closure;
    if (dmxScreen->scrnWin == win) return dmxScreen;
    return NULL;
}

static DMXScreenInfo *dmxBackendFindWindow(myPrivate *priv, Window win)
{
    return dmxPropertyIterate(priv->be, dmxBackendTestWindow,
                              (void *)(long)win);
}

/* If the cursor is over a set of overlapping screens and one of those
 * screens takes backend input, then we want that particular screen to
 * be current, not one of the other ones. */
static int dmxBackendFindOverlapping(myPrivate *priv, int screen, int x, int y)
{
    DMXScreenInfo *start = &dmxScreens[screen];
    DMXScreenInfo *pt;

    if (!start->over) return screen;
    
    for (pt = start->over; /* condition at end of loop */; pt = pt->over) {
        if (pt->index == priv->myScreen
            && dmxOnScreen(x, y, &dmxScreens[pt->index])) return pt->index;
        if (pt == start) break;
    }
    return screen;
}

/* Return non-zero if \a x and \a y are off \a screen. */
static int dmxBackendOffscreen(int screen, int x, int y)
{
    DMXScreenInfo *dmxScreen = &dmxScreens[screen];

    return (!dmxOnScreen(x, y, dmxScreen));
}

/** This routine is called from #dmxCoreMotion for each motion
 * event. \a x and \a y are global coordinants. */
void dmxBackendUpdatePosition(pointer private, int x, int y)
{
    GETPRIVFROMPRIVATE;
    int           screen      = miPointerGetScreen(inputInfo.pointer)->myNum;
    DMXScreenInfo *dmxScreen  = &dmxScreens[priv->myScreen];
    int           oldRelative = priv->relative;
    int           topscreen   = dmxBackendFindOverlapping(priv, screen, x, y);
    int           same        = dmxBackendSameDisplay(priv, topscreen);
    int           offscreen   = dmxBackendOffscreen(priv->myScreen, x, y);
    int           offthis     = dmxBackendOffscreen(screen, x, y);

    DMXDBG9("dmxBackendUpdatePosition(%d,%d) my=%d mi=%d rel=%d"
            " topscreen=%d same=%d offscreen=%d offthis=%d\n",
            x, y, priv->myScreen, screen, priv->relative,
            topscreen, same, offscreen, offthis);

    if (offscreen) {
        /* If the cursor is off the input screen, it should be moving
         * relative unless it is visible on a screen of the same display
         * (i.e., one that shares the mouse). */
        if (same == 2 && !offthis) {
            if (priv->relative) {
                DMXDBG0("   Off screen, but not absolute\n");
                priv->relative = 0;
            }
        } else {
            if (!priv->relative) {
                DMXDBG0("   Off screen, but not relative\n");
                priv->relative = 1;
            }
        }
    } else {
        if (topscreen != screen) {
            DMXDBG2("   Using screen %d instead of %d (from mi)\n",
                    topscreen, screen);
        }
        if (same) {
            if (priv->relative) {
                DMXDBG0("   On screen, but not absolute\n");
                priv->relative = 0;
            }
        } else {
            if (!priv->relative) {
                DMXDBG0("   Not on screen, but not relative\n");
                priv->relative = 1;
            }
        }
    }

    if (oldRelative != priv->relative) {
        DMXDBG2("   Do switch, relative=%d same=%d\n",
                priv->relative, same);
        /* Discard all pre-switch events */
        dmxSync(dmxScreen, TRUE);
        while (dmxBackendPendingMotionEvent(priv, FALSE));
        
        if (dmxInput->console && offscreen) {
            /* Our special case is a console window and a backend window
             * share a display.  In this case, the cursor is either on
             * the backend window (taking absolute input), or not (in
             * which case the cursor needs to be in the console
             * window). */
            if (priv->grabbedScreen) {
                DMXDBG2("   *** force ungrab on %s, display=%p\n",
                        priv->grabbedScreen->name,
                        priv->grabbedScreen->beDisplay);
                XUngrabPointer(priv->grabbedScreen->beDisplay, CurrentTime);
                dmxSync(priv->grabbedScreen, TRUE);
                priv->grabbedScreen = NULL;
            }
            DMXDBG0("   Capturing console\n");
            dmxConsoleCapture(dmxInput);
        } else {
            priv->newscreen = 1;
            if (priv->relative && !dmxInput->console) {
                DMXDBG5("   Hide cursor; warp from %d,%d to %d,%d on %d\n",
                        priv->lastX, priv->lastY, priv->centerX, priv->centerY,
                        priv->myScreen);
                dmxConsoleUncapture(dmxInput);
                dmxHideCursor(dmxScreen);
                priv->lastX   = priv->centerX;
                priv->lastY   = priv->centerY;
                XWarpPointer(priv->display, None, priv->window,
                             0, 0, 0, 0, priv->lastX, priv->lastY);
                dmxSync(dmxScreen, TRUE);
            } else {
                DMXDBG0("   Check cursor\n");
                dmxCheckCursor();
            }
        }
    }
}

/** Get events from the X queue on the backend servers and put the
 * events into the DMX event queue. */
void dmxBackendCollectEvents(DevicePtr pDev,
                             dmxMotionProcPtr motion,
                             dmxEnqueueProcPtr enqueue,
                             dmxCheckSpecialProcPtr checkspecial,
                             DMXBlockType block)
{
    GETPRIVFROMPDEV;
    GETDMXINPUTFROMPRIV;
    XEvent               X;
    DMXScreenInfo        *dmxScreen;
    int                  left        = 0;
    int                  entered     = priv->entered;
    int                  ignoreLeave = 0;
    int                  v[2];
    int                  retcode;

    while ((dmxScreen = dmxBackendGetEvent(priv, &X))) {
	switch (X.type) {
        case EnterNotify:
            dmxCommonSaveState(priv);
            if (entered++)
                continue;
            priv->entered = 1;
            ignoreLeave   = 1;
            DMXDBG5("dmxBackendCollectEvents: Enter %lu %d,%d; GRAB %s %p\n",
                    X.xcrossing.root, X.xcrossing.x, X.xcrossing.y,
                    dmxScreen->name, dmxScreen->beDisplay);
            XRaiseWindow(dmxScreen->beDisplay, dmxScreen->scrnWin);
            priv->grabbedScreen = dmxScreen;
            if ((retcode = XGrabPointer(dmxScreen->beDisplay,
					dmxScreen->scrnWin,
                                        True, 0, GrabModeAsync,
                                        GrabModeAsync, None, None,
                                        CurrentTime))) {
                dmxLog(dmxError,
                       "XGrabPointer failed during backend enter (%d)\n",
                       retcode);
            }
            break;
        case LeaveNotify:
            if (ignoreLeave) {
                ignoreLeave = 0;
                continue;
            }
            dmxCommonRestoreState(priv);
            if (left++)
                continue;
            DMXDBG7("dmxBackendCollectEvents: Leave %lu %d,%d %d %d %s %s\n",
                    X.xcrossing.root, X.xcrossing.x, X.xcrossing.y,
                    X.xcrossing.detail, X.xcrossing.focus,
                    priv->grabbedScreen ? "UNGRAB" : "",
                    dmxScreen->name);
            if (priv->grabbedScreen) {
                XUngrabPointer(priv->grabbedScreen->beDisplay, CurrentTime);
                dmxSync(priv->grabbedScreen, TRUE);
                priv->grabbedScreen = NULL;
            }
            break;
	case MotionNotify:
            DMXDBG9("dmxBackendCollectEvents: MotionNotify %d/%d (mi %d)"
                    " newscreen=%d: %d %d (e=%d; last=%d,%d)\n",
                    dmxScreen->index, priv->myScreen,
                    miPointerCurrentScreen()->myNum,
                    priv->newscreen,
                    X.xmotion.x, X.xmotion.y,
                    entered, priv->lastX, priv->lastY);
            if (dmxBackendPendingMotionEvent(priv, TRUE))
                continue;
            if (!(dmxScreen = dmxBackendFindWindow(priv, X.xmotion.window)))
                dmxLog(dmxFatal,
                       "   Event on non-existant window %lu\n",
                       X.xmotion.window);
            if (!priv->relative || dmxInput->console) {
                int newX = X.xmotion.x - dmxScreen->rootX;
                int newY = X.xmotion.y - dmxScreen->rootY;

                if (!priv->newscreen) {
                    int width  = dmxScreen->rootWidth;
                    int height = dmxScreen->rootHeight;
                    if (!newX)              newX = -1;
                    if (newX == width - 1)  newX = width;
                    if (!newY)              newY = -1;
                    if (newY == height - 1) newY = height;
                }
                priv->newscreen = 0;
                v[0] = dmxScreen->rootXOrigin + newX;
                v[1] = dmxScreen->rootYOrigin + newY;
                DMXDBG8("   Absolute move: %d,%d (r=%dx%d+%d+%d s=%dx%d)\n",
                        v[0], v[1],
                        priv->be->rootWidth, priv->be->rootHeight,
                        priv->be->rootX, priv->be->rootY,
                        priv->be->scrnWidth, priv->be->scrnHeight);
                motion(priv->mou, v, 0, 2, DMX_ABSOLUTE, block);
                priv->entered = 0;
            } else {
                int newX = priv->lastX - X.xmotion.x;
                int newY = priv->lastY - X.xmotion.y;
                priv->lastX = X.xmotion.x;
                priv->lastY = X.xmotion.y;
                v[0]        = newX;
                v[1]        = newY;
                DMXDBG2("   Relative move: %d, %d\n", v[0], v[1]);
                motion(priv->mou, v, 0, 2, DMX_RELATIVE, block);
            }
            if (entered && priv->relative) {
                DMXDBG4("   **** Relative %d %d instead of absolute %d %d\n",
                        v[0], v[1],
                        (dmxScreen->rootXOrigin + X.xmotion.x
                         - dmxScreen->rootX),
                        (dmxScreen->rootYOrigin + X.xmotion.y
                         - dmxScreen->rootY));
            }
	    break;

        case KeyPress:
        case KeyRelease:
            enqueue(priv->kbd, X.type, X.xkey.keycode, 0, NULL, block);
            break;
        case ButtonPress:
        case ButtonRelease:
            /* fall-through */
	default:
                                /* Pass the whole event here, because
                                 * this may be an extension event. */
            enqueue(priv->mou, X.type, X.xbutton.button, 0, &X, block);
	    break;
	}
    }
}

/** Called after input events are processed from the DMX queue.  No
 * event processing actually takes place here, but this is a convenient
 * place to update the pointer. */
void dmxBackendProcessInput(pointer private)
{
    GETPRIVFROMPRIVATE;

    DMXDBG6("dmxBackendProcessInput: myScreen=%d relative=%d"
            " last=%d,%d center=%d,%d\n",
            priv->myScreen, priv->relative,
            priv->lastX, priv->lastY,
            priv->centerX, priv->centerY);

    if (priv->relative
        && !dmxInput->console
        && (priv->lastX != priv->centerX || priv->lastY != priv->centerY)) {
        DMXDBG4("   warping pointer from last=%d,%d to center=%d,%d\n",
                priv->lastX, priv->lastY, priv->centerX, priv->centerY);
	priv->lastX   = priv->centerX;
	priv->lastY   = priv->centerY;
        XWarpPointer(priv->display, None, priv->window,
                     0, 0, 0, 0, priv->lastX, priv->lastY);
        dmxSync(&dmxScreens[priv->myScreen], TRUE);
    }
}

static void dmxBackendComputeCenter(myPrivate *priv)
{
    int centerX;
    int centerY;
    
    centerX       = priv->be->rootWidth / 2 + priv->be->rootX;
    centerY       = priv->be->rootHeight / 2 + priv->be->rootY;

    if (centerX > priv->be->rootWidth)  centerX = priv->be->rootWidth  - 1;
    if (centerY > priv->be->rootHeight) centerY = priv->be->rootHeight - 1;
    if (centerX < 1)                    centerX = 1;
    if (centerY < 1)                    centerY = 1;

    priv->centerX = centerX;
    priv->centerY = centerY;
}

static DMXScreenInfo *dmxBackendInitPrivate(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    DMXInputInfo      *dmxInput = &dmxInputs[dmxLocal->inputIdx];
    DMXScreenInfo     *dmxScreen;
    int               i;

    /* Fill in myPrivate */
    for (i = 0,dmxScreen = &dmxScreens[0]; i<dmxNumScreens; i++,dmxScreen++) {
        if (dmxPropertySameDisplay(dmxScreen, dmxInput->name)) {
            priv->display  = dmxScreen->beDisplay;
            priv->window   = dmxScreen->scrnWin;
            priv->be       = dmxScreen;
            break;
        }
    }

    if (i >= dmxNumScreens)
        dmxLog(dmxFatal,
               "%s is not an existing backend display - cannot initialize\n",
               dmxInput->name);

    return dmxScreen;
}

/** Re-initialized the backend device described by \a pDev (after a
 * reconfig). */
void dmxBackendLateReInit(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    int               x, y;

    DMXDBG1("dmxBackendLateReInit miPointerCurrentScreen() = %p\n",
            miPointerCurrentScreen());

    dmxBackendSameDisplay(NULL, 0); /* Invalidate cache */
    dmxBackendInitPrivate(pDev);
    dmxBackendComputeCenter(priv);
    dmxGetGlobalPosition(&x, &y);
    dmxInvalidateGlobalPosition(); /* To force event processing */
    dmxBackendUpdatePosition(priv, x, y);
}

/** Initialized the backend device described by \a pDev. */
void dmxBackendInit(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    DMXScreenInfo     *dmxScreen;

    dmxBackendSameDisplay(NULL, 0); /* Invalidate cache */

    if (dmxLocal->type == DMX_LOCAL_MOUSE)    priv->mou = pDev;
    if (dmxLocal->type == DMX_LOCAL_KEYBOARD) priv->kbd = pDev;
    if (priv->initialized++) return; /* Only do once for mouse/keyboard pair */

    dmxScreen = dmxBackendInitPrivate(pDev);

    /* Finish initialization using computed values or constants. */
    dmxBackendComputeCenter(priv);
    priv->eventMask          = (EnterWindowMask|LeaveWindowMask);
    priv->myScreen           = dmxScreen->index;
    priv->lastX              = priv->centerX;
    priv->lastY              = priv->centerY;
    priv->relative           = 0;
    priv->newscreen          = 0;
}

/** Get information about the backend pointer (for initialization). */
void dmxBackendMouGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
{
    const DMXScreenInfo *dmxScreen = dmxBackendInitPrivate(pDev);

    info->buttonClass      = 1;
    dmxCommonMouGetMap(pDev, info->map, &info->numButtons);
    info->valuatorClass    = 1;
    info->numRelAxes       = 2;
    info->minval[0]        = 0;
    info->minval[1]        = 0;
    info->maxval[0]        = dmxScreen->beWidth;
    info->maxval[1]        = dmxScreen->beHeight;
    info->res[0]           = 1;
    info->minres[0]        = 0;
    info->maxres[0]        = 1;
    info->ptrFeedbackClass = 1;
}

/** Get information about the backend keyboard (for initialization). */
void dmxBackendKbdGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
{
    dmxCommonKbdGetInfo(pDev, info);
    info->keyboard         = 1;
    info->keyClass         = 1;
    dmxCommonKbdGetMap(pDev, &info->keySyms, info->modMap);
    info->freemap          = 1;
    info->focusClass       = 1;
    info->kbdFeedbackClass = 1;
}

/** Process #DMXFunctionType functions.  The only function handled here
 * is to acknowledge a pending server shutdown. */
int dmxBackendFunctions(pointer private, DMXFunctionType function)
{
    switch (function) {
    case DMX_FUNCTION_TERMINATE:
        return 1;
    default:
        return 0;
    }
}