/*
 * Beryl Snap Plugin
 * Author : Guillaume "iXce" Seguin
 * Email  : ixce@beryl-project.org
 *
 * Ported by : Patrick "marex" Niklaus
 * Email     : marex@beryl-project.org
 *
 * Copyright (C) 2007 Guillaume Seguin
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/*
 * TODO
 *  - Apply Edge Resistance to resize
 */

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

#include <compiz-core.h>

#include "snap_options.h"

/*
 * The neat window coordinates macros
 */
#define WIN_X(w) ((w)->attrib.x - (w)->input.left)
#define WIN_Y(w) ((w)->attrib.y - (w)->input.top)
#define WIN_W(w) ((w)->width + (w)->input.left + (w)->input.right)
#define WIN_H(w) ((w)->height + (w)->input.top + (w)->input.bottom)

/*
 * The window we should snap too if snapping to windows
 */
#define SNAP_WINDOW_TYPE (CompWindowTypeNormalMask  | \
			  CompWindowTypeToolbarMask | \
			  CompWindowTypeMenuMask    | \
			  CompWindowTypeUtilMask)

#define VerticalSnap	(1L << 0)
#define HorizontalSnap	(1L << 1)

#define MoveGrab		(1L << 0)
#define ResizeGrab		(1L << 1)

typedef enum
{
	LeftEdge = 0,
	RightEdge,
	TopEdge,
	BottomEdge
} EdgeType;

typedef struct _Edge Edge;

/* Custom Edge struct, linked list
 * Position, start, end meanings are specific to type :
 *  - LeftEdge/RightEdge : position : x, start/end : y1/y2
 *  - TopEdge/BottomEdge : position : y, start/end : x1/x2
 * id/passed are used during visibility detection when adding edges
 * snapped is straight forward
 */
struct _Edge
{
	Edge *prev;
	Edge *next;

	int position;
	int start;
	int end;
	EdgeType type;
	Bool screenEdge;

	Window id;
	Bool passed;

	Bool snapped;
};

static int displayPrivateIndex;

typedef struct _SnapDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	int avoidSnapMask;

	// Used to check is avoidSnapMask is currently matched
	Bool snapping;
} SnapDisplay;

#define SNAP_SCREEN_OPTION_SNAP_TYPE		0
#define SNAP_SCREEN_OPTION_EDGES_CATEGORIES	1
#define SNAP_SCREEN_OPTION_RESISTANCE_DISTANCE	2
#define SNAP_SCREEN_OPTION_ATTRACTION_DISTANCE	3
#define SNAP_SCREEN_OPTION_NUM			4

typedef struct _SnapScreen
{
	int windowPrivateIndex;

	WindowResizeNotifyProc windowResizeNotify;
	WindowMoveNotifyProc windowMoveNotify;
	WindowGrabNotifyProc windowGrabNotify;
	WindowUngrabNotifyProc windowUngrabNotify;
} SnapScreen;

typedef struct _SnapWindow
{
	// Linked lists
	Edge *edges;
	Edge *reverseEdges;

	// bitfield
	int snapDirection;

	// dx/dy/dw/dh when a window is resisting to user
	int dx;
	int dy;
	int dw;
	int dh;

	// Internals
	Bool snapped;
	int grabbed;

	// Internal, avoids infinite notify loops
	Bool skipNotify;
} SnapWindow;

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

#define SNAP_DISPLAY(d) \
    SnapDisplay *sd = GET_SNAP_DISPLAY (d)

#define GET_SNAP_SCREEN(s, sd) \
    ((SnapScreen *) (s)->base.privates[(sd)->screenPrivateIndex].ptr)

#define SNAP_SCREEN(s) \
    SnapScreen *ss = GET_SNAP_SCREEN (s, GET_SNAP_DISPLAY (s->display))

#define GET_SNAP_WINDOW(w, ss) \
    ((SnapWindow *) (w)->base.privates[(ss)->windowPrivateIndex].ptr)

#define SNAP_WINDOW(w)                                   \
    SnapWindow *sw = GET_SNAP_WINDOW  (w,                \
            GET_SNAP_SCREEN  (w->screen,                 \
                GET_SNAP_DISPLAY (w->screen->display)))

// Helper functions ------------------------------------------------------------

/*
 * copied from Beryl's core
 */
static void snapScreenGetOutputDevRect(CompScreen * s, int outputDev, XRectangle * outputRect)
{
	if (!outputRect)
		return;

	if (outputDev >= s->nOutputDev)
		outputDev = 0;

	*outputRect = s->outputDev[outputDev].workArea;
}

/*
 * Wrapper functions to avoid infinite notify loops
 */
static void snapMoveWindow(CompWindow * w, int dx, int dy)
{
	SNAP_WINDOW(w);
	sw->skipNotify = TRUE;
	moveWindow(w, dx, dy, TRUE, TRUE);
	warpPointer(w->screen, dx, dy);
	sw->skipNotify = FALSE;
}

static void snapResizeWindow(CompWindow * w, int dx, int dy, int dw, int dh)
{
	SNAP_WINDOW(w);
	//fprintf (stderr, "Resizing: %d, %d, %d, %d\n", dx, dy, dw, dh);
	sw->skipNotify = TRUE;
	resizeWindow(w, w->attrib.x + dx, w->attrib.y + dy,
				 w->attrib.width + dw, w->attrib.height + dh,
				 w->attrib.border_width);
	sw->skipNotify = FALSE;
}

static void snapFreeEdges(CompWindow * w)
{
	SNAP_WINDOW(w);
	Edge *current = sw->edges, *next;

	while (current)
	{
		next = current->next;
		free(current);
		current = next;
	}
	sw->edges = sw->reverseEdges = NULL;
}

static void snapRemoveEdge(Edge * edge)
{
	if (edge == NULL)
		return;
	if (edge->prev)
		edge->prev->next = edge->next;
	if (edge->next)
		edge->next->prev = edge->prev;
	free(edge);
}

static Edge *snapAddEdge(Edge ** edges, Edge ** reverseEdges, Window id,
						 int position, int start, int end, EdgeType type,
						 Bool screenEdge)
{
	Edge *edge = malloc(sizeof(Edge));

	if (!edge)
		return NULL;
	edge->next = NULL;
	edge->position = position;
	edge->start = start;
	edge->end = end;
	edge->type = type;
	edge->screenEdge = screenEdge;
	edge->snapped = FALSE;
	edge->passed = FALSE;
	edge->id = id;
	if (!*edges)
	{
		edge->prev = NULL;
		*reverseEdges = *edges = edge;
	}
	else
	{
		edge->prev = *reverseEdges;
		edge->prev->next = edge;
		*reverseEdges = edge;
	}
	return edge;
}

/*
 * Add an edge for each rectangle of the region
 */
static void snapAddRegionEdges(SnapWindow * sw, Edge * parent, Region region)
{
	Edge *edge;
	int i, position, start, end;

	for (i = 0; i < region->numRects; i++)
	{
		switch (parent->type)
		{
		case LeftEdge:
		case RightEdge:
			position = region->rects[i].x1;
			start = region->rects[i].y1;
			end = region->rects[i].y2;
			break;
		case TopEdge:
		case BottomEdge:
		default:
			position = region->rects[i].y1;
			start = region->rects[i].x1;
			end = region->rects[i].x2;
		}
		edge = snapAddEdge(&sw->edges, &sw->reverseEdges, parent->id,
						   position, start, end, parent->type,
						   parent->screenEdge);
		if (edge)
			edge->passed = parent->passed;
	}
}

/* Checks if a window is considered a snap window. If it's
 * not visible, returns false. If it's a panel and we're
 * snapping to screen edges, it's considered a snap-window.
 */

#define UNLIKELY(x) __builtin_expect(!!(x),0)

static inline Bool isSnapWindow(CompWindow * w)
{
	//SNAP_SCREEN(w->screen);
	
	if (UNLIKELY(!w))
		return FALSE;
	if (w->invisible || w->hidden || w->minimized)
		return FALSE;
	if ((w->type & SNAP_WINDOW_TYPE) && 
		(snapGetEdgesCategoriesMask(w->screen) & EdgesCategoriesWindowEdgesMask))
		return TRUE;
	if (w->struts && 
		(snapGetEdgesCategoriesMask(w->screen) & EdgesCategoriesScreenEdgesMask))
		return TRUE;
	return FALSE;
}

// Edges update functions ------------------------------------------------------
/*
 * Detect visible windows edges
 */
static void snapUpdateWindowsEdges(CompWindow * w)
{
	CompWindow *c = NULL;
	Edge *e = NULL, *next = NULL;

	SNAP_WINDOW(w);
	Region edgeRegion, resultRegion;
	XRectangle rect;
	Bool remove = FALSE;

	// First add all the windows
	c = w->screen->windows;
	while (c)
	{
		// Just check that we're not trying to snap to current window,
		// that the window is not invisible and of a valid type
		if (c == w || !isSnapWindow(c))
		{
			c = c->next;
			continue;
		}
		snapAddEdge(&sw->edges, &sw->reverseEdges, c->id,
					WIN_Y(c), WIN_X(c), WIN_X(c) + WIN_W(c), TopEdge, FALSE);
		snapAddEdge(&sw->edges, &sw->reverseEdges, c->id,
					WIN_Y(c) + WIN_H(c), WIN_X(c), WIN_X(c) + WIN_W(c),
					BottomEdge, FALSE);
		snapAddEdge(&sw->edges, &sw->reverseEdges, c->id,
					WIN_X(c), WIN_Y(c), WIN_Y(c) + WIN_H(c), LeftEdge, FALSE);
		snapAddEdge(&sw->edges, &sw->reverseEdges, c->id,
					WIN_X(c) + WIN_W(c), WIN_Y(c), WIN_Y(c) + WIN_H(c),
					RightEdge, FALSE);
		c = c->next;
	}

	// Now strip invisible edges
	// Loop through all the windows stack, and through all the edges
	// If an edge has been passed, check if it's in the region window,
	// if the edge is fully under the window, drop it, or if it's only
	// partly covered, cut it/split it in one/two smaller visible edges
	for (c = w->screen->windows; c; c = c->next)
	{
		if (c == w || !isSnapWindow(c))
			continue;
		for (e = sw->edges; e; e = next)
		{
			if (!e->passed)
			{
				if (e->id == c->id)
					e->passed = TRUE;
				next = e->next;
				continue;
			}
			switch (e->type)
			{
				case LeftEdge:
				case RightEdge:
					rect.x = e->position;
					rect.y = e->start;
					rect.width = 1;
					rect.height = e->end - e->start;
					break;
				case TopEdge:
				case BottomEdge:
				default:
					rect.x = e->start;
					rect.y = e->position;
					rect.width = e->end - e->start;
					rect.height = 1;
			}
			// If the edge is in the window region, remove it,
			// if it's partly in the region, split it
			edgeRegion = XCreateRegion();
			resultRegion = XCreateRegion();
			XUnionRectWithRegion(&rect, edgeRegion, edgeRegion);
			XSubtractRegion(edgeRegion, c->region, resultRegion);
			if (XEmptyRegion(resultRegion))
				remove = TRUE;
			else if (!XEqualRegion(edgeRegion, resultRegion))
			{
				snapAddRegionEdges(sw, e, resultRegion);
				remove = TRUE;
			}
			next = e->next;
			if (remove)
			{
				if (e->prev == NULL)
					sw->edges = e->next;
				if (e->next == NULL)
					sw->reverseEdges = e->prev;
				snapRemoveEdge(e);
				remove = FALSE;
			}
			XDestroyRegion(resultRegion);
			XDestroyRegion(edgeRegion);
		}
	}
}

/*
 * Loop on outputDevs and add the extents as edges
 * Note that left side is a right edge, right side a left edge,
 * top side a bottom edge and bottom side a top edge,
 * since they will be snapped as the right/left/bottom/top edge of a window
 */
static void snapUpdateScreenEdges(CompWindow * w)
{
	CompWindow *c = NULL;
	Edge *e = NULL, *next = NULL;

	SNAP_WINDOW(w);
	Region edgeRegion, resultRegion;
	XRectangle rect;
	Bool remove = FALSE;

	XRectangle area;
	int i;

	for (i = 0; i < w->screen->nOutputDev; i++)
	{
		snapScreenGetOutputDevRect(w->screen, i, &area);
		snapAddEdge(&sw->edges, &sw->reverseEdges, 0,
					area.y, area.x, area.x + area.width - 1, BottomEdge, TRUE);
		snapAddEdge(&sw->edges, &sw->reverseEdges, 0,
					area.y + area.height, area.x,
					area.x + area.width - 1, TopEdge, TRUE);
		snapAddEdge(&sw->edges, &sw->reverseEdges, 0,
					area.x, area.y, area.y + area.height - 1, RightEdge, TRUE);
		snapAddEdge(&sw->edges, &sw->reverseEdges, 0,
					area.x + area.width, area.y,
					area.y + area.height - 1, LeftEdge, TRUE);
	}

	// Drop screen edges parts that are under struts, basically apply the
	// same strategy than for windows edges visibility
	for (c = w->screen->windows; c; c = c->next)
	{
		if (c == w || !c->struts)
			continue;
		for (e = sw->edges; e; e = next)
		{
			if (!e->screenEdge)
			{
				next = e->next;
				continue;
			}
			switch (e->type)
			{
				case LeftEdge:
				case RightEdge:
					rect.x = e->position;
					rect.y = e->start;
					rect.width = 1;
					rect.height = e->end - e->start;
					break;
				case TopEdge:
				case BottomEdge:
				default:
					rect.x = e->start;
					rect.y = e->position;
					rect.width = e->end - e->start;
					rect.height = 1;
			}
			edgeRegion = XCreateRegion();
			resultRegion = XCreateRegion();
			XUnionRectWithRegion(&rect, edgeRegion, edgeRegion);
			XSubtractRegion(edgeRegion, c->region, resultRegion);
			if (XEmptyRegion(resultRegion))
				remove = TRUE;
			else if (!XEqualRegion(edgeRegion, resultRegion))
			{
				snapAddRegionEdges(sw, e, resultRegion);
				remove = TRUE;
			}
			next = e->next;
			if (remove)
			{
				if (e->prev == NULL)
					sw->edges = e->next;
				if (e->next == NULL)
					sw->reverseEdges = e->prev;
				snapRemoveEdge(e);
				remove = FALSE;
			}
			XDestroyRegion(resultRegion);
			XDestroyRegion(edgeRegion);
		}
	}
}

/*
 * Clean edges and fill it again with appropriate edges
 */
static void snapUpdateEdges(CompWindow * w)
{
	//SNAP_SCREEN(w->screen);

	snapFreeEdges(w);

	snapUpdateWindowsEdges(w);

	if (snapGetEdgesCategoriesMask(w->screen) & EdgesCategoriesScreenEdgesMask)
		snapUpdateScreenEdges(w);
}

// Edges checking functions (move) ---------------------------------------------

/*
 * Find nearest edge in the direction set by "type",
 * w is the grabbed window, position/start/end are the window edges coordinates
 * before : if true the window has to be before the edge (top/left being origin)
 * snapDirection : just an helper, related to type
 */
static void
snapMoveCheckNearestEdge(CompWindow * w, int position, int start, int end,
						 Bool before, EdgeType type, int snapDirection)
{
	//SNAP_SCREEN(w->screen);
	SNAP_WINDOW(w);
	Edge *current = sw->edges;
	Edge *edge = current;
	int dist, min = 65535;

	while (current)
	{
		// Skip wrong type or outbound edges
		if (current->type != type
			|| current->end < start || current->start > end)
		{
			current = current->next;
			continue;
		}
		// Compute distance
		dist = before ? position - current->position
				: current->position - position;
		// Update minimum distance if needed
		if (dist < min && dist >= 0)
		{
			min = dist;
			edge = current;
		}
		// 0-dist edge, just break
		if (dist == 0)
			break;
		// Unsnap edges that aren't snapped anymore
		if (current->snapped && dist > snapGetResistanceDistance(w->screen))
			current->snapped = FALSE;
		current = current->next;
	}
	// We found a 0-dist edge, or we have a snapping candidate
	if (min == 0 || (min <= snapGetAttractionDistance(w->screen)
					 && snapGetSnapTypeMask(w->screen) & SnapTypeEdgeAttractionMask))
	{
		// Update snapping data
		if (snapGetSnapTypeMask(w->screen) & SnapTypeEdgeResistanceMask)
		{
			sw->snapped = TRUE;
			sw->snapDirection |= snapDirection;
		}
		// Attract the window if needed, moving it of the correct dist
		if (min != 0 && !edge->snapped)
		{
			edge->snapped = TRUE;
			switch (type)
			{
			case LeftEdge:
				snapMoveWindow(w, min, 0);
				break;
			case RightEdge:
				snapMoveWindow(w, -min, 0);
				break;
			case TopEdge:
				snapMoveWindow(w, 0, min);
				break;
			case BottomEdge:
				snapMoveWindow(w, 0, -min);
				break;
			default:
				break;
			}
		}
	}
}

/*
 * Call the previous function for each of the 4 sides of the window
 */
static void snapMoveCheckEdges(CompWindow * w)
{
	snapMoveCheckNearestEdge(w, WIN_X(w),
							 WIN_Y(w), WIN_Y(w) + WIN_H(w),
							 TRUE, RightEdge, HorizontalSnap);
	snapMoveCheckNearestEdge(w, WIN_X(w) + WIN_W(w),
							 WIN_Y(w), WIN_Y(w) + WIN_H(w),
							 FALSE, LeftEdge, HorizontalSnap);
	snapMoveCheckNearestEdge(w, WIN_Y(w),
							 WIN_X(w), WIN_X(w) + WIN_W(w),
							 TRUE, BottomEdge, VerticalSnap);
	snapMoveCheckNearestEdge(w, WIN_Y(w) + WIN_H(w),
							 WIN_X(w), WIN_X(w) + WIN_W(w),
							 FALSE, TopEdge, VerticalSnap);
}

// Edges checking functions (resize) -------------------------------------------

/*
 * Similar function for Snap on Resize
 */
static void
snapResizeCheckNearestEdge(CompWindow * w, int position, int start, int end,
						   Bool before, EdgeType type, int snapDirection)
{
	//SNAP_SCREEN(w->screen);
	SNAP_WINDOW(w);
	Edge *current = sw->edges;
	Edge *edge = current;
	int dist, min = 65535;

	while (current)
	{
		// Skip wrong type or outbound edges
		if (current->type != type
			|| current->end < start || current->start > end)
		{
			current = current->next;
			continue;
		}
		// Compute distance
		dist = before ? position - current->position
				: current->position - position;
		// Update minimum distance if needed
		if (dist < min && dist >= 0)
		{
			min = dist;
			edge = current;
		}
		// 0-dist edge, just break
		if (dist == 0)
			break;
		// Unsnap edges that aren't snapped anymore
		if (current->snapped && dist > snapGetResistanceDistance(w->screen))
			current->snapped = FALSE;
		current = current->next;
	}
	// We found a 0-dist edge, or we have a snapping candidate
	if (min == 0 || (min <= snapGetAttractionDistance(w->screen)
					 && snapGetSnapTypeMask(w->screen) & SnapTypeEdgeAttractionMask))
	{
		// Update snapping data
		if (snapGetSnapTypeMask(w->screen) & SnapTypeEdgeResistanceMask)
		{
			sw->snapped = TRUE;
			sw->snapDirection |= snapDirection;
		}
		// FIXME : this needs resize-specific code.
		// Attract the window if needed, moving it of the correct dist
		if (min != 0 && !edge->snapped)
		{
			edge->snapped = TRUE;
			switch (type)
			{
			case LeftEdge:
				snapResizeWindow(w, min, 0, 0, 0);
				break;
			case RightEdge:
				snapResizeWindow(w, -min, 0, 0, 0);
				break;
			case TopEdge:
				snapResizeWindow(w, 0, min, 0, 0);
				break;
			case BottomEdge:
				snapResizeWindow(w, 0, -min, 0, 0);
				break;
			default:
				break;
			}
		}
	}
}

/*
 * Call the previous function for each of the 4 sides of the window
 */
static void snapResizeCheckEdges(CompWindow * w, int dx, int dy, int dw, int dh)
{
	int x, y, width, height;
	x =  WIN_W(w);
	y =  WIN_Y(w);
	width = WIN_W(w);
	height = WIN_H(w);

	snapResizeCheckNearestEdge(w, x, y, y + height, TRUE, RightEdge,
							   HorizontalSnap);
	snapResizeCheckNearestEdge(w, x + width, y, y + height, FALSE, LeftEdge,
							   HorizontalSnap);
	snapResizeCheckNearestEdge(w, y, x, x + width, TRUE, BottomEdge,
							   VerticalSnap);
	snapResizeCheckNearestEdge(w, y + height, x, x + width, FALSE, TopEdge,
							   VerticalSnap);
}

// avoidSnap functions ---------------------------------------------------------

static Bool
snapEnableSnapping(CompDisplay * d,
				   CompAction * action,
				   CompActionState state, CompOption * option, int nOption)
{
	SNAP_DISPLAY(d);
	sd->snapping = TRUE;
	return FALSE;
}

static Bool
snapDisableSnapping(CompDisplay * d,
					CompAction * action,
					CompActionState state, CompOption * option, int nOption)
{
	SNAP_DISPLAY(d);
	sd->snapping = FALSE;
	return FALSE;
}

// Check if avoidSnap is matched, and enable/disable snap consequently
static void snapHandleEvent(CompDisplay * d, XEvent * event)
{
	SNAP_DISPLAY(d);

	if (event->type == d->xkbEvent)
	{
		XkbAnyEvent *xkbEvent = (XkbAnyEvent *) event;

		if (xkbEvent->xkb_type == XkbStateNotify)
		{
			XkbStateNotifyEvent *stateEvent = (XkbStateNotifyEvent *) event;
			unsigned int mods = 0xffffffff;

			if (sd->avoidSnapMask)
				mods = sd->avoidSnapMask;

			if ((stateEvent->mods & mods) == mods)
				snapDisableSnapping(d, NULL, 0, NULL, 0);
			else
				snapEnableSnapping(d, NULL, 0, NULL, 0);
		}
	}

	UNWRAP(sd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(sd, d, handleEvent, snapHandleEvent);
}

// Events notifications --------------------------------------------------------

static void
snapWindowResizeNotify(CompWindow * w, int dx, int dy, int dw, int dh)
{
	SNAP_DISPLAY (w->screen->display);
	SNAP_SCREEN(w->screen);
	SNAP_WINDOW (w);

	UNWRAP(ss, w->screen, windowResizeNotify);
	(*w->screen->windowResizeNotify) (w, dx, dy, dw, dh);
	WRAP(ss, w->screen, windowResizeNotify, snapWindowResizeNotify);

	// avoid-infinite-notify-loop mode/not grabbed
	if (sw->skipNotify || !(sw->grabbed & ResizeGrab))
		return;

	// we have to avoid snapping but there's still some buffered moving
	if (!sd->snapping && (sw->dx || sw->dy || sw->dw || sw->dh))
	{
		snapResizeWindow(w, sw->dx, sw->dy, sw->dw, sw->dh);
		sw->dx = sw->dy = sw->dw = sw->dh = 0;
		return;
	}

	// avoiding snap, nothing buffered
	if (!sd->snapping)
		return;

	// apply edge resistance
	if (snapGetSnapTypeMask(w->screen) & SnapTypeEdgeResistanceMask)
	{
		// If there's horizontal snapping, add dx to current buffered
		// dx and resist (move by -dx) or release the window and move
		// by buffered dx - dx, same for dh
		if (sw->snapped && sw->snapDirection & HorizontalSnap)
		{
			sw->dx += dx;
			if (sw->dx < snapGetResistanceDistance(w->screen)
				&& sw->dx > -snapGetResistanceDistance(w->screen))
				snapResizeWindow(w, -dx, 0, 0, 0);
			else
			{
				snapResizeWindow(w, sw->dx - dx, 0, 0, 0);
				sw->dx = 0;
				if (!sw->dw)
					sw->snapDirection &= VerticalSnap;
			}
			sw->dw += dw;
			if (sw->dw < snapGetResistanceDistance(w->screen)
				&& sw->dw > -snapGetResistanceDistance(w->screen))
				snapResizeWindow(w, 0, 0, -dw, 0);
			else
			{
				snapResizeWindow(w, 0, 0, sw->dw - dw, 0);
				sw->dw = 0;
				if (!sw->dx)
					sw->snapDirection &= VerticalSnap;
			}
		}
		// Same for vertical snapping and dy/dh
		if (sw->snapped && sw->snapDirection & VerticalSnap)
		{
			sw->dy += dy;
			if (sw->dy < snapGetResistanceDistance(w->screen)
				&& sw->dy > -snapGetResistanceDistance(w->screen))
				snapResizeWindow(w, 0, -dy, 0, 0);
			else
			{
				snapResizeWindow(w, 0, sw->dy - dy, 0, 0);
				sw->dy = 0;
				if (!sw->dh)
					sw->snapDirection &= HorizontalSnap;
			}
			sw->dh += dh;
			if (sw->dh < snapGetResistanceDistance(w->screen)
				&& sw->dh > -snapGetResistanceDistance(w->screen))
				snapResizeWindow(w, 0, 0, 0, -dh);
			else
			{
				snapResizeWindow(w, 0, 0, 0, sw->dh - dh);
				sw->dh = 0;
				if (!sw->dy)
					sw->snapDirection &= HorizontalSnap;
			}
		}
		// If we are no longer snapping in any direction, reset snapped
		if (sw->snapped && !sw->snapDirection)
			sw->snapped = FALSE;
	}

	// If we don't already snap vertically and horizontally,
	// check edges status
	if (sw->snapDirection != (VerticalSnap | HorizontalSnap))
		snapResizeCheckEdges(w, dx, dy, dw, dh);
}

static void
snapWindowMoveNotify(CompWindow * w, int dx, int dy, Bool immediate)
{
	SNAP_DISPLAY(w->screen->display);
	SNAP_SCREEN(w->screen);
	SNAP_WINDOW(w);

	UNWRAP(ss, w->screen, windowMoveNotify);
	(*w->screen->windowMoveNotify) (w, dx, dy, immediate);
	WRAP(ss, w->screen, windowMoveNotify, snapWindowMoveNotify);

	// avoid-infinite-notify-loop mode/not grabbed
	if (sw->skipNotify || !(sw->grabbed & MoveGrab))
		return;

	// we have to avoid snapping but there's still some buffered moving
	if (!sd->snapping && (sw->dx || sw->dy))
	{
		snapMoveWindow(w, sw->dx, sw->dy);
		sw->dx = sw->dy = 0;
		return;
	}

	// avoiding snap, nothing buffered
	if (!sd->snapping)
		return;

	// apply edge resistance
	if (snapGetSnapTypeMask(w->screen) & SnapTypeEdgeResistanceMask)
	{
		// If there's horizontal snapping, add dx to current buffered
		// dx and resist (move by -dx) or release the window and move
		// by buffered dx - dx
		if (sw->snapped && sw->snapDirection & HorizontalSnap)
		{
			sw->dx += dx;
			if (sw->dx < snapGetResistanceDistance(w->screen)
				&& sw->dx > -snapGetResistanceDistance(w->screen))
				snapMoveWindow(w, -dx, 0);
			else
			{
				snapMoveWindow(w, sw->dx - dx, 0);
				sw->dx = 0;
				sw->snapDirection &= VerticalSnap;
			}
		}
		// Same for vertical snapping and dy
		if (sw->snapped && sw->snapDirection & VerticalSnap)
		{
			sw->dy += dy;
			if (sw->dy < snapGetResistanceDistance(w->screen)
				&& sw->dy > -snapGetResistanceDistance(w->screen))
				snapMoveWindow(w, 0, -dy);
			else
			{
				snapMoveWindow(w, 0, sw->dy - dy);
				sw->dy = 0;
				sw->snapDirection &= HorizontalSnap;
			}
		}
		// If we are no longer snapping in any direction, reset snapped
		if (sw->snapped && !sw->snapDirection)
			sw->snapped = FALSE;
	}
	// If we don't already snap vertically and horizontally,
	// check edges status
	if (sw->snapDirection != (VerticalSnap | HorizontalSnap))
		snapMoveCheckEdges(w);
}

/*
 * Initiate snap, get edges
 */
static void
snapWindowGrabNotify(CompWindow * w,
					 int x, int y, unsigned int state, unsigned int mask)
{
	SNAP_SCREEN(w->screen);
	SNAP_WINDOW(w);

	sw->grabbed = (mask & CompWindowGrabResizeMask) ? ResizeGrab : MoveGrab;
	snapUpdateEdges(w);

	UNWRAP(ss, w->screen, windowGrabNotify);
	(*w->screen->windowGrabNotify) (w, x, y, state, mask);
	WRAP(ss, w->screen, windowGrabNotify, snapWindowGrabNotify);
}

/*
 * Clean edges data, reset dx/dy to avoid buggy moves
 * when snap will be triggered again.
 */
static void snapWindowUngrabNotify(CompWindow * w)
{
	SNAP_SCREEN(w->screen);
	SNAP_WINDOW(w);

	snapFreeEdges(w);
	sw->snapped = FALSE;
	sw->snapDirection = 0;
	sw->grabbed = 0;
	sw->dx = sw->dy = sw->dw = sw->dh = 0;

	UNWRAP(ss, w->screen, windowUngrabNotify);
	(*w->screen->windowUngrabNotify) (w);
	WRAP(ss, w->screen, windowUngrabNotify, snapWindowUngrabNotify);
}

// Internal stuff --------------------------------------------------------------

static void snapDisplayOptionChanged(CompDisplay *d, CompOption *opt, SnapDisplayOptions num)
{
	SNAP_DISPLAY(d);

	switch (num)
	{
		case SnapDisplayOptionAvoidSnap:
		{
			unsigned int mask = snapGetAvoidSnapMask(d);
			sd->avoidSnapMask = 0;
			if (mask & AvoidSnapShiftMask)
				sd->avoidSnapMask |= ShiftMask;
			if (mask & AvoidSnapAltMask)
				sd->avoidSnapMask |= CompAltMask;
			if (mask & AvoidSnapControlMask)
				sd->avoidSnapMask |= ControlMask;
			if (mask & AvoidSnapMetaMask)
				sd->avoidSnapMask |= CompMetaMask;
		}

		default:
			break;
	}
}

static Bool snapInitDisplay(CompPlugin * p, CompDisplay * d)
{
	SnapDisplay *sd;

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

	sd = malloc(sizeof(SnapDisplay));
	if (!sd)
		return FALSE;

	sd->screenPrivateIndex = allocateScreenPrivateIndex(d);
	if (sd->screenPrivateIndex < 0)
	{
		free(sd);
		return FALSE;
	}

	WRAP(sd, d, handleEvent, snapHandleEvent);

	snapSetAvoidSnapNotify(d, snapDisplayOptionChanged);

	sd->avoidSnapMask = 0;
	sd->snapping = TRUE;

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

	return TRUE;
}

static void snapFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	SNAP_DISPLAY(d);

	freeScreenPrivateIndex(d, sd->screenPrivateIndex);

	UNWRAP(sd, d, handleEvent);

	free(sd);
}

static Bool snapInitScreen(CompPlugin * p, CompScreen * s)
{
	SnapScreen *ss;

	SNAP_DISPLAY(s->display);

	ss = malloc(sizeof(SnapScreen));
	if (!ss)
		return FALSE;

	ss->windowPrivateIndex = allocateWindowPrivateIndex(s);
	if (ss->windowPrivateIndex < 0)
	{
		free(ss);
		return FALSE;
	}

	//WRAP(ss, s, windowResizeNotify, snapWindowResizeNotify);
	WRAP(ss, s, windowMoveNotify, snapWindowMoveNotify);
	WRAP(ss, s, windowGrabNotify, snapWindowGrabNotify);
	WRAP(ss, s, windowUngrabNotify, snapWindowUngrabNotify);

	s->base.privates[sd->screenPrivateIndex].ptr = ss;

	return TRUE;
}

static void snapFiniScreen(CompPlugin * p, CompScreen * s)
{
	SNAP_SCREEN(s);

	freeWindowPrivateIndex(s, ss->windowPrivateIndex);

	//UNWRAP(ss, s, windowResizeNotify);
	UNWRAP(ss, s, windowMoveNotify);
	UNWRAP(ss, s, windowGrabNotify);
	UNWRAP(ss, s, windowUngrabNotify);

	free(ss);
}

static Bool snapInitWindow(CompPlugin * p, CompWindow * w)
{
	SnapWindow *sw;

	SNAP_SCREEN(w->screen);

	sw = malloc(sizeof(SnapWindow));
	if (!sw)
		return FALSE;

	sw->edges = sw->reverseEdges = NULL;
	sw->snapDirection = 0;
	sw->dx = sw->dy = sw->dw = sw->dh = 0;
	sw->grabbed = 0;
	sw->snapped = FALSE;
	sw->skipNotify = FALSE;

	w->base.privates[ss->windowPrivateIndex].ptr = sw;

	return TRUE;
}

static void snapFiniWindow(CompPlugin * p, CompWindow * w)
{
	SNAP_WINDOW(w);

	snapFreeEdges(w);

	free(sw);
}

static CompBool
snapInitObject (CompPlugin *p,
		CompObject *o)
{
	static InitPluginObjectProc dispTab[] = {
		(InitPluginObjectProc) 0, /* InitCore */
		(InitPluginObjectProc) snapInitDisplay,
		(InitPluginObjectProc) snapInitScreen,
		(InitPluginObjectProc) snapInitWindow
    };

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

static void
snapFiniObject (CompPlugin *p,
		CompObject *o)
{
    static FiniPluginObjectProc dispTab[] = {
		(FiniPluginObjectProc) 0, /* FiniCore */
		(FiniPluginObjectProc) snapFiniDisplay,
		(FiniPluginObjectProc) snapFiniScreen,
		(FiniPluginObjectProc) snapFiniWindow
    };

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

static Bool snapInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();
	if (displayPrivateIndex < 0)
		return FALSE;
	return TRUE;
}

static void snapFini(CompPlugin * p)
{
	freeDisplayPrivateIndex(displayPrivateIndex);
}

CompPluginVTable snapVTable = {
	"snap",
	0,
	snapInit,
	snapFini,
	snapInitObject,
	snapFiniObject,
	0,
	0
};

CompPluginVTable *getCompPluginInfo(void)
{
	return &snapVTable;
}
