/*
 * Copyright © 2005 Novell, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, 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
 * Novell, Inc. not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * Novell, Inc. makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL NOVELL, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <X11/Xatom.h>
#include <X11/cursorfont.h>

#include <compiz-core.h>

static CompMetadata resizeMetadata;

#define ResizeUpMask    (1L << 0)
#define ResizeDownMask  (1L << 1)
#define ResizeLeftMask  (1L << 2)
#define ResizeRightMask (1L << 3)

#define RESIZE_MODE_NORMAL    0
#define RESIZE_MODE_OUTLINE   1
#define RESIZE_MODE_RECTANGLE 2
#define RESIZE_MODE_STRETCH   3
#define RESIZE_MODE_LAST      RESIZE_MODE_STRETCH

struct _ResizeKeys {
    char	 *name;
    int		 dx;
    int		 dy;
    unsigned int warpMask;
    unsigned int resizeMask;
} rKeys[] = {
    { "Left",  -1,  0, ResizeLeftMask | ResizeRightMask, ResizeLeftMask },
    { "Right",  1,  0, ResizeLeftMask | ResizeRightMask, ResizeRightMask },
    { "Up",     0, -1, ResizeUpMask | ResizeDownMask,    ResizeUpMask },
    { "Down",   0,  1, ResizeUpMask | ResizeDownMask,    ResizeDownMask }
};

#define NUM_KEYS (sizeof (rKeys) / sizeof (rKeys[0]))

#define MIN_KEY_WIDTH_INC  24
#define MIN_KEY_HEIGHT_INC 24

#define RESIZE_DISPLAY_OPTION_INITIATE_NORMAL_KEY    0
#define RESIZE_DISPLAY_OPTION_INITIATE_OUTLINE_KEY   1
#define RESIZE_DISPLAY_OPTION_INITIATE_RECTANGLE_KEY 2
#define RESIZE_DISPLAY_OPTION_INITIATE_STRETCH_KEY   3
#define RESIZE_DISPLAY_OPTION_INITIATE_BUTTON	     4
#define RESIZE_DISPLAY_OPTION_INITIATE_KEY	     5
#define RESIZE_DISPLAY_OPTION_MODE	             6
#define RESIZE_DISPLAY_OPTION_BORDER_COLOR           7
#define RESIZE_DISPLAY_OPTION_FILL_COLOR             8
#define RESIZE_DISPLAY_OPTION_NORMAL_MATCH	     9
#define RESIZE_DISPLAY_OPTION_OUTLINE_MATCH	     10
#define RESIZE_DISPLAY_OPTION_RECTANGLE_MATCH	     11
#define RESIZE_DISPLAY_OPTION_STRETCH_MATCH	     12
#define RESIZE_DISPLAY_OPTION_NUM		     13

static int displayPrivateIndex;

typedef struct _ResizeDisplay {
    CompOption opt[RESIZE_DISPLAY_OPTION_NUM];

    int		    screenPrivateIndex;
    HandleEventProc handleEvent;

    Atom resizeNotifyAtom;
    Atom resizeInformationAtom;

    CompWindow	 *w;
    int		 mode;
    XRectangle	 savedGeometry;
    XRectangle	 geometry;

    int		 releaseButton;
    unsigned int mask;
    int		 pointerDx;
    int		 pointerDy;
    KeyCode	 key[NUM_KEYS];

    Bool offWorkAreaConstrained;
} ResizeDisplay;

typedef struct _ResizeScreen {
    int grabIndex;

    WindowResizeNotifyProc windowResizeNotify;
    PaintOutputProc	   paintOutput;
    PaintWindowProc	   paintWindow;
    DamageWindowRectProc   damageWindowRect;

    Cursor leftCursor;
    Cursor rightCursor;
    Cursor upCursor;
    Cursor upLeftCursor;
    Cursor upRightCursor;
    Cursor downCursor;
    Cursor downLeftCursor;
    Cursor downRightCursor;
    Cursor middleCursor;
    Cursor cursor[NUM_KEYS];

    const XRectangle *grabWindowWorkArea;
} ResizeScreen;

#define GET_RESIZE_DISPLAY(d)					    \
    ((ResizeDisplay *) (d)->base.privates[displayPrivateIndex].ptr)

#define RESIZE_DISPLAY(d)		       \
    ResizeDisplay *rd = GET_RESIZE_DISPLAY (d)

#define GET_RESIZE_SCREEN(s, rd)					\
    ((ResizeScreen *) (s)->base.privates[(rd)->screenPrivateIndex].ptr)

#define RESIZE_SCREEN(s)						      \
    ResizeScreen *rs = GET_RESIZE_SCREEN (s, GET_RESIZE_DISPLAY (s->display))

#define NUM_OPTIONS(d) (sizeof ((d)->opt) / sizeof (CompOption))

static void
resizeGetPaintRectangle (CompDisplay *d,
			 BoxPtr      pBox)
{
    RESIZE_DISPLAY (d);

    pBox->x1 = rd->geometry.x - rd->w->input.left;
    pBox->y1 = rd->geometry.y - rd->w->input.top;
    pBox->x2 = rd->geometry.x +
	rd->geometry.width + rd->w->serverBorderWidth * 2 +
	rd->w->input.right;

    if (rd->w->shaded)
    {
	pBox->y2 = rd->geometry.y + rd->w->height + rd->w->input.bottom;
    }
    else
    {
	pBox->y2 = rd->geometry.y +
	    rd->geometry.height + rd->w->serverBorderWidth * 2 +
	    rd->w->input.bottom;
    }
}

static void
resizeGetStretchScale (CompWindow *w,
		       BoxPtr     pBox,
		       float	  *xScale,
		       float	  *yScale)
{
    int width, height;

    width  = w->width  + w->input.left + w->input.right;
    height = w->height + w->input.top  + w->input.bottom;

    *xScale = (width)  ? (pBox->x2 - pBox->x1) / (float) width  : 1.0f;
    *yScale = (height) ? (pBox->y2 - pBox->y1) / (float) height : 1.0f;
}

static void
resizeGetStretchRectangle (CompDisplay *d,
			   BoxPtr      pBox)
{
    BoxRec box;
    float  xScale, yScale;

    RESIZE_DISPLAY (d);

    resizeGetPaintRectangle (d, &box);
    resizeGetStretchScale (rd->w, &box, &xScale, &yScale);

    pBox->x1 = box.x1 - (rd->w->output.left - rd->w->input.left) * xScale;
    pBox->y1 = box.y1 - (rd->w->output.top - rd->w->input.top) * yScale;
    pBox->x2 = box.x2 + rd->w->output.right * xScale;
    pBox->y2 = box.y2 + rd->w->output.bottom * yScale;
}

static void
resizeDamageRectangle (CompScreen *s,
		       BoxPtr     pBox)
{
    REGION reg;

    reg.rects    = &reg.extents;
    reg.numRects = 1;

    reg.extents = *pBox;

    reg.extents.x1 -= 1;
    reg.extents.y1 -= 1;
    reg.extents.x2 += 1;
    reg.extents.y2 += 1;

    damageScreenRegion (s, &reg);
}

static Cursor
resizeCursorFromResizeMask (CompScreen   *s,
			    unsigned int mask)
{
    Cursor cursor;

    RESIZE_SCREEN (s);

    if (mask & ResizeLeftMask)
    {
	if (mask & ResizeDownMask)
	    cursor = rs->downLeftCursor;
	else if (mask & ResizeUpMask)
	    cursor = rs->upLeftCursor;
	else
	    cursor = rs->leftCursor;
    }
    else if (mask & ResizeRightMask)
    {
	if (mask & ResizeDownMask)
	    cursor = rs->downRightCursor;
	else if (mask & ResizeUpMask)
	    cursor = rs->upRightCursor;
	else
	    cursor = rs->rightCursor;
    }
    else if (mask & ResizeUpMask)
    {
	cursor = rs->upCursor;
    }
    else
    {
	cursor = rs->downCursor;
    }

    return cursor;
}

static void
resizeSendResizeNotify (CompDisplay *d)
{
    XEvent xev;

    RESIZE_DISPLAY (d);
    xev.xclient.type    = ClientMessage;
    xev.xclient.display = d->display;
    xev.xclient.format  = 32;

    xev.xclient.message_type = rd->resizeNotifyAtom;
    xev.xclient.window	     = rd->w->id;

    xev.xclient.data.l[0] = rd->geometry.x;
    xev.xclient.data.l[1] = rd->geometry.y;
    xev.xclient.data.l[2] = rd->geometry.width;
    xev.xclient.data.l[3] = rd->geometry.height;
    xev.xclient.data.l[4] = 0;

    XSendEvent (d->display,
		rd->w->screen->root,
		FALSE,
		SubstructureRedirectMask | SubstructureNotifyMask,
		&xev);
}

static void
resizeUpdateWindowProperty (CompDisplay *d)
{
    unsigned long data[4];

    RESIZE_DISPLAY (d);

    data[0] = rd->geometry.x;
    data[1] = rd->geometry.y;
    data[2] = rd->geometry.width;
    data[3] = rd->geometry.height;

    XChangeProperty (d->display, rd->w->id,
		     rd->resizeInformationAtom,
		     XA_CARDINAL, 32, PropModeReplace,
		     (unsigned char*) data, 4);
}

static void
resizeFinishResizing (CompDisplay *d)
{
    RESIZE_DISPLAY (d);

    (*rd->w->screen->windowUngrabNotify) (rd->w);

    XDeleteProperty (d->display,
		     rd->w->id,
		     rd->resizeInformationAtom);

    rd->w = NULL;
}

static Bool
resizeInitiate (CompDisplay     *d,
		CompAction      *action,
		CompActionState state,
		CompOption      *option,
		int	        nOption)
{
    CompWindow *w;
    Window     xid;

    RESIZE_DISPLAY (d);

    xid = getIntOptionNamed (option, nOption, "window", 0);

    w = findWindowAtDisplay (d, xid);
    if (w && (w->actions & CompWindowActionResizeMask))
    {
	unsigned int mask;
	int          x, y;
	int	     button;
	int	     i;

	RESIZE_SCREEN (w->screen);

	x = getIntOptionNamed (option, nOption, "x", pointerX);
	y = getIntOptionNamed (option, nOption, "y", pointerY);

	button = getIntOptionNamed (option, nOption, "button", -1);

	mask = getIntOptionNamed (option, nOption, "direction", 0);

	/* Initiate the resize in the direction suggested by the
	 * sector of the window the mouse is in, eg drag in top left
	 * will resize up and to the left.  Keyboard resize starts out
	 * with the cursor in the middle of the window and then starts
	 * resizing the edge corresponding to the next key press. */
	if (state & CompActionStateInitKey)
	{
	    mask = 0;
	}
	else if (!mask)
	{
	    int sectorSizeX = w->serverWidth / 3;
	    int sectorSizeY = w->serverHeight / 3;
	    int posX        = x - w->serverX;
	    int posY        = y - w->serverY;

	    if (posX < sectorSizeX)
		mask |= ResizeLeftMask;
	    else if (posX > (2 * sectorSizeX))
		mask |= ResizeRightMask;

	    if (posY < sectorSizeY)
		mask |= ResizeUpMask;
	    else if (posY > (2 * sectorSizeY))
		mask |= ResizeDownMask;

	    /* if the pointer was in the middle of the window,
	       just prevent input to the window */
	    if (!mask)
		return TRUE;
	}

	if (otherScreenGrabExist (w->screen, "resize", NULL))
	    return FALSE;

	if (rd->w)
	    return FALSE;

	if (w->type & (CompWindowTypeDesktopMask |
		       CompWindowTypeDockMask	 |
		       CompWindowTypeFullscreenMask))
	    return FALSE;

	if (w->attrib.override_redirect)
	    return FALSE;

	if (state & CompActionStateInitButton)
	    action->state |= CompActionStateTermButton;

	if (w->shaded)
	    mask &= ~(ResizeUpMask | ResizeDownMask);

	rd->w	 = w;
	rd->mask = mask;

	rd->savedGeometry.x	 = w->serverX;
	rd->savedGeometry.y	 = w->serverY;
	rd->savedGeometry.width  = w->serverWidth;
	rd->savedGeometry.height = w->serverHeight;

	rd->geometry = rd->savedGeometry;

	rd->pointerDx = x - pointerX;
	rd->pointerDy = y - pointerY;

	if ((w->state & MAXIMIZE_STATE) == MAXIMIZE_STATE)
	{
	    /* if the window is fully maximized, showing the outline or
	       rectangle would be visually distracting as the window can't
	       be resized anyway; so we better don't use them in this case */
	    rd->mode = RESIZE_MODE_NORMAL;
	}
	else
	{
	    rd->mode = rd->opt[RESIZE_DISPLAY_OPTION_MODE].value.i;
	    for (i = 0; i <= RESIZE_MODE_LAST; i++)
	    {
		if (action == &rd->opt[i].value.action)
		{
		    rd->mode = i;
		    break;
		}
	    }

	    if (i > RESIZE_MODE_LAST)
	    {
		int index;

		for (i = 0; i <= RESIZE_MODE_LAST; i++)
		{
		    index = RESIZE_DISPLAY_OPTION_NORMAL_MATCH + i;
		    if (matchEval (&rd->opt[index].value.match, w))
		    {
			rd->mode = i;
			break;
		    }
		}
	    }
	}

	if (!rs->grabIndex)
	{
	    Cursor cursor;

	    if (state & CompActionStateInitKey)
	    {
		cursor = rs->middleCursor;
	    }
	    else
	    {
		cursor = resizeCursorFromResizeMask (w->screen, mask);
	    }

	    rs->grabIndex = pushScreenGrab (w->screen, cursor, "resize");
	}

	if (rs->grabIndex)
	{
	    unsigned int grabMask = CompWindowGrabResizeMask |
				    CompWindowGrabButtonMask;
	    Bool sourceExternalApp = getBoolOptionNamed (option, nOption,
							 "external", FALSE);

	    if (sourceExternalApp)
		grabMask |= CompWindowGrabExternalAppMask;

	    BoxRec box;

	    rd->releaseButton = button;

	    (w->screen->windowGrabNotify) (w, x, y, state, grabMask);

	    if (d->opt[COMP_DISPLAY_OPTION_RAISE_ON_CLICK].value.b)
		updateWindowAttributes (w,
					CompStackingUpdateModeAboveFullscreen);

	    /* using the paint rectangle is enough here
	       as we don't have any stretch yet */
	    resizeGetPaintRectangle (d, &box);
	    resizeDamageRectangle (w->screen, &box);

	    if (state & CompActionStateInitKey)
	    {
		int xRoot, yRoot;

		xRoot = w->serverX + (w->serverWidth  / 2);
		yRoot = w->serverY + (w->serverHeight / 2);

		warpPointer (w->screen, xRoot - pointerX, yRoot - pointerY);
	    }

	    /* Update offWorkAreaConstrained and workArea at grab time */
	    rd->offWorkAreaConstrained = FALSE;
	    if (sourceExternalApp)
	    {
		int output = outputDeviceForWindow (w);

		rs->grabWindowWorkArea = &w->screen->outputDev[output].workArea;

		/* Prevent resizing beyond work area edges when resize is
		   initiated externally (e.g. with window frame or menu)
		   and not with a key (e.g. alt+button) */
		rd->offWorkAreaConstrained = TRUE;
	    }
	}
    }

    return FALSE;
}

static Bool
resizeTerminate (CompDisplay	 *d,
		 CompAction	 *action,
		 CompActionState state,
		 CompOption	 *option,
		 int		 nOption)
{
    RESIZE_DISPLAY (d);

    if (rd->w)
    {
	CompWindow     *w = rd->w;
	XWindowChanges xwc;
	unsigned int   mask = 0;

	RESIZE_SCREEN (w->screen);

	if (rd->mode == RESIZE_MODE_NORMAL)
	{
	    if (state & CompActionStateCancel)
	    {
		xwc.x      = rd->savedGeometry.x;
		xwc.y      = rd->savedGeometry.y;
		xwc.width  = rd->savedGeometry.width;
		xwc.height = rd->savedGeometry.height;

		mask = CWX | CWY | CWWidth | CWHeight;
	    }
	}
	else
	{
	    XRectangle geometry;

	    if (state & CompActionStateCancel)
		geometry = rd->savedGeometry;
	    else
		geometry = rd->geometry;

	    if (memcmp (&geometry, &rd->savedGeometry, sizeof (geometry)) == 0)
	    {
		BoxRec box;

		if (rd->mode == RESIZE_MODE_STRETCH)
		    resizeGetStretchRectangle (d, &box);
		else
		    resizeGetPaintRectangle (d, &box);

		resizeDamageRectangle (w->screen, &box);
	    }
	    else
	    {
		xwc.x      = geometry.x;
		xwc.y      = geometry.y;
		xwc.width  = geometry.width;
		xwc.height = geometry.height;

		mask = CWX | CWY | CWWidth | CWHeight;
	    }
	}

	if ((mask & CWWidth) && xwc.width == w->serverWidth)
	    mask &= ~CWWidth;

	if ((mask & CWHeight) && xwc.height == w->serverHeight)
	    mask &= ~CWHeight;

	if (mask)
	{
	    if (mask & (CWWidth | CWHeight))
		sendSyncRequest (w);

	    configureXWindow (w, mask, &xwc);
	}

	if (!(mask & (CWWidth | CWHeight)))
	    resizeFinishResizing (d);

	if (rs->grabIndex)
	{
	    removeScreenGrab (w->screen, rs->grabIndex, NULL);
	    rs->grabIndex = 0;
	}

	rd->releaseButton = 0;
    }

    action->state &= ~(CompActionStateTermKey | CompActionStateTermButton);

    return FALSE;
}

static void
resizeUpdateWindowSize (CompDisplay *d)
{
    RESIZE_DISPLAY (d);

    if (rd->w->syncWait)
	return;

    if (rd->w->serverWidth  != rd->geometry.width ||
	rd->w->serverHeight != rd->geometry.height)
    {
	XWindowChanges xwc;

	xwc.x	   = rd->geometry.x;
	xwc.y	   = rd->geometry.y;
	xwc.width  = rd->geometry.width;
	xwc.height = rd->geometry.height;

	sendSyncRequest (rd->w);

	configureXWindow (rd->w,
			  CWX | CWY | CWWidth | CWHeight,
			  &xwc);
    }
}

static void
resizeHandleKeyEvent (CompScreen *s,
		      KeyCode	 keycode)
{
    RESIZE_SCREEN (s);
    RESIZE_DISPLAY (s->display);

    if (rs->grabIndex && rd->w)
    {
	CompWindow *w = rd->w;
	int	   widthInc, heightInc, i;

	widthInc  = w->sizeHints.width_inc;
	heightInc = w->sizeHints.height_inc;

	if (widthInc < MIN_KEY_WIDTH_INC)
	    widthInc = MIN_KEY_WIDTH_INC;

	if (heightInc < MIN_KEY_HEIGHT_INC)
	    heightInc = MIN_KEY_HEIGHT_INC;

	for (i = 0; i < NUM_KEYS; i++)
	{
	    if (keycode != rd->key[i])
		continue;

	    if (rd->mask & rKeys[i].warpMask)
	    {
		XWarpPointer (s->display->display, None, None, 0, 0, 0, 0,
			      rKeys[i].dx * widthInc,
			      rKeys[i].dy * heightInc);
	    }
	    else
	    {
		int x, y, left, top, width, height;

		left   = w->serverX - w->input.left;
		top    = w->serverY - w->input.top;
		width  = w->input.left + w->serverWidth  + w->input.right;
		height = w->input.top  + w->serverHeight + w->input.bottom;

		x = left + width  * (rKeys[i].dx + 1) / 2;
		y = top  + height * (rKeys[i].dy + 1) / 2;

		warpPointer (s, x - pointerX, y - pointerY);

		rd->mask = rKeys[i].resizeMask;

		updateScreenGrab (s, rs->grabIndex, rs->cursor[i]);
	    }
	    break;
	}
    }
}

static void
resizeHandleMotionEvent (CompScreen *s,
			 int	    xRoot,
			 int	    yRoot)
{
    RESIZE_SCREEN (s);

    if (rs->grabIndex)
    {
	BoxRec box;
	int    w, h;

	RESIZE_DISPLAY (s->display);

	w = rd->savedGeometry.width;
	h = rd->savedGeometry.height;

	if (!rd->mask)
	{
	    CompWindow *w = rd->w;
	    int        xDist, yDist;
	    int        minPointerOffsetX, minPointerOffsetY;

	    xDist = xRoot - (w->serverX + (w->serverWidth / 2));
	    yDist = yRoot - (w->serverY + (w->serverHeight / 2));

	    /* decision threshold is 10% of window size */
	    minPointerOffsetX = MIN (20, w->serverWidth / 10);
	    minPointerOffsetY = MIN (20, w->serverHeight / 10);

	    /* if we reached the threshold in one direction,
	       make the threshold in the other direction smaller
	       so there is a chance that this threshold also can
	       be reached (by diagonal movement) */
	    if (abs (xDist) > minPointerOffsetX)
		minPointerOffsetY /= 2;
	    else if (abs (yDist) > minPointerOffsetY)
		minPointerOffsetX /= 2;

	    if (abs (xDist) > minPointerOffsetX)
	    {
		if (xDist > 0)
		    rd->mask |= ResizeRightMask;
		else
		    rd->mask |= ResizeLeftMask;
	    }

	    if (abs (yDist) > minPointerOffsetY)
	    {
		if (yDist > 0)
		    rd->mask |= ResizeDownMask;
		else
		    rd->mask |= ResizeUpMask;
	    }

	    /* if the pointer movement was enough to determine a
	       direction, warp the pointer to the appropriate edge
	       and set the right cursor */
	    if (rd->mask)
	    {
		Cursor     cursor;
		CompScreen *s = rd->w->screen;
		CompAction *action;
		int        pointerAdjustX = 0;
		int        pointerAdjustY = 0;
		int	   option = RESIZE_DISPLAY_OPTION_INITIATE_KEY;

		RESIZE_SCREEN (s);

		action = &rd->opt[option].value.action;
		action->state |= CompActionStateTermButton;

		if (rd->mask & ResizeRightMask)
			pointerAdjustX = w->serverX + w->serverWidth +
					 w->input.right - xRoot;
		else if (rd->mask & ResizeLeftMask)
			pointerAdjustX = w->serverX - w->input.left - xRoot;

		if (rd->mask & ResizeDownMask)
			pointerAdjustY = w->serverY + w->serverHeight +
					 w->input.bottom - yRoot;
		else if (rd->mask & ResizeUpMask)
			pointerAdjustY = w->serverY - w->input.top - yRoot;

		warpPointer (s, pointerAdjustX, pointerAdjustY);

		cursor = resizeCursorFromResizeMask (s, rd->mask);
		updateScreenGrab (s, rs->grabIndex, cursor);
	    }
	}
	else
	{
	    /* only accumulate pointer movement if a mask is
	       already set as we don't have a use for the
	       difference information otherwise */
	    rd->pointerDx += xRoot - lastPointerX;
	    rd->pointerDy += yRoot - lastPointerY;
	}

	if (rd->mask & ResizeLeftMask)
	    w -= rd->pointerDx;
	else if (rd->mask & ResizeRightMask)
	    w += rd->pointerDx;

	if (rd->mask & ResizeUpMask)
	    h -= rd->pointerDy;
	else if (rd->mask & ResizeDownMask)
	    h += rd->pointerDy;

	if (rd->w->state & CompWindowStateMaximizedVertMask)
	    h = rd->w->serverHeight;

	if (rd->w->state & CompWindowStateMaximizedHorzMask)
	    w = rd->w->serverWidth;

	constrainNewWindowSize (rd->w, w, h, &w, &h);

	/* constrain to work area */
	if (rd->offWorkAreaConstrained)
	{
	    if (rd->mask & ResizeUpMask)
	    {
		int decorTop = rd->savedGeometry.y + rd->savedGeometry.height -
		    (h + rd->w->input.top);

		if (rs->grabWindowWorkArea->y > decorTop)
		    h -= rs->grabWindowWorkArea->y - decorTop;
	    }
	    if (rd->mask & ResizeDownMask)
	    {
		int decorBottom = rd->savedGeometry.y + h + rd->w->input.bottom;

		if (decorBottom >
		    rs->grabWindowWorkArea->y + rs->grabWindowWorkArea->height)
		    h -= decorBottom - (rs->grabWindowWorkArea->y +
					rs->grabWindowWorkArea->height);
	    }
	    if (rd->mask & ResizeLeftMask)
	    {
		int decorLeft = rd->savedGeometry.x + rd->savedGeometry.width -
		    (w + rd->w->input.left);

		if (rs->grabWindowWorkArea->x > decorLeft)
		    w -= rs->grabWindowWorkArea->x - decorLeft;
	    }
	    if (rd->mask & ResizeRightMask)
	    {
		int decorRight = rd->savedGeometry.x + w + rd->w->input.right;

		if (decorRight >
		    rs->grabWindowWorkArea->x + rs->grabWindowWorkArea->width)
		    w -= decorRight - (rs->grabWindowWorkArea->x +
				       rs->grabWindowWorkArea->width);
	    }
	}

	if (rd->mode != RESIZE_MODE_NORMAL)
	{
	    if (rd->mode == RESIZE_MODE_STRETCH)
		resizeGetStretchRectangle (s->display, &box);
	    else
		resizeGetPaintRectangle (s->display, &box);

	    resizeDamageRectangle (s, &box);
	}

	if (rd->mask & ResizeLeftMask)
	    rd->geometry.x -= w - rd->geometry.width;

	if (rd->mask & ResizeUpMask)
	    rd->geometry.y -= h - rd->geometry.height;

	rd->geometry.width  = w;
	rd->geometry.height = h;

	if (rd->mode != RESIZE_MODE_NORMAL)
	{
	    if (rd->mode == RESIZE_MODE_STRETCH)
		resizeGetStretchRectangle (s->display, &box);
	    else
		resizeGetPaintRectangle (s->display, &box);

	    resizeDamageRectangle (s, &box);
	}
	else
	{
	    resizeUpdateWindowSize (s->display);
	}

	resizeUpdateWindowProperty (s->display);
	resizeSendResizeNotify (s->display);
    }
}

static void
resizeHandleEvent (CompDisplay *d,
		   XEvent      *event)
{
    CompScreen *s;

    RESIZE_DISPLAY (d);

    switch (event->type) {
    case KeyPress:
	s = findScreenAtDisplay (d, event->xkey.root);
	if (s)
	    resizeHandleKeyEvent (s, event->xkey.keycode);
	break;
    case ButtonRelease:
	s = findScreenAtDisplay (d, event->xbutton.root);
	if (s)
	{
	    RESIZE_SCREEN (s);

	    if (rs->grabIndex)
	    {
		if (rd->releaseButton     == -1 ||
		    event->xbutton.button == rd->releaseButton)
		{
		    int        opt = RESIZE_DISPLAY_OPTION_INITIATE_BUTTON;
		    CompAction *action = &rd->opt[opt].value.action;

		    resizeTerminate (d, action, CompActionStateTermButton,
				     NULL, 0);
		}
	    }
	}
	break;
    case MotionNotify:
	s = findScreenAtDisplay (d, event->xmotion.root);
	if (s)
	    resizeHandleMotionEvent (s, pointerX, pointerY);
	break;
    case EnterNotify:
    case LeaveNotify:
	s = findScreenAtDisplay (d, event->xcrossing.root);
	if (s)
	    resizeHandleMotionEvent (s, pointerX, pointerY);
	break;
    case ClientMessage:
	if (event->xclient.message_type == d->wmMoveResizeAtom)
	{
	    CompWindow *w;

	    if (event->xclient.data.l[2] <= WmMoveResizeSizeLeft ||
		event->xclient.data.l[2] == WmMoveResizeSizeKeyboard)
	    {
		w = findWindowAtDisplay (d, event->xclient.window);
		if (w)
		{
		    CompOption o[7];
		    int	       option;

		    o[0].type    = CompOptionTypeInt;
		    o[0].name    = "window";
		    o[0].value.i = event->xclient.window;

		    o[1].type    = CompOptionTypeBool;
		    o[1].name    = "external";
		    o[1].value.b = TRUE;

		    if (event->xclient.data.l[2] == WmMoveResizeSizeKeyboard)
		    {
			option = RESIZE_DISPLAY_OPTION_INITIATE_KEY;

			resizeInitiate (d, &rd->opt[option].value.action,
					CompActionStateInitKey,
					o, 2);
		    }
		    else
		    {
			static unsigned int mask[] = {
			    ResizeUpMask | ResizeLeftMask,
			    ResizeUpMask,
			    ResizeUpMask | ResizeRightMask,
			    ResizeRightMask,
			    ResizeDownMask | ResizeRightMask,
			    ResizeDownMask,
			    ResizeDownMask | ResizeLeftMask,
			    ResizeLeftMask,
			};
			unsigned int mods;
			Window	     root, child;
			int	     xRoot, yRoot, i;

			option = RESIZE_DISPLAY_OPTION_INITIATE_BUTTON;

			XQueryPointer (d->display, w->screen->root,
				       &root, &child, &xRoot, &yRoot,
				       &i, &i, &mods);

			/* TODO: not only button 1 */
			if (mods & Button1Mask)
			{
			    o[2].type	 = CompOptionTypeInt;
			    o[2].name	 = "modifiers";
			    o[2].value.i = mods;

			    o[3].type	 = CompOptionTypeInt;
			    o[3].name	 = "x";
			    o[3].value.i = event->xclient.data.l[0];

			    o[4].type	 = CompOptionTypeInt;
			    o[4].name	 = "y";
			    o[4].value.i = event->xclient.data.l[1];

			    o[5].type	 = CompOptionTypeInt;
			    o[5].name	 = "direction";
			    o[5].value.i = mask[event->xclient.data.l[2]];

			    o[6].type	 = CompOptionTypeInt;
			    o[6].name	 = "button";
			    o[6].value.i = event->xclient.data.l[3] ?
				event->xclient.data.l[3] : -1;

			    resizeInitiate (d,
					    &rd->opt[option].value.action,
					    CompActionStateInitButton,
					    o, 7);

			    resizeHandleMotionEvent (w->screen, xRoot, yRoot);
			}
		    }
		}
	    }
	    else if (rd->w && event->xclient.data.l[2] == WmMoveResizeCancel)
	    {
		if (rd->w->id == event->xclient.window)
		{
		    int option;

		    option = RESIZE_DISPLAY_OPTION_INITIATE_BUTTON;
		    resizeTerminate (d, &rd->opt[option].value.action,
				     CompActionStateCancel, NULL, 0);
		    option = RESIZE_DISPLAY_OPTION_INITIATE_KEY;
		    resizeTerminate (d, &rd->opt[option].value.action,
				     CompActionStateCancel, NULL, 0);
		}
	    }
	}
	break;
    case DestroyNotify:
	if (rd->w && rd->w->id == event->xdestroywindow.window)
	{
	    int option;

	    option = RESIZE_DISPLAY_OPTION_INITIATE_BUTTON;
	    resizeTerminate (d, &rd->opt[option].value.action, 0, NULL, 0);
	    option = RESIZE_DISPLAY_OPTION_INITIATE_KEY;
	    resizeTerminate (d, &rd->opt[option].value.action, 0, NULL, 0);
	}
	break;
    case UnmapNotify:
	if (rd->w && rd->w->id == event->xunmap.window)
	{
	    int option;

	    option = RESIZE_DISPLAY_OPTION_INITIATE_BUTTON;
	    resizeTerminate (d, &rd->opt[option].value.action, 0, NULL, 0);
	    option = RESIZE_DISPLAY_OPTION_INITIATE_KEY;
	    resizeTerminate (d, &rd->opt[option].value.action, 0, NULL, 0);
	}
    default:
	break;
    }

    UNWRAP (rd, d, handleEvent);
    (*d->handleEvent) (d, event);
    WRAP (rd, d, handleEvent, resizeHandleEvent);

    if (event->type == d->syncEvent + XSyncAlarmNotify)
    {
	if (rd->w)
	{
	    XSyncAlarmNotifyEvent *sa;

	    sa = (XSyncAlarmNotifyEvent *) event;

	    if (rd->w->syncAlarm == sa->alarm)
		resizeUpdateWindowSize (d);
	}
    }
}

static void
resizeWindowResizeNotify (CompWindow *w,
			  int	     dx,
			  int        dy,
			  int	     dwidth,
			  int	     dheight)
{
    RESIZE_DISPLAY (w->screen->display);
    RESIZE_SCREEN (w->screen);

    UNWRAP (rs, w->screen, windowResizeNotify);
    (*w->screen->windowResizeNotify) (w, dx, dy, dwidth, dheight);
    WRAP (rs, w->screen, windowResizeNotify, resizeWindowResizeNotify);

    if (rd->w == w && !rs->grabIndex)
	resizeFinishResizing (w->screen->display);
}

static void
resizePaintRectangle (CompScreen              *s,
		      const ScreenPaintAttrib *sa,
		      const CompTransform     *transform,
		      CompOutput              *output,
		      unsigned short	      *borderColor,
		      unsigned short	      *fillColor)
{
    BoxRec        box;
    CompTransform sTransform = *transform;

    resizeGetPaintRectangle (s->display, &box);

    glPushMatrix ();

    transformToScreenSpace (s, output, -DEFAULT_Z_CAMERA, &sTransform);

    glLoadMatrixf (sTransform.m);

    glDisableClientState (GL_TEXTURE_COORD_ARRAY);
    glEnable (GL_BLEND);

    /* fill rectangle */
    if (fillColor)
    {
	glColor4usv (fillColor);
	glRecti (box.x1, box.y2, box.x2, box.y1);
    }

    /* draw outline */
    glColor4usv (borderColor);
    glLineWidth (2.0);
    glBegin (GL_LINE_LOOP);
    glVertex2i (box.x1, box.y1);
    glVertex2i (box.x2, box.y1);
    glVertex2i (box.x2, box.y2);
    glVertex2i (box.x1, box.y2);
    glEnd ();

    /* clean up */
    glColor4usv (defaultColor);
    glDisable (GL_BLEND);
    glEnableClientState (GL_TEXTURE_COORD_ARRAY);
    glPopMatrix ();
}

static Bool
resizePaintOutput (CompScreen              *s,
		   const ScreenPaintAttrib *sAttrib,
		   const CompTransform     *transform,
		   Region                  region,
		   CompOutput              *output,
		   unsigned int            mask)
{
    Bool status;

    RESIZE_SCREEN (s);
    RESIZE_DISPLAY (s->display);

    if (rd->w && (s == rd->w->screen))
    {
	if (rd->mode == RESIZE_MODE_STRETCH)
	    mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK;
    }

    UNWRAP (rs, s, paintOutput);
    status = (*s->paintOutput) (s, sAttrib, transform, region, output, mask);
    WRAP (rs, s, paintOutput, resizePaintOutput);

    if (status && rd->w && (s == rd->w->screen))
    {
	unsigned short *border, *fill;

	border = rd->opt[RESIZE_DISPLAY_OPTION_BORDER_COLOR].value.c;
	fill   = rd->opt[RESIZE_DISPLAY_OPTION_FILL_COLOR].value.c;

	switch (rd->mode) {
	case RESIZE_MODE_OUTLINE:
	    resizePaintRectangle (s, sAttrib, transform, output, border, NULL);
	    break;
	case RESIZE_MODE_RECTANGLE:
	    resizePaintRectangle (s, sAttrib, transform, output, border, fill);
	default:
	    break;
	}
    }

    return status;
}

static Bool
resizePaintWindow (CompWindow              *w,
		   const WindowPaintAttrib *attrib,
		   const CompTransform     *transform,
		   Region                  region,
		   unsigned int            mask)
{
    CompScreen *s = w->screen;
    Bool       status;

    RESIZE_SCREEN (s);
    RESIZE_DISPLAY (s->display);

    if (w == rd->w && rd->mode == RESIZE_MODE_STRETCH)
    {
	FragmentAttrib fragment;
	CompTransform  wTransform = *transform;
	BoxRec	       box;
	float	       xOrigin, yOrigin;
	float	       xScale, yScale;

	if (mask & PAINT_WINDOW_OCCLUSION_DETECTION_MASK)
	    return FALSE;

	UNWRAP (rs, s, paintWindow);
	status = (*s->paintWindow) (w, attrib, transform, region,
				    mask | PAINT_WINDOW_NO_CORE_INSTANCE_MASK);
	WRAP (rs, s, paintWindow, resizePaintWindow);

	initFragmentAttrib (&fragment, &w->lastPaint);

	if (w->alpha || fragment.opacity != OPAQUE)
	    mask |= PAINT_WINDOW_TRANSLUCENT_MASK;

	resizeGetPaintRectangle (s->display, &box);
	resizeGetStretchScale (w, &box, &xScale, &yScale);

	xOrigin = w->attrib.x - w->input.left;
	yOrigin = w->attrib.y - w->input.top;

	matrixTranslate (&wTransform, xOrigin, yOrigin, 0.0f);
	matrixScale (&wTransform, xScale, yScale, 1.0f);
	matrixTranslate (&wTransform,
			 (rd->geometry.x - w->attrib.x) / xScale - xOrigin,
			 (rd->geometry.y - w->attrib.y) / yScale - yOrigin,
			 0.0f);

	glPushMatrix ();
	glLoadMatrixf (wTransform.m);

	(*s->drawWindow) (w, &wTransform, &fragment, region,
			  mask | PAINT_WINDOW_TRANSFORMED_MASK);

	glPopMatrix ();
    }
    else
    {
	UNWRAP (rs, s, paintWindow);
	status = (*s->paintWindow) (w, attrib, transform, region, mask);
	WRAP (rs, s, paintWindow, resizePaintWindow);
    }

    return status;
}

static Bool
resizeDamageWindowRect (CompWindow *w,
			Bool	   initial,
			BoxPtr     rect)
{
    Bool status = FALSE;

    RESIZE_SCREEN (w->screen);
    RESIZE_DISPLAY (w->screen->display);

    if (w == rd->w && rd->mode == RESIZE_MODE_STRETCH)
    {
	BoxRec box;

	resizeGetStretchRectangle (w->screen->display, &box);
	resizeDamageRectangle (w->screen, &box);

	status = TRUE;
    }

    UNWRAP (rs, w->screen, damageWindowRect);
    status |= (*w->screen->damageWindowRect) (w, initial, rect);
    WRAP (rs, w->screen, damageWindowRect, resizeDamageWindowRect);

    return status;
}

static CompOption *
resizeGetDisplayOptions (CompPlugin  *plugin,
			 CompDisplay *display,
			 int	     *count)
{
    RESIZE_DISPLAY (display);

    *count = NUM_OPTIONS (rd);
    return rd->opt;
}

static Bool
resizeSetDisplayOption (CompPlugin      *plugin,
			CompDisplay     *display,
			const char	*name,
			CompOptionValue *value)
{
    CompOption *o;

    RESIZE_DISPLAY (display);

    o = compFindOption (rd->opt, NUM_OPTIONS (rd), name, NULL);
    if (!o)
	return FALSE;

    return compSetDisplayOption (display, o, value);
}

static const CompMetadataOptionInfo resizeDisplayOptionInfo[] = {
    { "initiate_normal_key", "key", 0, resizeInitiate, resizeTerminate },
    { "initiate_outline_key", "key", 0, resizeInitiate, resizeTerminate },
    { "initiate_rectangle_key", "key", 0, resizeInitiate, resizeTerminate },
    { "initiate_stretch_key", "key", 0, resizeInitiate, resizeTerminate },
    { "initiate_button", "button", 0, resizeInitiate, resizeTerminate },
    { "initiate_key", "key", 0, resizeInitiate, resizeTerminate },
    { "mode", "int", RESTOSTRING (0, RESIZE_MODE_LAST), 0, 0 },
    { "border_color", "color", 0, 0, 0 },
    { "fill_color", "color", 0, 0, 0 },
    { "normal_match", "match", 0, 0, 0 },
    { "outline_match", "match", 0, 0, 0 },
    { "rectangle_match", "match", 0, 0, 0 },
    { "stretch_match", "match", 0, 0, 0 }
};

static Bool
resizeInitDisplay (CompPlugin  *p,
		   CompDisplay *d)
{
    ResizeDisplay *rd;
    int	          i;

    if (!checkPluginABI ("core", CORE_ABIVERSION))
	return FALSE;

    rd = malloc (sizeof (ResizeDisplay));
    if (!rd)
	return FALSE;

    if (!compInitDisplayOptionsFromMetadata (d,
					     &resizeMetadata,
					     resizeDisplayOptionInfo,
					     rd->opt,
					     RESIZE_DISPLAY_OPTION_NUM))
    {
	free (rd);
	return FALSE;
    }

    rd->screenPrivateIndex = allocateScreenPrivateIndex (d);
    if (rd->screenPrivateIndex < 0)
    {
	compFiniDisplayOptions (d, rd->opt, RESIZE_DISPLAY_OPTION_NUM);
	free (rd);
	return FALSE;
    }

    rd->w = 0;

    rd->releaseButton = 0;

    rd->resizeNotifyAtom      = XInternAtom (d->display,
					     "_COMPIZ_RESIZE_NOTIFY", 0);
    rd->resizeInformationAtom = XInternAtom (d->display,
					     "_COMPIZ_RESIZE_INFORMATION", 0);

    for (i = 0; i < NUM_KEYS; i++)
	rd->key[i] = XKeysymToKeycode (d->display,
				       XStringToKeysym (rKeys[i].name));

    rd->offWorkAreaConstrained = TRUE;

    WRAP (rd, d, handleEvent, resizeHandleEvent);

    d->base.privates[displayPrivateIndex].ptr = rd;

    return TRUE;
}

static void
resizeFiniDisplay (CompPlugin  *p,
		   CompDisplay *d)
{
    RESIZE_DISPLAY (d);

    freeScreenPrivateIndex (d, rd->screenPrivateIndex);

    UNWRAP (rd, d, handleEvent);

    compFiniDisplayOptions (d, rd->opt, RESIZE_DISPLAY_OPTION_NUM);

    free (rd);
}

static Bool
resizeInitScreen (CompPlugin *p,
		  CompScreen *s)
{
    ResizeScreen *rs;

    RESIZE_DISPLAY (s->display);

    rs = malloc (sizeof (ResizeScreen));
    if (!rs)
	return FALSE;

    rs->grabIndex = 0;

    rs->leftCursor	= XCreateFontCursor (s->display->display, XC_left_side);
    rs->rightCursor	= XCreateFontCursor (s->display->display, XC_right_side);
    rs->upCursor	= XCreateFontCursor (s->display->display,
					     XC_top_side);
    rs->upLeftCursor	= XCreateFontCursor (s->display->display,
					     XC_top_left_corner);
    rs->upRightCursor	= XCreateFontCursor (s->display->display,
					     XC_top_right_corner);
    rs->downCursor	= XCreateFontCursor (s->display->display,
					     XC_bottom_side);
    rs->downLeftCursor	= XCreateFontCursor (s->display->display,
					     XC_bottom_left_corner);
    rs->downRightCursor = XCreateFontCursor (s->display->display,
					     XC_bottom_right_corner);
    rs->middleCursor    = XCreateFontCursor (s->display->display, XC_fleur);

    rs->cursor[0] = rs->leftCursor;
    rs->cursor[1] = rs->rightCursor;
    rs->cursor[2] = rs->upCursor;
    rs->cursor[3] = rs->downCursor;

    rs->grabWindowWorkArea = NULL;

    WRAP (rs, s, windowResizeNotify, resizeWindowResizeNotify);
    WRAP (rs, s, paintOutput, resizePaintOutput);
    WRAP (rs, s, paintWindow, resizePaintWindow);
    WRAP (rs, s, damageWindowRect, resizeDamageWindowRect);

    s->base.privates[rd->screenPrivateIndex].ptr = rs;

    return TRUE;
}

static void
resizeFiniScreen (CompPlugin *p,
		  CompScreen *s)
{
    RESIZE_SCREEN (s);

    if (rs->leftCursor)
	XFreeCursor (s->display->display, rs->leftCursor);
    if (rs->rightCursor)
	XFreeCursor (s->display->display, rs->rightCursor);
    if (rs->upCursor)
	XFreeCursor (s->display->display, rs->upCursor);
    if (rs->downCursor)
	XFreeCursor (s->display->display, rs->downCursor);
    if (rs->middleCursor)
	XFreeCursor (s->display->display, rs->middleCursor);
    if (rs->upLeftCursor)
	XFreeCursor (s->display->display, rs->upLeftCursor);
    if (rs->upRightCursor)
	XFreeCursor (s->display->display, rs->upRightCursor);
    if (rs->downLeftCursor)
	XFreeCursor (s->display->display, rs->downLeftCursor);
    if (rs->downRightCursor)
	XFreeCursor (s->display->display, rs->downRightCursor);

    UNWRAP (rs, s, windowResizeNotify);
    UNWRAP (rs, s, paintOutput);
    UNWRAP (rs, s, paintWindow);
    UNWRAP (rs, s, damageWindowRect);

    free (rs);
}

static CompBool
resizeInitObject (CompPlugin *p,
		  CompObject *o)
{
    static InitPluginObjectProc dispTab[] = {
	(InitPluginObjectProc) 0, /* InitCore */
	(InitPluginObjectProc) resizeInitDisplay,
	(InitPluginObjectProc) resizeInitScreen
    };

    RETURN_DISPATCH (o, dispTab, ARRAY_SIZE (dispTab), TRUE, (p, o));
}

static void
resizeFiniObject (CompPlugin *p,
		 CompObject *o)
{
    static FiniPluginObjectProc dispTab[] = {
	(FiniPluginObjectProc) 0, /* FiniCore */
	(FiniPluginObjectProc) resizeFiniDisplay,
	(FiniPluginObjectProc) resizeFiniScreen
    };

    DISPATCH (o, dispTab, ARRAY_SIZE (dispTab), (p, o));
}

static CompOption *
resizeGetObjectOptions (CompPlugin *plugin,
			CompObject *object,
			int	   *count)
{
    static GetPluginObjectOptionsProc dispTab[] = {
	(GetPluginObjectOptionsProc) 0, /* GetCoreOptions */
	(GetPluginObjectOptionsProc) resizeGetDisplayOptions
    };

    *count = 0;
    RETURN_DISPATCH (object, dispTab, ARRAY_SIZE (dispTab),
		     (void *) count, (plugin, object, count));
}

static CompBool
resizeSetObjectOption (CompPlugin      *plugin,
		       CompObject      *object,
		       const char      *name,
		       CompOptionValue *value)
{
    static SetPluginObjectOptionProc dispTab[] = {
	(SetPluginObjectOptionProc) 0, /* SetCoreOption */
	(SetPluginObjectOptionProc) resizeSetDisplayOption
    };

    RETURN_DISPATCH (object, dispTab, ARRAY_SIZE (dispTab), FALSE,
		     (plugin, object, name, value));
}

static Bool
resizeInit (CompPlugin *p)
{
    if (!compInitPluginMetadataFromInfo (&resizeMetadata,
					 p->vTable->name,
					 resizeDisplayOptionInfo,
					 RESIZE_DISPLAY_OPTION_NUM,
					 0, 0))
	return FALSE;

    displayPrivateIndex = allocateDisplayPrivateIndex ();
    if (displayPrivateIndex < 0)
    {
	compFiniMetadata (&resizeMetadata);
	return FALSE;
    }

    compAddMetadataFromFile (&resizeMetadata, p->vTable->name);

    return TRUE;
}

static void
resizeFini (CompPlugin *p)
{
    freeDisplayPrivateIndex (displayPrivateIndex);
    compFiniMetadata (&resizeMetadata);
}

static CompMetadata *
resizeGetMetadata (CompPlugin *plugin)
{
    return &resizeMetadata;
}

CompPluginVTable resizeVTable = {
    "resize",
    resizeGetMetadata,
    resizeInit,
    resizeFini,
    resizeInitObject,
    resizeFiniObject,
    resizeGetObjectOptions,
    resizeSetObjectOption
};

CompPluginVTable *
getCompPluginInfo20070830 (void)
{
    return &resizeVTable;
}
