/*
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2002, 2003 Red Hat, Inc.
 * Copyright (C) 2003 Rob Adams
 * Copyright (C) 2005 Novell, Inc.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

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

#include <beryl.h>

#include <glib.h>

#define PLACE_WORKAROUND_DEFAULT TRUE
#define RECTANGLE_FROM_WINDOW(w) {w->attrib.x,w->attrib.y,w->attrib.width,w->attrib.height}

#define GET_WINDOW_HEIGHT(w) (window->serverHeight + window->serverBorderWidth * 2)
#define GET_WINDOW_WIDTH(w) (window->serverWidth + window->serverBorderWidth * 2)

static int displayPrivateIndex;

typedef struct _PlaceDisplay
{
	int screenPrivateIndex;
	//    HandleEventProc handleEvent;
} PlaceDisplay;

#define PLACE_SCREEN_OPTION_WORKAROUND      0
#define PLACE_SCREEN_OPTION_MODE 	    1
#define PLACE_SCREEN_OPTION_NUM             2

typedef struct _PlaceScreen
{
	CompOption opt[PLACE_SCREEN_OPTION_NUM];

	DamageWindowRectProc damageWindowRect;
	int placeMode;
} PlaceScreen;

typedef enum _PlaceMode
{
	PlaceModeCascade = 0,
	PlaceModeCentered,
	PlaceModeRandom,
	PlaceModeIntelligent,
} PlaceMode;

char *placeModes[] =
		{ N_("Cascade"), N_("Centered"), N_("Random"), N_("Intelligent") };

#define PLACE_MODE_DEFAULT PlaceModeIntelligent
#define NUM_PLACE_MODES 4

#define GET_PLACE_DISPLAY(d)				      \
    ((PlaceDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define PLACE_DISPLAY(d)		     \
    PlaceDisplay *pd = GET_PLACE_DISPLAY (d)

#define GET_PLACE_SCREEN(s, pd)					  \
    ((PlaceScreen *) (s)->privates[(pd)->screenPrivateIndex].ptr)

#define PLACE_SCREEN(s)							   \
    PlaceScreen *ps = GET_PLACE_SCREEN (s, GET_PLACE_DISPLAY (s->display))

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

static Bool
placeSetScreenOption(CompScreen * screen, char *name, CompOptionValue * value)
{
	CompOption *o;
	int index;

	PLACE_SCREEN(screen);

	o = compFindOption(ps->opt, NUM_OPTIONS(ps), name, &index);
	if (!o)
		return FALSE;

	switch (index)
	{
	case PLACE_SCREEN_OPTION_WORKAROUND:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case PLACE_SCREEN_OPTION_MODE:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_PLACE_MODES; i++)
				if (strcmp(placeModes[i], o->value.s) == 0)
					ps->placeMode = (PlaceMode) i;
		}
	default:
		break;
	}

	return FALSE;
}

static void placeScreenInitOptions(PlaceScreen * ps)
{
	CompOption *o;
	int i;

	o = &ps->opt[PLACE_SCREEN_OPTION_WORKAROUND];
	o->advanced = False;
	o->name = "workarounds";
	o->group = N_("Misc. options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Workarounds");
	o->longDesc = N_("Window placement Workarounds.");
	o->type = CompOptionTypeBool;
	o->value.b = PLACE_WORKAROUND_DEFAULT;

	o = &ps->opt[PLACE_SCREEN_OPTION_MODE];
	o->advanced = False;
	o->name = "place_mode";
	o->group = N_("Misc. options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Placement Mode");
	o->longDesc = N_("Window Placement technique.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(placeModes[PLACE_MODE_DEFAULT]);
	o->rest.s.string = placeModes;
	o->rest.s.nString = NUM_PLACE_MODES;
	
	for (i = 0; i < NUM_PLACE_MODES; i++)
				if (strcmp(placeModes[i], o->value.s) == 0)
					ps->placeMode = (PlaceMode) i;

}

static CompOption *placeGetScreenOptions(CompScreen * screen, int *count)
{
	if (screen)
	{
		PLACE_SCREEN(screen);

		*count = NUM_OPTIONS(ps);
		return ps->opt;
	}
	else
	{
		PlaceScreen *ps = malloc(sizeof(PlaceScreen));

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


typedef enum
{
	PlaceLeft,
	PlaceRight,
	PlaceTop,
	PlaceBottom
} PlaceWindowDirection;

static Bool
rectangleIntersect(XRectangle * src1, XRectangle * src2, XRectangle * dest)
{
	int dest_x, dest_y;
	int dest_w, dest_h;
	int return_val;

	g_return_val_if_fail(src1 != NULL, FALSE);
	g_return_val_if_fail(src2 != NULL, FALSE);
	g_return_val_if_fail(dest != NULL, FALSE);

	return_val = FALSE;

	dest_x = MAX(src1->x, src2->x);
	dest_y = MAX(src1->y, src2->y);
	dest_w = MIN(src1->x + src1->width, src2->x + src2->width) - dest_x;
	dest_h = MIN(src1->y + src1->height, src2->y + src2->height) - dest_y;

	if (dest_w > 0 && dest_h > 0)
	{
		dest->x = dest_x;
		dest->y = dest_y;
		dest->width = dest_w;
		dest->height = dest_h;
		return_val = TRUE;
	}
	else
	{
		dest->width = 0;
		dest->height = 0;
	}

	return return_val;
}

static gint northwestcmp(gconstpointer a, gconstpointer b)
{
	CompWindow *aw = (gpointer) a;
	CompWindow *bw = (gpointer) b;
	int from_origin_a;
	int from_origin_b;
	int ax, ay, bx, by;

	ax = aw->attrib.x - aw->input.left;
	ay = aw->attrib.y - aw->input.top;

	bx = bw->attrib.x - bw->input.left;
	by = bw->attrib.y - bw->input.top;

	/* probably there's a fast good-enough-guess we could use here. */
	from_origin_a = sqrt(ax * ax + ay * ay);
	from_origin_b = sqrt(bx * bx + by * by);

	if (from_origin_a < from_origin_b)
		return -1;
	else if (from_origin_a > from_origin_b)
		return 1;
	else
		return 0;
}


static void
get_workarea_of_current_output_device(CompScreen * s, XRectangle * area)
{
	int head;

	head = screenGetCurrentOutputDev(s);
	screenGetOutputDevWorkArea(s, head, area);
}




static void placeCentered(CompWindow * window, int *x, int *y)
{
	*x = window->screen->workArea.x + (window->screen->workArea.width
									   - GET_WINDOW_WIDTH(window)) / 2;
	*y = window->screen->workArea.y + (window->screen->workArea.height
									   - GET_WINDOW_HEIGHT(window)) / 2;
}



static void placeRandom(CompWindow * window, int *x, int *y)
{
	*x = rand() % (window->screen->workArea.width -
				   window->screen->workArea.x);
	*y = rand() % (window->screen->workArea.height -
				   window->screen->workArea.y);
}








static void
find_next_cascade(CompWindow * window,
				  GList * windows, int x, int y, int *new_x, int *new_y)
{
	GList *tmp;
	GList *sorted;
	int cascade_x, cascade_y;
	int x_threshold, y_threshold;
	int window_width, window_height;
	int cascade_stage;
	XRectangle work_area;

	sorted = g_list_copy(windows);
	sorted = g_list_sort(sorted, northwestcmp);

	/* This is a "fuzzy" cascade algorithm.
	 * For each window in the list, we find where we'd cascade a
	 * new window after it. If a window is already nearly at that
	 * position, we move on.
	 */

	/* arbitrary-ish threshold, honors user attempts to
	 * manually cascade.
	 */
#define CASCADE_FUZZ 15

	x_threshold = MAX(window->input.left, CASCADE_FUZZ);
	y_threshold = MAX(window->input.top, CASCADE_FUZZ);

	/* Find furthest-SE origin of all workspaces.
	 * cascade_x, cascade_y are the target position
	 * of NW corner of window frame.
	 */

	get_workarea_of_current_output_device(window->screen, &work_area);

	cascade_x = MAX(0, work_area.x);
	cascade_y = MAX(0, work_area.y);

	/* Find first cascade position that's not used. */

	window_width = GET_WINDOW_WIDTH(window) + window->input.left +
			window->input.right;
	window_height = GET_WINDOW_HEIGHT(window) + window->input.top +
			window->input.bottom;

	cascade_stage = 0;
	tmp = sorted;
	while (tmp != NULL)
	{
		CompWindow *w;
		int wx, wy;

		w = tmp->data;

		/* we want frame position, not window position */
		wx = w->attrib.x - w->input.left;
		wy = w->attrib.y - w->input.top;

		if (ABS(wx - cascade_x) < x_threshold &&
			ABS(wy - cascade_y) < y_threshold)
		{
			/* This window is "in the way", move to next cascade
			 * point. The new window frame should go at the origin
			 * of the client window we're stacking above.
			 */
			wx = w->attrib.x;
			wy = w->attrib.y;

			cascade_x = wx;
			cascade_y = wy;

			/* If we go off the screen, start over with a new cascade */
			if (((cascade_x + window_width) >
				 (work_area.x + work_area.width)) ||
				((cascade_y + window_height) >
				 (work_area.y + work_area.height)))
			{
				cascade_x = MAX(0, work_area.x);
				cascade_y = MAX(0, work_area.y);

#define CASCADE_INTERVAL 50		/* space between top-left corners of cascades */

				cascade_stage += 1;
				cascade_x += CASCADE_INTERVAL * cascade_stage;

				/* start over with a new cascade translated to the right,
				 * unless we are out of space
				 */
				if ((cascade_x + window_width) <
					(work_area.x + work_area.width))
				{
					tmp = sorted;
					continue;
				}
				else
				{
					/* All out of space, this cascade_x won't work */
					cascade_x = MAX(0, work_area.x);
					break;
				}
			}
		}
		else
		{
			/* Keep searching for a further-down-the-diagonal window. */
		}

		tmp = tmp->next;
	}

	/* cascade_x and cascade_y will match the last window in the list
	 * that was "in the way" (in the approximate cascade diagonal)
	 */

	g_list_free(sorted);

	/* Convert coords to position of window, not position of frame. */
	*new_x = cascade_x + window->input.left;
	*new_y = cascade_y + window->input.top;
}

static void
find_most_freespace(CompWindow * window,
					CompWindow * focus_window,
					int x, int y, int *new_x, int *new_y)
{
	PlaceWindowDirection side;
	int max_area;
	int max_width, max_height, left, right, top, bottom;
	int left_space, right_space, top_space, bottom_space;
	int frame_size_left, frame_size_top;
	XRectangle work_area;
	XRectangle avoid;
	XRectangle outer;

	frame_size_left = window->input.left;
	frame_size_top = window->input.top;

	get_workarea_of_current_output_device(window->screen, &work_area);

	getOuterRectOfWindow(focus_window, &avoid);
	getOuterRectOfWindow(window, &outer);

	/* Find the areas of choosing the various sides of the focus window */
	max_width = MIN(avoid.width, outer.width);
	max_height = MIN(avoid.height, outer.height);
	left_space = avoid.x - work_area.x;
	right_space = work_area.width - (avoid.x + avoid.width - work_area.x);
	top_space = avoid.y - work_area.y;
	bottom_space = work_area.height - (avoid.y + avoid.height - work_area.y);
	left = MIN(left_space, outer.width);
	right = MIN(right_space, outer.width);
	top = MIN(top_space, outer.height);
	bottom = MIN(bottom_space, outer.height);

	/* Find out which side of the focus_window can show the most of the
	 * window
	 */
	side = PlaceLeft;
	max_area = left * max_height;
	if (right * max_height > max_area)
	{
		side = PlaceRight;
		max_area = right * max_height;
	}
	if (top * max_width > max_area)
	{
		side = PlaceTop;
		max_area = top * max_width;
	}
	if (bottom * max_width > max_area)
	{
		side = PlaceBottom;
		max_area = bottom * max_width;
	}

	/* Give up if there's no where to put it
	 * (i.e. focus window is maximized)
	 */
	if (max_area == 0)
		return;

	/* Place the window on the relevant side; if the whole window fits,
	 * make it adjacent to the focus window; if not, make sure the
	 * window doesn't go off the edge of the screen.
	 */
	switch (side)
	{
	case PlaceLeft:
		*new_y = avoid.y + frame_size_top;
		if (left_space > outer.width)
			*new_x = avoid.x - outer.width + frame_size_left;
		else
			*new_x = work_area.x + frame_size_left;
		break;
	case PlaceRight:
		*new_y = avoid.y + frame_size_top;
		if (right_space > outer.width)
			*new_x = avoid.x + avoid.width + frame_size_left;
		else
			*new_x = work_area.x + work_area.width - outer.width +
					frame_size_left;
		break;
	case PlaceTop:
		*new_x = avoid.x + frame_size_left;
		if (top_space > outer.height)
			*new_y = avoid.y - outer.height + frame_size_top;
		else
			*new_y = work_area.y + frame_size_top;
		break;
	case PlaceBottom:
		*new_x = avoid.x + frame_size_left;
		if (bottom_space > outer.height)
			*new_y = avoid.y + avoid.height + frame_size_top;
		else
			*new_y = work_area.y + work_area.height - outer.height +
					frame_size_top;
		break;
	}
}


static void
avoid_being_obscured_as_second_modal_dialog(CompWindow * window,
											int *x, int *y)
{
	/* We can't center this dialog if it was denied focus and it
	 * overlaps with the focus window and this dialog is modal and this
	 * dialog is in the same app as the focus window (*phew*...please
	 * don't make me say that ten times fast). See bug 307875 comment 11
	 * and 12 for details, but basically it means this is probably a
	 * second modal dialog for some app while the focus window is the
	 * first modal dialog.  We should probably make them simultaneously
	 * visible in general, but it becomes mandatory to do so due to
	 * buggy apps (e.g. those using gtk+ *sigh*) because in those cases
	 * this second modal dialog also happens to be modal to the first
	 * dialog in addition to the main window, while it has only let us
	 * know about the modal-to-the-main-window part.
	 */

	CompWindow *focus_window;

	focus_window =
			findWindowAtDisplay(window->screen->display,
								window->screen->display->activeWindow);

}

static gboolean
rectangle_overlaps_some_window(XRectangle * rect, GList * windows)
{
	GList *tmp;
	XRectangle dest;

	tmp = windows;
	while (tmp != NULL)
	{
		CompWindow *other = tmp->data;
		XRectangle other_rect;

		switch (other->type)
		{
		case CompWindowTypeSplashMask:
		case CompWindowTypeDesktopMask:
		case CompWindowTypeDialogMask:
		case CompWindowTypeModalDialogMask:
		case CompWindowTypeFullscreenMask:
		case CompWindowTypeUnknownMask:
			break;
		case CompWindowTypeNormalMask:
		case CompWindowTypeUtilMask:
		case CompWindowTypeToolbarMask:
		case CompWindowTypeMenuMask:
			getOuterRectOfWindow(other, &other_rect);

			if (rectangleIntersect(rect, &other_rect, &dest))
				return TRUE;
			break;
		}

		tmp = tmp->next;
	}

	return FALSE;
}



static void placeIntelligent(CompWindow * w, int *x, int *y, GList * windows)
{
	int tries = 0;
	XRectangle wRect = RECTANGLE_FROM_WINDOW(w);
	XRectangle sRect = { 0, 0, 0, 0 };
	wRect.height += w->serverBorderWidth;
	int xi;
	int yi;
	CompScreen *screen = w->screen;
	int maxTries =  ( ((wRect.height + wRect.width ) / 100) > 1 ? ((wRect.height + wRect.width) / 100) : 1 );
	int wIter = wRect.width / maxTries;
	int hIter = wRect.height / maxTries;


	get_workarea_of_current_output_device(screen, &sRect);

	placeCentered(w, x, y);
	XRectangle cRect = { *x, *y, wRect.width, wRect.height };
	if (!rectangle_overlaps_some_window(&cRect, windows))
	{
		return;					/*If it fits place it in the center */
	}
	

	/* Recursing would be ineffecient, a goto really is the best solution */
  thisisdisgusting:

	for (xi = sRect.x; xi < (sRect.width+sRect.x); xi += 10)
	{
		wRect.x = xi;
		for (yi = sRect.y; yi < (sRect.height+sRect.y); yi += 10)
		{
			wRect.y = yi;

			if (!rectangle_overlaps_some_window(&wRect, windows))
			{

				if (!
					((wRect.width > sRect.width / 2)
					 && ((wRect.width + w->serverX > sRect.width)
						 || (wRect.height + w->serverY > sRect.height))))
				{
					*x = xi;
				}
				*y = yi;
				return;

			}
		}
	}
	/* No fit and big window? Center it, that tends to be what people expect */
	if (tries == 0 && wRect.width > (sRect.width / 2))
	{
		placeCentered(w, x, y);
		return;
	}
	/* Would the window fit if it was smaller ? Reduce width of the window rectangle and go through the check loop again */
	if (tries < maxTries && wRect.width > 0 && wRect.height > 0)
	{
		tries++;
		wRect.width -= wIter;
		wRect.height -= hIter;
		goto thisisdisgusting;
	}
	/* Fallback, we shouldn't get here? */
	placeCentered(w, x, y);
}

static gint leftmost_cmp(gconstpointer a, gconstpointer b)
{
	CompWindow *aw = (gpointer) a;
	CompWindow *bw = (gpointer) b;
	int ax, bx;

	ax = aw->attrib.x - aw->input.left;
	bx = bw->attrib.x - bw->input.left;

	if (ax < bx)
		return -1;
	else if (ax > bx)
		return 1;
	else
		return 0;
}

static gint topmost_cmp(gconstpointer a, gconstpointer b)
{
	CompWindow *aw = (gpointer) a;
	CompWindow *bw = (gpointer) b;
	int ay, by;

	ay = aw->attrib.y - aw->input.top;
	by = bw->attrib.y - bw->input.top;

	if (ay < by)
		return -1;
	else if (ay > by)
		return 1;
	else
		return 0;
}

static void
center_tile_rect_in_area(XRectangle * rect, XRectangle * work_area)
{
	int fluff;

	/* The point here is to tile a window such that "extra"
	 * space is equal on either side (i.e. so a full screen
	 * of windows tiled this way would center the windows
	 * as a group)
	 */

	fluff = (work_area->width % (rect->width + 1)) / 2;
	rect->x = work_area->x + fluff;
	fluff = (work_area->height % (rect->height + 1)) / 3;
	rect->y = work_area->y + fluff;
}

static gboolean
rect_fits_in_work_area(XRectangle * work_area, XRectangle * rect)
{
	return ((rect->x >= work_area->x) &&
			(rect->y >= work_area->y) &&
			(rect->x + rect->width <= work_area->x + work_area->width)
			&& (rect->y + rect->height <= work_area->y + work_area->height));
}

/* Find the leftmost, then topmost, empty area on the workspace
 * that can contain the new window.
 *
 * Cool feature to have: if we can't fit the current window size,
 * try shrinking the window (within geometry constraints). But
 * beware windows such as Emacs with no sane minimum size, we
 * don't want to create a 1x1 Emacs.
 */
static gboolean
find_first_fit(CompWindow * window,
			   GList * windows, int x, int y, int *new_x, int *new_y)
{
	/* This algorithm is limited - it just brute-force tries
	 * to fit the window in a small number of locations that are aligned
	 * with existing windows. It tries to place the window on
	 * the bottom of each existing window, and then to the right
	 * of each existing window, aligned with the left/top of the
	 * existing window in each of those cases.
	 */
	int retval;
	GList *below_sorted;
	GList *right_sorted;
	GList *tmp;
	XRectangle rect;
	XRectangle work_area;

	retval = FALSE;

	/* Below each window */
	below_sorted = g_list_copy(windows);
	below_sorted = g_list_sort(below_sorted, leftmost_cmp);
	below_sorted = g_list_sort(below_sorted, topmost_cmp);

	/* To the right of each window */
	right_sorted = g_list_copy(windows);
	right_sorted = g_list_sort(right_sorted, topmost_cmp);
	right_sorted = g_list_sort(right_sorted, leftmost_cmp);

	getOuterRectOfWindow(window, &rect);

	get_workarea_of_current_output_device(window->screen, &work_area);

	work_area.x += (window->initialViewportX - window->screen->x) *
			window->screen->width;
	work_area.y += (window->initialViewportY - window->screen->y) *
			window->screen->height;

	center_tile_rect_in_area(&rect, &work_area);

	if (rect_fits_in_work_area(&work_area, &rect) &&
		!rectangle_overlaps_some_window(&rect, windows))
	{
		*new_x = rect.x + window->input.left;
		*new_y = rect.y + window->input.top;

		retval = TRUE;

		goto out;
	}

	/* try below each window */
	tmp = below_sorted;
	while (tmp != NULL)
	{
		CompWindow *w = tmp->data;
		XRectangle outer_rect;

		getOuterRectOfWindow(w, &outer_rect);

		rect.x = outer_rect.x;
		rect.y = outer_rect.y + outer_rect.height;

		if (rect_fits_in_work_area(&work_area, &rect) &&
			!rectangle_overlaps_some_window(&rect, below_sorted))
		{
			*new_x = rect.x + window->input.left;
			*new_y = rect.y + window->input.top;

			retval = TRUE;

			goto out;
		}

		tmp = tmp->next;
	}

	/* try to the right of each window */
	tmp = right_sorted;
	while (tmp != NULL)
	{
		CompWindow *w = tmp->data;
		XRectangle outer_rect;

		getOuterRectOfWindow(w, &outer_rect);

		rect.x = outer_rect.x + outer_rect.width;
		rect.y = outer_rect.y;

		if (rect_fits_in_work_area(&work_area, &rect) &&
			!rectangle_overlaps_some_window(&rect, right_sorted))
		{
			*new_x = rect.x + window->input.left;
			*new_y = rect.y + window->input.top;

			retval = TRUE;

			goto out;
		}

		tmp = tmp->next;
	}

  out:
	g_list_free(below_sorted);
	g_list_free(right_sorted);

	return retval;
}


static void
placeWindow(CompWindow * window, int x, int y, int *new_x, int *new_y)
{
	CompWindow *wi;
	GList *windows;
	XRectangle work_area;
	int x0 = (window->initialViewportX - window->screen->x) *
			window->screen->width;
	int y0 = (window->initialViewportY - window->screen->y) *
			window->screen->height;
	int window_width, window_height;

	PLACE_SCREEN(window->screen);

	window_width = GET_WINDOW_WIDTH(window);
	window_height = GET_WINDOW_HEIGHT(window);

	get_workarea_of_current_output_device(window->screen, &work_area);

	work_area.x += x0;
	work_area.y += y0;

	windows = NULL;

	switch (window->type)
	{
	case CompWindowTypeSplashMask:
	case CompWindowTypeDialogMask:
	case CompWindowTypeModalDialogMask:
	case CompWindowTypeNormalMask:
		/* Run placement algorithm on these. */
		break;
	case CompWindowTypeDockMask:
	case CompWindowTypeDesktopMask:
	case CompWindowTypeUtilMask:
	case CompWindowTypeToolbarMask:
	case CompWindowTypeMenuMask:
	case CompWindowTypeFullscreenMask:
	case CompWindowTypeUnknownMask:
		/* Assume the app knows best how to place these, no placement
		 * algorithm ever (other than "leave them as-is")
		 */
		goto done_no_constraints;
		break;
	}

	/* don't run placement algorithm on windows that can't be moved */
	if (!(window->actions & CompWindowActionMoveMask))
	{
		goto done_no_constraints;
	}

	if (window->type & CompWindowTypeFullscreenMask)
	{
		x = x0;
		y = y0;
		goto done_no_constraints;
	}

	if (window->state & (CompWindowStateMaximizedVertMask |
						 CompWindowStateMaximizedHorzMask))
	{
		if (window->screen->nOutputDev == 1)
		{
			if (window->state & CompWindowStateMaximizedVertMask)
				y = work_area.y + window->input.top;

			if (window->state & CompWindowStateMaximizedHorzMask)
				x = work_area.x + window->input.left;
		}
		else
		{
			int head = screenGetOutputDevForWindow(window);
			XRectangle workArea;

			screenGetOutputDevWorkArea(window->screen, head, &workArea);

			if (window->state & CompWindowStateMaximizedVertMask)
				y = workArea.y + window->input.top;

			if (window->state & CompWindowStateMaximizedHorzMask)
				x = workArea.x + window->input.left;
		}

		goto done;
	}

	if (ps->opt[PLACE_SCREEN_OPTION_WORKAROUND].value.b)
	{
		/* workarounds enabled */

		if ((window->sizeHints.flags & PPosition) ||
			(window->sizeHints.flags & USPosition))
		{
			avoid_being_obscured_as_second_modal_dialog(window, &x, &y);
			goto done;
		}
	}
	else
	{
		switch (window->type)
		{
		case CompWindowTypeNormalMask:
			/* Only accept USPosition on normal windows because the app is full
			 * of shit claiming the user set -geometry for a dialog or dock
			 */
			if (window->sizeHints.flags & USPosition)
			{
				/* don't constrain with placement algorithm */
				goto done;
			}
			break;
		case CompWindowTypeSplashMask:
		case CompWindowTypeDialogMask:
		case CompWindowTypeModalDialogMask:
			/* Ignore even USPosition on dialogs, splashscreen */
			break;
		case CompWindowTypeDockMask:
		case CompWindowTypeDesktopMask:
		case CompWindowTypeUtilMask:
		case CompWindowTypeToolbarMask:
		case CompWindowTypeMenuMask:
		case CompWindowTypeFullscreenMask:
		case CompWindowTypeUnknownMask:
			/* Assume the app knows best how to place these. */
			if (window->sizeHints.flags & PPosition)
			{
				goto done_no_constraints;
			}
			break;
		}
	}

	if ((window->type == CompWindowTypeDialogMask ||
		 window->type == CompWindowTypeModalDialogMask) &&
		window->transientFor != None)
	{
		/* Center horizontally, at top of parent vertically */

		CompWindow *parent;

		parent = findWindowAtDisplay(window->screen->display,
									 window->transientFor);
		if (parent)
		{
			int w;

			x = parent->attrib.x;
			y = parent->attrib.y;

			w = GET_WINDOW_WIDTH(parent);

			/* center of parent */
			x = x + w / 2 - window_width / 2;

			/* "visually" center window over parent, leaving twice as
			 * much space below as on top.
			 */
			y += (GET_WINDOW_HEIGHT(parent) -
				  window_height) / 3 + window->input.top;

			/* clip to screen if parent is visible in current viewport */
			if (parent->attrib.x < parent->screen->width &&
				parent->attrib.x + parent->screen->width > 0)
			{
				XRectangle area;

				get_workarea_of_current_output_device(window->screen, &area);

				if (x + window_width > area.x + area.width)
					x = area.x + area.width - window_width;
				if (y + window_height > area.y + area.height)
					y = area.y + area.height - window_height;
				if (x < area.x)
					x = area.x;
				if (y < area.y)
					y = area.y;
			}

			avoid_being_obscured_as_second_modal_dialog(window, &x, &y);

			goto done_no_x_constraints;
		}
	}

	/* FIXME UTILITY with transient set should be stacked up
	 * on the sides of the parent window or something.
	 */
	if (window->type == CompWindowTypeDialogMask ||
		window->type == CompWindowTypeModalDialogMask ||
		window->type == CompWindowTypeSplashMask)
	{
		/* Center on screen */
		int head;
		XRectangle workArea;

		/*      w = window->screen->width;
		   h = window->screen->height;

		   x = (w - window->width) / 2;
		   y = (h - window->height) / 2;
		 */
		head = screenGetCurrentOutputDev(window->screen);
		screenGetOutputDevWorkArea(window->screen, head, &workArea);

		x = (workArea.width - window_width) / 2;
		y = (workArea.height - window_height) / 2;

		goto done_check_denied_focus;
	}

	/* Find windows that matter (not minimized, on same workspace
	 * as placed window, may be shaded - if shaded we pretend it isn't
	 * for placement purposes)
	 */
	for (wi = window->screen->windows; wi; wi = wi->next)
	{
		if (!wi->shaded && wi->attrib.map_state != IsViewable)
			continue;

		if (wi->attrib.x >= work_area.x + work_area.width ||
			wi->attrib.x + GET_WINDOW_WIDTH(wi) <= work_area.x ||
			wi->attrib.y >= work_area.y + work_area.height ||
			wi->attrib.y + GET_WINDOW_HEIGHT(wi) <= work_area.y)
			continue;

		if (wi->attrib.override_redirect)
			continue;

		if (wi->state & CompWindowStateOffscreenMask)
			continue;

		if (wi->type & (CompWindowTypeDesktopMask |
					    CompWindowTypeDockMask |
						CompWindowTypeFullscreenMask |
						CompWindowTypeUnknownMask))
			continue;

		if (wi != window)
			windows = g_list_prepend(windows, wi);
	}

	/* "Origin" placement algorithm */
	x = x0;
	y = y0;

	if (ps->placeMode == PlaceModeCascade)
	{
		if (find_first_fit(window, windows, x, y, &x, &y))
			goto done_check_denied_focus;

		/* if the window wasn't placed at the origin of screen,
		 * cascade it onto the current screen
		 */
		find_next_cascade(window, windows, x, y, &x, &y);
	}
	else if (ps->placeMode == PlaceModeCentered)
	{
		placeCentered(window, &x, &y);
	}
	else if (ps->placeMode == PlaceModeRandom)
	{
		placeRandom(window, &x, &y);
	}
	else
	{
		placeIntelligent(window, &x, &y, windows);
	}



  done_check_denied_focus:
	/* If the window is being denied focus and isn't a transient of the
	 * focus window, we do NOT want it to overlap with the focus window
	 * if at all possible.  This is guaranteed to only be called if the
	 * focus_window is non-NULL, and we try to avoid that window.
	 */
	if (0 /* window->denied_focus_and_not_transient */ )
	{
		gboolean found_fit = FALSE;
		CompWindow *focus_window;

		focus_window =
				findWindowAtDisplay(window->screen->display,
									window->screen->display->activeWindow);
		if (focus_window)
		{
			XRectangle wr, fwr, overlap;

			getOuterRectOfWindow(window, &wr);
			getOuterRectOfWindow(focus_window, &fwr);

			/* No need to do anything if the window doesn't overlap at all */
			found_fit = !rectangleIntersect(&wr, &fwr, &overlap);

			/* Try to do a first fit again, this time only taking into
			 * account the focus window.
			 */
			if (!found_fit)
			{
				GList *focus_window_list;

				focus_window_list = g_list_prepend(NULL, focus_window);

				/* Reset x and y ("origin" placement algorithm) */
				x = 0;
				y = 0;

				found_fit =
						find_first_fit(window, focus_window_list, x, y, &x,
									   &y);

				g_list_free(focus_window_list);
			}
		}

		/* If that still didn't work, just place it where we can see as much
		 * as possible.
		 */
		if (!found_fit)
			find_most_freespace(window, focus_window, x, y, &x, &y);
	}

	g_list_free(windows);

  done:
	if (((x + window_width + window->input.right < work_area.x +
		  work_area.width) &&
		 (x + window_width + window->input.right > work_area.x))
		||
		((x - window->input.left > work_area.x) &&
		 (x - window->input.left < work_area.x + work_area.width)))
	{
		if (x + window_width + window->input.right >
			work_area.x + work_area.width)
			x = work_area.x + work_area.width
					- window_width - window->input.right;

		if (x - window->input.left < work_area.x)
			x = work_area.x + window->input.left;
	}
	else
	{
		//only use THIS code if the window is TOTALLY outside the viewport.
		while (x + window_width + window->input.right >
			   work_area.x + work_area.width)
			x -= work_area.width;


		if (x - window->input.left < work_area.x)
			x = work_area.x + window->input.left;
	}

  done_no_x_constraints:
	if (window->screen->nOutputDev == 1)
	{
		if (y + window_height + window->input.bottom >
			work_area.y + work_area.height)
			y = work_area.y + work_area.height
					- window_height - window->input.bottom;

		if (y - window->input.top < work_area.y)
			y = work_area.y + window->input.top;
	}
	else
	{
		int head = screenGetOutputDevForWindow(window);
		XRectangle workArea;

		screenGetOutputDevWorkArea(window->screen, head, &workArea);

		if (y + window_height + window->input.bottom >
			workArea.y + workArea.height)
			y = workArea.y + workArea.height - window_height -
					window->input.bottom;

		if (y - window->input.top < workArea.y)
			y = workArea.y + window->input.top;

	}

  done_no_constraints:
	*new_x = x;
	*new_y = y;
}



static Bool placeDamageWindowRect(CompWindow * w, Bool initial, BoxPtr rect)
{
	Bool status;

	PLACE_SCREEN(w->screen);

	UNWRAP(ps, w->screen, damageWindowRect);
	status = (*w->screen->damageWindowRect) (w, initial, rect);
	WRAP(ps, w->screen, damageWindowRect, placeDamageWindowRect);

	if (initial && !w->attrib.override_redirect && !w->placed)
	{
		int newX, newY;

		placeWindow(w, w->attrib.x, w->attrib.y, &newX, &newY);

		w->placed = TRUE;

		if (newX != w->attrib.x || newY != w->attrib.y)
		{
			moveWindow(w, newX - w->attrib.x,
					   newY - w->attrib.y, FALSE, TRUE);
			syncWindowPosition(w);
		}
	}

	return status;
}

static Bool placeInitDisplay(CompPlugin * p, CompDisplay * d)
{
	PlaceDisplay *pd;

	pd = malloc(sizeof(PlaceDisplay));
	if (!pd)
		return FALSE;

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

	d->privates[displayPrivateIndex].ptr = pd;

	//    WRAP (pd, d, handleEvent, placeHandleEvent);

	return TRUE;
}

static void placeFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	PLACE_DISPLAY(d);

	//    UNWRAP (pd, d, handleEvent);

	freeScreenPrivateIndex(d, pd->screenPrivateIndex);

	free(pd);
}

static Bool placeInitScreen(CompPlugin * p, CompScreen * s)
{
	PlaceScreen *ps;

	PLACE_DISPLAY(s->display);

	ps = malloc(sizeof(PlaceScreen));
	if (!ps)
		return FALSE;

	placeScreenInitOptions(ps);
	//    addScreenBinding (s, &ps->opt[PLACE_SCREEN_OPTION_CENTER_INITIATE].value.bind);

	WRAP(ps, s, damageWindowRect, placeDamageWindowRect);

	s->privates[pd->screenPrivateIndex].ptr = ps;

	return TRUE;
}

static void placeFiniScreen(CompPlugin * p, CompScreen * s)
{
	PLACE_SCREEN(s);

	UNWRAP(ps, s, damageWindowRect);

	free(ps);
}

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

	return TRUE;
}

static void placeFini(CompPlugin * p)
{
	if (displayPrivateIndex >= 0)
		freeDisplayPrivateIndex(displayPrivateIndex);
}

static CompPluginVTable placeVTable = {
	"place",
	N_("Place Windows"),
	N_("Place windows at appropriate positions when mapped"),
	placeInit,
	placeFini,
	placeInitDisplay,
	placeFiniDisplay,
	placeInitScreen,
	placeFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	0,							/* GetDisplayOptions */
	0,							/* SetDisplayOption */
	placeGetScreenOptions,
	placeSetScreenOption,
	0,
	0,
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins",
	"wm",
	0,
	0,
	True,
};

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


syntax highlighted by Code2HTML, v. 0.9.1