/*
 * callback.c --
 *
 * Copyright (c) 1994 Software Research Associates, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  Software Research
 * Associates makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 */

#include "xlibInt.h" 

static HHOOK hNextHook;

static void CreateEnterNotify(HWND, int, int, int);
static void CreateLeaveNotify(HWND, int, int, int);

/*
 *----------------------------------------------------------------------
 *
 * EnterLeave --
 *
 *	This routine generates EnterNotify and LeaveNotify events.
 *	Normally, it is called when the mouse has been moved, but it
 *	is also called when a window moves to check if the window has
 *	moved from being under the cursor to outside the cursor.
 *
 * Return:
 *	None
 *
 * Side Effects:
 *	X events may be added to the X event queue
 *
 *----------------------------------------------------------------------
 */
void
EnterLeave(HWND hcurwnd, int x, int y, int windowMoved)
{
    DWORD mytask = GetCurrentThreadId();
    RECT rect;
    HWND hgrab, hwnd;
    int nmWin;
    char className[256];
    static HWND hprevwnd = 0;
    static int prev_x = 0;
    static int prev_y = 0;
    static int szHwndChain = 0;
    static HWND *hwndChain = NULL;

    if (windowMoved) {
	if (hcurwnd != hprevwnd) {
	    return;
	}
	hwnd = XWindowFromPoint(XOpenDisplay(NULL), prev_y, prev_x);
	if (hwnd == hprevwnd) {
	    return;
	}
	hcurwnd = hwnd;
	x = prev_x;
	y = prev_y;
    }

    if (prev_x == x && prev_y == y && hcurwnd == hprevwnd) {
	return;
    }


    hgrab = GetCapture();
    if (hgrab) {
	/* Take care of the case where a button is pressed in a window and the
	 * mouse is moved outside the window (while the mouse is captured)
	 */
	GetWindowRect(hgrab, &rect);
	if (hprevwnd != hgrab) {
	    if (x >= rect.left && x <= rect.right &&
		y >= rect.top && y <= rect.bottom)
	    {
		CreateEnterNotify(hgrab, x, y, NotifyNonlinear);
		hcurwnd = hgrab;
	    } else {
		hcurwnd = hprevwnd;
	    }
	} else if (hprevwnd == hcurwnd) {
	    if (x < rect.left || x > rect.right ||
		y < rect.top || y > rect.bottom)
	    {
		if (GetWindowThreadProcessId(hprevwnd, NULL) == mytask) {
		    CreateLeaveNotify(hprevwnd, prev_x, prev_y, NotifyNonlinear);
		    hcurwnd = GetDesktopWindow();
		}
	    }
	}
	goto elfinish;
    }

    if (hprevwnd == hcurwnd) {
	goto elfinish;
    }

    if (hprevwnd && GetWindowThreadProcessId(hprevwnd, NULL) == mytask &&
	hprevwnd != GetDesktopWindow()) {
    	if (GetWindowThreadProcessId(hcurwnd, NULL) != mytask ||
	    hcurwnd == GetDesktopWindow()) {
	    CreateLeaveNotify(hprevwnd, prev_x, prev_y, NotifyNonlinear);
	} else {
	    HWND hwnd1, hwnd2;
	    int nomem = 0;

	    for (nmWin = 0, hwnd1 = hcurwnd;
		 hwnd1 != HWND_DESKTOP && hwnd1 != hprevwnd; 
	   	    hwnd1 = GetParent(hwnd1))
	    {
		if (nomem) {
		    continue;
		}
		GetClassName(hwnd1, className, sizeof(className));
		if (strcmp(className, "TopLevel") == 0) {
		    hwnd1 = HWND_DESKTOP;
		    break;
		}
		if (nmWin >= szHwndChain) {
		    HWND *newChain;
		    int szNewChain;

		    szNewChain = szHwndChain + 20;
		    newChain = (HWND *) malloc(sizeof(HWND) * szNewChain);
		    if (newChain == NULL) {
			nomem = 1;
		    } else {
			if (hwndChain) {
			    memcpy(newChain, hwndChain, szHwndChain * sizeof(HWND));
			    free(hwndChain);
			}
			szHwndChain = szNewChain;
			hwndChain = newChain;
		    }
		}
		hwndChain[nmWin] = hwnd1;
		nmWin++;
	    }

	    for (hwnd2 = hprevwnd; hwnd2 != HWND_DESKTOP && hwnd2 != hcurwnd;
		 hwnd2 = GetParent(hwnd2))
	    {
		GetClassName(hwnd2, className, sizeof(className));
		if (strcmp(className, "TopLevel") == 0) {
		    hwnd2 = HWND_DESKTOP;
		    break;
		}
	    }

	    if (hwnd1 == HWND_DESKTOP && hwnd2 == HWND_DESKTOP) {
		/* This is a crossing between two non-ancestorial windows.
		 * We still need to generate the NotifyNonlinearVirtual
		 * Leave and Enter events
		 */
		HWND hwndCommon = NULL;
		int i, j;

		/* Find a common ancestor */
		for (hwnd2 = hprevwnd;
		     hwnd2 != HWND_DESKTOP && hwnd2 != hcurwnd;
		     hwnd2 = GetParent(hwnd2))
		{
		    for (i = 0; i < nmWin; i++) {
			if (hwndChain[i] == hwnd2) {
			    hwndCommon = hwndChain[i];
			    break;
			}
		    }
		    if (hwndCommon) {
			break;
		    }
		}
	    	CreateLeaveNotify(hprevwnd, prev_x, prev_y, NotifyNonlinear);
		if (hwndCommon) {
		    /* Found a common ancestor.  Now we need to generate all
		     * the NotifyNonlinearVirtual event types
		     */
		    for (hwnd2 = GetParent(hprevwnd);
			 hwnd2 != hwndCommon;
			 hwnd2 = GetParent(hwnd2))
		    {
			CreateLeaveNotify(hwnd2, x, y, NotifyNonlinearVirtual);
		    }
		    for (j = i - 1; j > 0; j--) {
			CreateEnterNotify(hwndChain[j], x, y, NotifyNonlinearVirtual);
		    }
		}
		CreateEnterNotify(hcurwnd, x, y, NotifyNonlinear);
            } else {
		/* This is a crossing between two related windows.
		 * Normally, this means that they is only one type of
		 * notification: Enter or Leave, not both.  However, in
		 * the case of a toplevel in the tree somewhere,*/
		if (hwnd1 == HWND_DESKTOP) {
		    /* hcurwnd is the superior */
		    CreateLeaveNotify(hprevwnd, x, y, NotifyInferior);
		    for (hwnd2 = hprevwnd; hwnd2 != hcurwnd;
			 hwnd2 = GetParent(hwnd2))
		    {
			CreateLeaveNotify(hwnd2, x, y, NotifyVirtual);
		    }
		} else {
		    /* hprevwnd is the superior */
		    CreateEnterNotify(hcurwnd, x, y, NotifyAncestor);
		    for (hwnd1 = hcurwnd; hwnd1 != hprevwnd;
			 hwnd1 = GetParent(hwnd1))
		    {
			CreateEnterNotify(hwnd1, x, y, NotifyVirtual);
		    }
		}
	    }
	}
    } else {
    	if (GetWindowThreadProcessId(hcurwnd, NULL) == mytask) { 	    
	    CreateEnterNotify(hcurwnd, x, y, NotifyInferior);
	}
    }

elfinish:
    hprevwnd = hcurwnd;
    prev_x = x;
    prev_y = y;

    return;
}


/*
 *----------------------------------------------------------------------
 *
 * DeliverEnterLeave --
 * 
 * 	This is a callback for SetWindowsHookEx(WH_MOUSE, ...).
 *
 *----------------------------------------------------------------------
 */

LRESULT CALLBACK
DeliverEnterLeave(int code, WPARAM wParam, LPARAM lParam)
{
    MOUSEHOOKSTRUCT * lpmhs = (MOUSEHOOKSTRUCT *)lParam;
    POINT pt = lpmhs->pt;
    HWND hcurwnd = lpmhs->hwnd;

    if (!(code < 0 || (wParam != WM_MOUSEMOVE && wParam != WM_NCMOUSEMOVE))) {
	EnterLeave(hcurwnd, pt.x, pt.y, 0 /* windowMoved */);
    }

    return CallNextHookEx(hNextHook, code, wParam, lParam);
}

/*
 *----------------------------------------------------------------------
 *
 * SetHookDeliverEL --
 *
 *	Sets the next hook to be called after the Windows hook 
 *	for WH_MOUSE events has been installed.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
void
SetHookDeliverEL(HHOOK handle)
{
    hNextHook = handle;
}

/*
 *----------------------------------------------------------------------
 *
 * CreateEnterNotify -- 
 *
 *	Actually create an EnterNotify event and add it to the
 *	X event queue.  Post a message to make sure that the
 *	GetMessage() call will know that something is waiting
 *	in the X event queue.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
static void
CreateEnterNotify(HWND hwnd, int x, int y, int detail)
{
    XEvent e;
    POINT sPt;
    Display *display;

    e.type = EnterNotify;
    e.xany.window = (Window) hwnd;
    e.xcrossing.x = x;
    e.xcrossing.y = y;
    sPt.x = x;
    sPt.y = y;
    ScreenToClient(hwnd, &sPt);
    e.xcrossing.x_root = sPt.x;
    e.xcrossing.y_root = sPt.y; 
    e.xcrossing.detail = detail;
    // e.xcrossing.mode = (keyboardGrabbed) ? NotifyGrab : NotifyNormal; ;
    e.xcrossing.mode = 0;
    e.xcrossing.focus = 0;	
    e.xcrossing.state = msgGetState(0);
    display = XOpenDisplay(NULL);
    putEvent(display, &e);
    PostMessage(hwnd, EVENTADDED, 0, 0L);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateLeaveNotify -- 
 *
 *	Actually create an EnterLeave event and add it to the
 *	X event queue.  Post a message to make sure that the
 *	GetMessage() call will know that something is waiting
 *	in the X event queue.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
static void
CreateLeaveNotify(HWND hwnd, int x, int y, int detail)
{
    POINT sPt;
    XEvent e;
    Display *display;

    e.type = LeaveNotify;
    e.xany.window = (Window) hwnd;
    e.xcrossing.x = x;
    e.xcrossing.y = y;
    sPt.x = x;
    sPt.y = y;
    ScreenToClient(hwnd, &sPt);
    e.xcrossing.x_root = sPt.x;
    e.xcrossing.y_root = sPt.y; 
    e.xcrossing.detail = detail;
    // e.xcrossing.mode = (keyboardGrabbed) ? NotifyGrab : NotifyNormal; ;
    e.xcrossing.mode = 0;
    e.xcrossing.focus = 0;
    e.xcrossing.state = msgGetState(0);
    display = XOpenDisplay(NULL);
    putEvent(display, &e);
    PostMessage(hwnd, EVENTADDED, 0, 0L);
}
