/*
 * 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;
    }
}
