/**
 *
 * Beryl tile plugin
 *
 * tile.c
 *
 * Copyright (c) 2006 Atie H. <atie.at.matrix@gmail.com>
 * Copyright (c) 2006 Michal Fojtik <pichalsi(at)gmail.com>
 *
 * 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.
 *
 * TODO
 *	- change vertical and horizontal tiling to similar behavior as Left
 *	- fix bugs
 *	- make vertical and horizontal maximization be saved when tiling
 *
 **/

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

#include <X11/Xatom.h>
#include <X11/extensions/Xrender.h>

#include <beryl.h>

#define GET_TILE_DISPLAY(d) ((TileDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define TILE_DISPLAY(d) TileDisplay *td = GET_TILE_DISPLAY (d)

#define GET_TILE_SCREEN(s, td) ((TileScreen *) (s)->privates[(td)->screenPrivateIndex].ptr)
#define TILE_SCREEN(s) TileScreen *ts = GET_TILE_SCREEN (s, GET_TILE_DISPLAY (s->display))

#define GET_TILE_WINDOW(w, ts) ((TileWindow *) (w)->privates[(ts)->windowPrivateIndex].ptr)
#define TILE_WINDOW(w) TileWindow *tw = GET_TILE_WINDOW (w, GET_TILE_SCREEN (w->screen, GET_TILE_DISPLAY (w->screen->display)))

#define THIS_VIEWPORT(s) (ts->viewports[(s)->x])

#define TILE_DISPLAY_OPTION_VERTICALLY										0
#define TILE_DISPLAY_OPTION_HORIZONTALLY									1
#define TILE_DISPLAY_OPTION_TILE											2
#define TILE_DISPLAY_OPTION_CASCADE											3
#define TILE_DISPLAY_OPTION_RESTORE											4
#define TILE_DISPLAY_OPTION_TOGGLE											5
#define TILE_DISPLAY_OPTION_EXCLUDE_LIST									6
#define TILE_DISPLAY_OPTION_JOIN											7
#define TILE_DISPLAY_OPTION_DELTA											8
#define TILE_DISPLAY_OPTION_LEFT_OCCUPANCY									9
#define TILE_DISPLAY_OPTION_ANIMATE											10
#define TILE_DISPLAY_OPTION_ANIMATION_DURATION								11
#define TILE_DISPLAY_OPTION_ANIMATION_TYPE									12
#define TILE_DISPLAY_OPTION_TOGGLE_TYPE										13
#define TILE_DISPLAY_OPTION_NUM												14

#define TILE_HORIZONTALLY_DISPLAY_OPTION_INITIATE_KEY						"q"
#define TILE_HORIZONTALLY_DISPLAY_OPTION_INITIATE_MOD						CompSuperMask|ShiftMask
#define TILE_VERTICALLY_DISPLAY_OPTION_INITIATE_KEY							"w"
#define TILE_VERTICALLY_DISPLAY_OPTION_INITIATE_MOD							CompSuperMask|ShiftMask
#define TILE_TILE_DISPLAY_OPTION_INITIATE_KEY								"a"
#define TILE_TILE_DISPLAY_OPTION_INITIATE_MOD								CompSuperMask|ShiftMask
#define TILE_CASCADE_DISPLAY_OPTION_INITIATE_KEY							"s"
#define TILE_CASCADE_DISPLAY_OPTION_INITIATE_MOD							CompSuperMask|ShiftMask
#define TILE_RESTORE_DISPLAY_OPTION_INITIATE_KEY							"z"
#define TILE_RESTORE_DISPLAY_OPTION_INITIATE_MOD							CompSuperMask|ShiftMask
#define TILE_TOGGLE_DISPLAY_OPTION_INITIATE_KEY								"x"
#define TILE_TOGGLE_DISPLAY_OPTION_INITIATE_MOD								CompSuperMask|ShiftMask

#define TILE_DEFAULT_JOIN 													FALSE
#define TILE_DEFAULT_ANIMATE												FALSE
#define TILE_DEFAULT_ANIMATION												fade
#define TILE_DEFAULT_TOGGLE_TYPE											tile

#define TILE_DELTA_DEFAULT													35
#define TILE_DELTA_MIN														0
#define TILE_DELTA_MAX														250

#define TILE_LEFT_OCCUPANCY_DEFAULT											60
#define TILE_LEFT_OCCUPANCY_MIN												20
#define TILE_LEFT_OCCUPANCY_MAX												80

#define TILE_ANIMATION_DURATION_DEFAULT										1000
#define TILE_ANIMATION_DURATION_MIN 										200
#define TILE_ANIMATION_DURATION_MAX											5000

// Minimal width and height under which the window cant be resized
#define MINIMUM_WIDTH 														25
#define MINIMUM_HEIGHT														10

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

static int displayPrivateIndex = 0;

#define NUM_ANIMATIONS														5
static char *animationTypeString[] = {
	N_("Filled outline"),
	N_("Slide"),
	N_("Zoom"),
	N_("Drop from top"),
	N_("Fade")
};

typedef enum {
	outline = 0,
	slide,
	zoom,
	drop,
	fade
} animationType;

// number of tile types for dropdown, without 'none'
#define TILE_TYPE_NUM														5

static char *tileTypeString[] = {
	N_("Tile"),
	N_("Left"),
	N_("Tile Vertically"),
	N_("Tile Horizontally"),
	N_("Cascade")
};

typedef enum {
	tile = 0,
	left,
	vert,
	horz,
	cascade,
	none
} tileType;

typedef struct _TileDisplay {
	tileType currentToggleType; // toggle tiling type
	animationType currentAnimationType;

	int animationDuration; // duration of animation
	int screenPrivateIndex;

	CompOption opt[TILE_DISPLAY_OPTION_NUM];

} TileDisplay;

typedef struct _TileViewport {
	tileType currentTileType; // which kind of tiling is applied to windows on viewport
	int tiledCount; // number of windows
	CompWindow *firstTiled; // pointer to first tiled window
} TileViewport;

typedef struct _TileScreen {
	TileViewport *viewports;
	Bool isResizing; // whether there any windows on screen being resized
	int oneDuration; // duration of animation for one window
	int msResizing; // number of ms elapsed from start of resizing animation

	PaintWindowProc paintWindow;
	WindowResizeNotifyProc windowResizeNotify;
	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	PaintScreenProc paintScreen;

	int decoWidth, decoHeight; // decoration width and height
	int windowPrivateIndex;
} TileScreen;

typedef struct _TileWindow {
	int isOtherAnimationAtom; // atom to check whether animation plugin is animating the window

	CompWindow *next; // next window for tiling
	CompWindow *prev; // previous window for tiling

	Bool resizedAlready; // whether the animation already resized the window

	// coords used in animation, before last resize
	int previousX;
	int previousY;
	int previousWidth;
	int previousHeight;

	// coords used when configuring window in middle of animation
	int futureX;
	int futureY;
	int futureWidth;
	int futureHeight;

	Bool isResizing; // tells if the window is being resized

	GLushort outlineColor[3];
	int prevState;

	// coords before whole tiling
	int originalX;
	int originalY;
	int originalWidth;
	int originalHeight;
} TileWindow;

static Bool placeWindow(CompWindow *w, int x, int y, int width, int height);
static Bool isTileWindow(CompWindow *w);
static Bool setWindowFutureSize(CompWindow *w);

int current = 0;

// window painting function, draws animation
static Bool tilePaintWindow(CompWindow * w, const WindowPaintAttrib * attrib, Region region, unsigned int mask)
{
	CompScreen *s = w->screen;
	Bool status;

	TILE_SCREEN(s);
	TILE_WINDOW(w);
	TILE_DISPLAY(s->display);
	
	if(tw->isResizing)
		mask |= PAINT_WINDOW_NO_CORE_INSTANCE_MASK;

	if(tw->isResizing && td->currentAnimationType != outline) // on window texture animation
	{
	WindowPaintAttrib sAttrib = *attrib;

	glPushMatrix();
	glLoadIdentity();
	prepareXCoords(s, s->currentOutputDev, -DEFAULT_Z_CAMERA);

	switch(td->currentAnimationType)
	{
		/*
			Drop animation
		*/
		case drop:
			glRotatef(100.0f/td->animationDuration*ts->msResizing - 100, 0,0,1);
			(*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK);
		break;

		/*
			Zoom animation
		*/
		case zoom:
			glTranslatef(0,0, -1 + ts->msResizing/(float)td->animationDuration);
			(*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK);
		break;

		/*
			Slide animation
		*/
		case slide:
			if(ts->msResizing < 0.75*td->animationDuration)
				sAttrib.opacity = OPAQUE / 2;
			else
				sAttrib.opacity = OPAQUE/2 + OPAQUE/2*(ts->msResizing - 0.75*td->animationDuration)/(0.25*td->animationDuration);
			
			if(ts->msResizing > current*ts->oneDuration) // windows that have animation finished already
			{
				(*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK);
			}
			else if(ts->msResizing > (current-1)*ts->oneDuration && ts->msResizing < current*ts->oneDuration) // animation in progress
			{
				int thisDur; // ms spent animating this window
				for(thisDur = ts->msResizing;thisDur > ts->oneDuration;thisDur -= ts->oneDuration)
					;

				if(current%2)
					glTranslatef(-s->width + s->width * (float)thisDur/ts->oneDuration, 0, 0);
				else
					glTranslatef(s->width - s->width * (float)thisDur/ts->oneDuration, 0, 0);

				(*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK);
			}
		break;

		/*
			Outline animation
		*/
		case outline:

		break;

		/*
			Fade animation
		*/
		case fade:
		// first half of the animation, fade out
		if(ts->msResizing < 0.40f*td->animationDuration)
		{
			sAttrib.opacity = OPAQUE - OPAQUE*ts->msResizing/(0.40f*td->animationDuration);
			(*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK);
		}
		else if(ts->msResizing > 0.40f*td->animationDuration && !tw->resizedAlready) // resize window right after first half
			setWindowFutureSize(w);
		else if(ts->msResizing > 0.60f*td->animationDuration) // second half of animation, fade in
		{
			sAttrib.opacity = OPAQUE*(ts->msResizing - 0.60f*td->animationDuration)/(0.40f*td->animationDuration);
			(*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK);
		}
		break;
	}

	current -= 1;
	glPopMatrix();
	}
	else // paint window as always
	{
		UNWRAP(ts, s, paintWindow);
		status = (*s->paintWindow) (w, attrib, region, mask);
		WRAP(ts, s, paintWindow, tilePaintWindow);
	}

	return status;
}

static void tilePreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	TILE_SCREEN(s);
	TILE_DISPLAY(s->display);

	// this probably shouldnt be here...
	td->animationDuration = td->opt[TILE_DISPLAY_OPTION_ANIMATION_DURATION].value.i;
	current = THIS_VIEWPORT(s).tiledCount;

	// add spent time
	if(ts->isResizing)
		ts->msResizing += msSinceLastPaint;
	
	// Check if the animation hasnt finished yet
	if(ts->isResizing && ts->msResizing > td->animationDuration)
	{
		CompWindow *w = THIS_VIEWPORT(s).firstTiled;
		while(w)
		{
			TILE_WINDOW(w);
			if(tw->isResizing)
				tw->isResizing = FALSE;

			w = tw->next;
		}
		ts->isResizing = FALSE;
		ts->msResizing = 0;
	}

	UNWRAP(ts, s, preparePaintScreen);
	(*s->preparePaintScreen) (s, msSinceLastPaint);
	WRAP(ts, s, preparePaintScreen, tilePreparePaintScreen);
}

static void tileDonePaintScreen(CompScreen * s)
{
	TILE_SCREEN(s);

	// hope this is ok
	if(ts->isResizing)
		damageScreen(s);

	UNWRAP(ts, s, donePaintScreen);
	(*s->donePaintScreen) (s);
	WRAP(ts, s, donePaintScreen, tileDonePaintScreen);
}

static Bool tilePaintScreen(CompScreen * s, const ScreenPaintAttrib * sa, Region region, int output, unsigned int mask)
{
	Bool status;

	TILE_SCREEN(s);
	TILE_DISPLAY(s->display);

	if(ts->isResizing)
		mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK;

	UNWRAP(ts, s, paintScreen);
	status = (*s->paintScreen) (s, sa, region, output, mask);
	WRAP(ts, s, paintScreen, tilePaintScreen);

	// Check if animation is enabled, there is resizing on screen and only outline should be drawn
	if(!td->opt[TILE_DISPLAY_OPTION_ANIMATE].value.b || !ts->isResizing || td->currentAnimationType != outline)
		return status;

	glPushMatrix();
	glLoadIdentity();

	prepareXCoords(s, output, -DEFAULT_Z_CAMERA);

	glLineWidth(4.0f);

	CompWindow *w = THIS_VIEWPORT(s).firstTiled;
	while(w && isTileWindow(w))
	{
		TILE_WINDOW(w);

		if(tw->isResizing)
		{
			// Coordinate = start +            speed          * elapsedTime
			// Coordinate = start + (target - start)/interval * elapsedTime
			// Draw outline

			int x = ((float)w->attrib.x - (float)tw->previousX)/td->animationDuration * ts->msResizing + tw->previousX;
			x -= w->input.left; // decoration
			int y = ((float)w->attrib.y - (float)tw->previousY)/td->animationDuration * ts->msResizing + tw->previousY;
			y -= w->input.top; // decoration
			int width = ((float)w->attrib.width - (float)tw->previousWidth)/td->animationDuration * ts->msResizing + tw->previousWidth;
			width += w->input.left + w->input.right; // decoration
			int height = ((float)w->attrib.height - (float)tw->previousHeight)/td->animationDuration * ts->msResizing + tw->previousHeight;
			height += w->input.top + w->input.bottom; //decoration

			glColor3us(tw->outlineColor[0]*0.66, tw->outlineColor[1]*0.66, tw->outlineColor[2]*0.66);
			glRecti(x, y + height, x + width, y);

			glColor3usv(tw->outlineColor);

			glBegin(GL_LINE_LOOP);
			glVertex3f(x, y, 0.0f);
			glVertex3f(x + width, y, 0.0f);
			glVertex3f(x + width, y + height, 0.0f);
			glVertex3f(x, y + height, 0.0f);
			glEnd();
		}

		w = tw->next;
	}

	glPopMatrix();
	glColor4usv(defaultColor);
	glLineWidth(1.0f);

	return status;
}

// Resize notify used when windows are tiled horizontally or vertically
static void tileResizeNotify(CompWindow * w, int dx, int dy, int dwidth,
							 int dheight, Bool preview)
{
	TILE_SCREEN(w->screen);
	TILE_WINDOW(w);
	TILE_DISPLAY(w->screen->display);

	UNWRAP(ts, w->screen, windowResizeNotify);
	(*w->screen->windowResizeNotify) (w, dx, dy, dwidth, dheight, preview);
	WRAP(ts, w->screen, windowResizeNotify, tileResizeNotify);

	if(!tw->resizedAlready)
	{
		tw->resizedAlready = True; // window is resized now
		return;
	}

	// Dont do anything if joining is disabled or windows are being resized
	if(preview || !td->opt[TILE_DISPLAY_OPTION_JOIN].value.b || ts->isResizing)
		return;

	if(THIS_VIEWPORT(w->screen).currentTileType == vert)
	{
		if(tw->prev)
		{
			placeWindow(tw->prev, tw->prev->attrib.x, tw->prev->attrib.y, w->attrib.x - tw->prev->attrib.x - w->input.left - tw->prev->input.right, tw->prev->height);
		}
		if(tw->next)
		{
			int currentX = w->attrib.x + w->width + w->input.right + tw->next->input.left;
			placeWindow(tw->next, currentX, tw->next->attrib.y, tw->next->width + tw->next->attrib.x - currentX, tw->next->height);
		}
	}
	else if(THIS_VIEWPORT(w->screen).currentTileType == horz)
	{
		if(tw->prev)
		{
			placeWindow(tw->prev, tw->prev->attrib.x, tw->prev->attrib.y, tw->prev->width, w->attrib.y - tw->prev->attrib.y - w->input.top - tw->prev->input.bottom);
		}
		if(tw->next)
		{
			int currentY = w->attrib.y + w->height + w->input.bottom + tw->next->input.top;
			placeWindow(tw->next, tw->next->attrib.x, currentY, tw->next->width, tw->next->height + tw->next->attrib.y - currentY);
		}
	}
	else if(THIS_VIEWPORT(w->screen).currentTileType == left)
	{
		if(!tw->next && tw->prev && dwidth) // last window - on the left
		{
			CompWindow *temp = THIS_VIEWPORT(w->screen).firstTiled;
			while(temp)
			{
				TILE_WINDOW(temp);
				if(!tw->next)
					break;

				XRectangle workArea;
				screenGetOutputDevWorkArea(w->screen, screenGetCurrentOutputDev(w->screen), &workArea);

				int currentX = workArea.x + w->serverX + w->serverWidth + w->input.right + temp->input.left;
				placeWindow(temp, currentX, temp->attrib.y, workArea.width - currentX - w->input.right, temp->attrib.height);
				temp = tw->next;
			}
		}
		else if(tw->next) // windows on the right
		{
			XRectangle workArea;
			screenGetOutputDevWorkArea(w->screen, screenGetCurrentOutputDev(w->screen), &workArea);

			CompWindow *temp = THIS_VIEWPORT(w->screen).firstTiled;
			while(temp)
			{
				TILE_WINDOW(temp);
				if(!tw->next) // left window, last window
				{
					placeWindow(temp, workArea.x + temp->input.left, temp->attrib.y, w->serverX - w->input.left - temp->input.left - temp->input.right - workArea.x, temp->attrib.height);
					break;
				}

				if(w->id != temp->id)
				{
					int x = temp->attrib.x;
					int y = temp->attrib.y;
					int width = temp->attrib.width;
					int height = temp->attrib.height;

					TileWindow * otw = GET_TILE_WINDOW(w, ts); // tilewindow from the resized window, tw is from temp 
					if(otw->prev && (temp->id == otw->prev->id))
						height = w->serverY - temp->attrib.y - w->input.top - temp->input.bottom;
					else if(otw->next && (temp->id == otw->next->id))
						y = w->serverY + w->serverHeight + w->input.bottom + temp->input.top;
					x = w->serverX;
					width = workArea.width + workArea.x - w->serverX - w->input.right;

					placeWindow(temp, x, y, width, height);
				}
				temp = tw->next;
			}
		}
	}
}

static Bool tileInitScreen(CompPlugin * p, CompScreen * s)
{
	TILE_DISPLAY(s->display);

	TileScreen *ts = (TileScreen *) calloc(1, sizeof(TileScreen));

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

	s->privates[td->screenPrivateIndex].ptr = ts;

	ts->decoWidth = 0;
	ts->decoHeight = 0;
	ts->isResizing = FALSE;
	ts->msResizing = 0;
	ts->oneDuration = 0;

	ts->viewports = calloc(s->hsize, sizeof(TileViewport));
	int i;
	for(i=0;i<s->hsize;i++)
		ts->viewports[i].currentTileType = none;

	// Wrap plugin functions
	WRAP(ts, s, paintScreen, tilePaintScreen);
	WRAP(ts, s, preparePaintScreen, tilePreparePaintScreen);
	WRAP(ts, s, donePaintScreen, tileDonePaintScreen);
	WRAP(ts, s, windowResizeNotify, tileResizeNotify);
	WRAP(ts, s, paintWindow, tilePaintWindow);

	addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_VERTICALLY].value.action);
	addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_HORIZONTALLY].value.action);
	addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_TILE].value. action);
	addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_CASCADE].value.action);
	addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_RESTORE].value.action);
	addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_TOGGLE].value.action);

	return TRUE;
}

static void tileFiniScreen(CompPlugin * p, CompScreen * s)
{
	TILE_SCREEN(s);
	TILE_DISPLAY(s->display);
	freeWindowPrivateIndex(s, ts->windowPrivateIndex);
	free(ts->viewports);

	//Restore the original function
	UNWRAP(ts, s, paintScreen);
	UNWRAP(ts, s, preparePaintScreen);
	UNWRAP(ts, s, donePaintScreen);
	UNWRAP(ts, s, windowResizeNotify);
	UNWRAP(ts, s, paintWindow);

	removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_HORIZONTALLY].value.action);
	removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_VERTICALLY].value.action);
	removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_TILE].value.action);
	removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_CASCADE].value.action);
	removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_RESTORE].value.action);
	removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_TOGGLE].value.action);

	//Free the pointer
	free(ts);
}

static Bool	tileSetDisplayOption(CompDisplay * display, char *name, CompOptionValue * value)
{
	CompOption *o;
	int index;

	TILE_DISPLAY(display);

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

	switch (index) {
		case TILE_DISPLAY_OPTION_VERTICALLY:
		case TILE_DISPLAY_OPTION_HORIZONTALLY:
		case TILE_DISPLAY_OPTION_TILE:
		case TILE_DISPLAY_OPTION_CASCADE:
		case TILE_DISPLAY_OPTION_RESTORE:
		case TILE_DISPLAY_OPTION_TOGGLE:
			if (setDisplayAction(display, o, value))
				return TRUE;
			break;
		case TILE_DISPLAY_OPTION_ANIMATE:
		case TILE_DISPLAY_OPTION_JOIN:
			if (compSetBoolOption(o, value))
				return TRUE;
			break;
		case TILE_DISPLAY_OPTION_ANIMATION_DURATION:
		case TILE_DISPLAY_OPTION_DELTA:
		case TILE_DISPLAY_OPTION_LEFT_OCCUPANCY:
			if (compSetIntOption(o, value))
				return TRUE;
			break;
		case TILE_DISPLAY_OPTION_EXCLUDE_LIST:
			if (compSetOptionList(o, value))
				return TRUE;
			break;
		case TILE_DISPLAY_OPTION_ANIMATION_TYPE:
			if (compSetStringOption(o, value))
			{
				int i;

				for (i = 0; i < NUM_ANIMATIONS; i++)
				{
					if (strcmp(o->value.s, animationTypeString[i]) == 0)
					{
						td->currentAnimationType = i;
						return TRUE;
					}
				}
			}
			break;
		case TILE_DISPLAY_OPTION_TOGGLE_TYPE:
			if (compSetStringOption(o, value))
			{
				int i;

				for (i = 0; i < TILE_TYPE_NUM; i++)
				{
					if (strcmp(o->value.s, tileTypeString[i]) == 0)
					{
						td->currentToggleType = i;
						return TRUE;
					}
				}
			}
			break;
		default:
			break;
	}

	return FALSE;
}

// this is resizeConstrainMinMax from resize.c, thanks to David Reveman/Nigel Cunningham
static void constrainMinMax(CompWindow * w, int width, int height, int *newWidth, int *newHeight)
{
	const XSizeHints *hints = &w->sizeHints;
	int min_width = 0;
	int min_height = 0;
	int max_width = MAXSHORT;
	int max_height = MAXSHORT;

	if ((hints->flags & PBaseSize) && (hints->flags & PMinSize))
	{
		min_width = hints->min_width;
		min_height = hints->min_height;
	}
	else if (hints->flags & PBaseSize)
	{
		min_width = hints->base_width;
		min_height = hints->base_height;
	}
	else if (hints->flags & PMinSize)
	{
		min_width = hints->min_width;
		min_height = hints->min_height;
	}

	if (hints->flags & PMaxSize)
	{
		max_width = hints->max_width;
		max_height = hints->max_height;
	}
#define CLAMP(v, min, max) ((v) <= (min) ? (min) : (v) >= (max) ? (max) : (v))

	/* clamp width and height to min and max values */
	width = CLAMP(width, min_width, max_width);
	height = CLAMP(height, min_height, max_height);

#undef CLAMP

	*newWidth = width;
	*newHeight = height;
}

// Moves window to [x,y] and resizes to width x height if no animation or starts animation
static Bool placeWindow(CompWindow *w, int x, int y, int width, int height)
{
	// window existence check
	if(!w)
		return FALSE;

	// this checks if the window isnt smaller than minimum size it has defined
	constrainMinMax(w, width, height, &width, &height);

	// check if the window isnt already where its going to be
	if(x == w->attrib.x && y == w->attrib.y && width == w->attrib.width && height == w->attrib.height)
		return TRUE;

	TILE_WINDOW(w);
	TILE_SCREEN(w->screen);
	TILE_DISPLAY(w->screen->display);

	// set previous coordinates for animation
	tw->previousX = w->attrib.x;
	tw->previousY = w->attrib.y;
	tw->previousWidth = w->attrib.width;
	tw->previousHeight = w->attrib.height;

	// set future coordinates for animation
	tw->futureX = x;
	tw->futureY = y;
	tw->futureWidth = width;
	tw->futureHeight = height;

	tw->resizedAlready = False; // window is not resized now

	if(!td->opt[TILE_DISPLAY_OPTION_ANIMATE].value.b)
		setWindowFutureSize(w);
	else
	{
		if(td->currentAnimationType != fade) // for now all animations except fade resize window before animation
		{
			setWindowFutureSize(w);
		}

		// set animation
		if(td->opt[TILE_DISPLAY_OPTION_ANIMATE].value.b)
		{
			ts->isResizing = TRUE;
			tw->isResizing = TRUE;
			ts->msResizing = 0;
			ts->oneDuration = td->animationDuration/THIS_VIEWPORT(w->screen).tiledCount;
		}
	}

	return TRUE;
}

static Bool setWindowFutureSize(CompWindow *w)
{
	TILE_WINDOW(w);
	TILE_SCREEN(w->screen);

	int x = tw->futureX;
	int y = tw->futureY;
	int width = tw->futureWidth;
	int height = tw->futureHeight;

	XWindowChanges xwc;
	xwc.x = x;
	xwc.y = y;
	xwc.width = width;
	xwc.height = height;

	if(THIS_VIEWPORT(w->screen).currentTileType == none)
	{
		maximizeWindow(w, tw->prevState);
	}
	else
	{
		maximizeWindow(w, 0);
	}

	// FIXME
	// restack window where it should be, under next window because maximization/restoration breaks it somehow
	if(tw->prevState&MAXIMIZE_STATE && tw->next)
		restackWindowBelow(w, tw->next);

	// if after restoring the window is maximized, then restore "restore data" :P
	if((w->state & MAXIMIZE_STATE) && THIS_VIEWPORT(w->screen).currentTileType == none)
	{
		xwc.x = x;
		xwc.y = y;
		xwc.width = width;
		xwc.height = height;

		// restore "restore data"
		saveVertRestoreData(w, &xwc);
		saveHorzRestoreData(w, &xwc);
	}
	else
		configureXWindow (w, CWHeight | CWWidth | CWY | CWX, &xwc);

	return TRUE;
}

// Heavily inspired by windowIs3D and isSDWindow, returns TRUE if the window is usable for tiling
static Bool isTileWindow(CompWindow * w)
{
	TILE_DISPLAY(w->screen->display);

	// Exclude windows from exclusion list
	CompOption *o = &td->opt[TILE_DISPLAY_OPTION_EXCLUDE_LIST];
	int i;
	for (i = 0; i < o->value.list.nValue; i++)
	{
		if (w->resClass && (strcmp(o->value.list.value[i].s, w->resClass) == 0))
			return FALSE;
	}

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

	if (w->state & CompWindowStateOffscreenMask)
		return FALSE;

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

	if (w->state & CompWindowStateSkipPagerMask)
		return FALSE;

	// Normal window
	if ((w->type & CompWindowTypeNormalMask)==CompWindowTypeNormalMask && !w->minimized && w->placed)
		return TRUE;

	return FALSE;
}

// Finds windows on desktop that are to be tiled
static Bool loadTiledWindows(CompScreen *s)
{
	TILE_SCREEN(s);

	int count = 0;
	int decoHeight = 0, decoWidth = 0;
	CompWindow *first = 0, *previous = 0;

	CompWindow *w;
	if(THIS_VIEWPORT(s).currentTileType != none)
	for (w = s->windows; w; w = w->next)
	{
		int x,y;
		defaultViewportForWindow(w,&x,&y);
		if(isTileWindow(w) && s->x == x)
		{
			if(previous)
			{
				TILE_WINDOW(previous);
				tw->next = w;
			}

			TILE_WINDOW(w);
			if(!first)
				first = w;

			tw->prev = previous;
			previous = w;
			tw->next = 0;
			count++;
			decoHeight = w->input.top + w->input.bottom;
			decoWidth = w->input.left + w->input.right;
		}
	}
	else // when reloading windows for restoration, in case window order was changed since last tiling
	for (w = s->windows; w; w = w->next)
	{
		TILE_WINDOW(w);
		int x,y;
		defaultViewportForWindow(w,&x,&y);
		if((tw->originalWidth || tw->originalHeight) && s->x == x)
		{
		// This fixes problem with window closing animation running while restoring window, causing beryl crash
			if(IPCS_GetBool(IPCS_OBJECT(w), tw->isOtherAnimationAtom))
				continue;

			if(previous)
			{
				TILE_WINDOW(previous);
				tw->next = w;
			}

			if(!first)
				first = w;

			tw->prev = previous;
			previous = w;
			tw->next = 0;
			count++;
			decoHeight = w->input.top + w->input.bottom;
			decoWidth = w->input.left + w->input.right;
		}
	}

	THIS_VIEWPORT(s).firstTiled = first;
	THIS_VIEWPORT(s).tiledCount = count;
	ts->decoHeight = decoHeight;
	ts->decoWidth = decoWidth;

	return TRUE;
}

// save window coordinates to use for restore
static Bool saveCoords(CompWindow *w)
{
	TILE_WINDOW(w);

	// if the window was maximized save "restore data" for later restoring
	if(w->state&MAXIMIZE_STATE)
	{
		XWindowChanges xwc;
		restoreVertRestoreData(w, &xwc);
		restoreHorzRestoreData(w, &xwc);

		tw->originalX = xwc.x;
		tw->originalY = xwc.y;
		tw->originalWidth = xwc.width;
		tw->originalHeight = xwc.height;
	}
	else
	{
		tw->originalX = w->serverX;
		tw->originalY = w->serverY;
		tw->originalWidth = w->serverWidth;
		tw->originalHeight = w->serverHeight;
	}

	// save state
	tw->prevState = w->state;

	return TRUE;
}

// Applies tiling/restoring
static Bool applyTiling(CompScreen *s)
{
	TILE_SCREEN(s);
	TILE_DISPLAY(s->display);
	if(ts->isResizing)
		return FALSE;

	loadTiledWindows(s);
	if(THIS_VIEWPORT(s).tiledCount>1)
	{
		XRectangle workArea;
		screenGetOutputDevWorkArea(s, screenGetCurrentOutputDev(s), &workArea);
	
		CompWindow *w = THIS_VIEWPORT(s).firstTiled;
		int i = 0;

		switch(THIS_VIEWPORT(s).currentTileType)
		{
			/*
				Tile into grid
			*/
			case tile :
			{
				int numWidth = ceil(sqrt(THIS_VIEWPORT(s).tiledCount));
				int numHeight = ceil((float)THIS_VIEWPORT(s).tiledCount/numWidth);
		
				int height = (workArea.height - ts->decoHeight*numHeight)/numHeight;
				int width = (workArea.width - ts->decoWidth*numWidth)/numWidth;

				int currentX = w->input.left + workArea.x;
				int currentY = w->input.top + workArea.y;

				while(w)
				{
					TILE_WINDOW(w);
					if(!tw->originalWidth || !tw->originalHeight)
						saveCoords(w);

					placeWindow(w, currentX, currentY, width, height);
					i++;

					if(i%numWidth==0)
					{
						currentX = w->input.left + workArea.x;
						currentY += height + ts->decoHeight;
					}
					else
						currentX += width + ts->decoWidth;

					w = tw->next;
				}
			}
			break;

			/*
				Tile left
			*/
			case left:
			{
				int x = 0, y = 0, wid = 0, hei = 0;
				int height = (workArea.height - ts->decoHeight*(THIS_VIEWPORT(s).tiledCount-1))/(THIS_VIEWPORT(s).tiledCount-1);
				int occupancy = td->opt[TILE_DISPLAY_OPTION_LEFT_OCCUPANCY].value.i;

				while(w)
				{
					TILE_WINDOW(w);
					if (!tw->next) // this is the last window in the list - the active/topmost window
					{
						x = workArea.x + w->input.left;
						y = workArea.y + w->input.top;
						wid = workArea.width * occupancy / 100;
						hei = workArea.height - w->input.top - w->input.bottom;
					}
					else
					{
						x = workArea.x + (w->input.left * 2) + w->input.right +
								(workArea.width * occupancy / 100);
						y = workArea.y + w->input.top*(i+1) + w->input.bottom*(i) + height*(i);
						wid = (workArea.width * (100 - occupancy) / 100) -
								((w->input.left + w->input.right) * 2);
						hei = height;
					}
					if(!tw->originalWidth || !tw->originalHeight)
						saveCoords(w);

					placeWindow(w, x, y, wid, hei);
					i++;
					w = tw->next;
				}
			}
			break;

			/*
			Tile vertically
			*/
			case vert:
			{
				int width = (workArea.width - ts->decoWidth*THIS_VIEWPORT(s).tiledCount)/THIS_VIEWPORT(s).tiledCount;
				int height = workArea.height - w->input.top - w->input.bottom;
				int y = w->input.top + workArea.y;

				while(w)
				{
					TILE_WINDOW(w);
					int x = workArea.x + w->input.left*(i+1) + w->input.right*i + width*i;
					if(!tw->originalWidth || !tw->originalHeight)
						saveCoords(w);

					placeWindow(w, x, y, width, height);
					i++;
					w = tw->next;
				}
			}
			break;

			/*
				Tile horizontally
			*/
			case horz:
			{
				int width = workArea.width - w->input.left - w->input.right;
				int height=(workArea.height - ts->decoHeight*THIS_VIEWPORT(s).tiledCount)/THIS_VIEWPORT(s).tiledCount;
				int x = w->input.left + workArea.x;

				while(w)
				{
					TILE_WINDOW(w);
					int y = workArea.y + w->input.top*(i+1) + w->input.bottom*i + height*i;
					if(!tw->originalWidth || !tw->originalHeight)
						saveCoords(w);

					placeWindow(w, x, y, width, height);
					i++;
					w = tw->next;
				}
			}
			break;

			/*
				Cascade
			*/
			case cascade:
			{
				int delta = td->opt[TILE_DISPLAY_OPTION_DELTA].value.i;
				int currentX = w->input.left + workArea.x;
				int currentY = w->input.top + workArea.y;
				int height = workArea.height - delta*(THIS_VIEWPORT(s).tiledCount - 1) - ts->decoHeight;
				int width = workArea.width - delta*(THIS_VIEWPORT(s).tiledCount - 1) - ts->decoWidth;

				while(w)
				{
					TILE_WINDOW(w);
					if(!tw->originalWidth || !tw->originalHeight)
						saveCoords(w);

					placeWindow(w, currentX, currentY, width, height);

					currentX += delta;
					currentY += delta;
					w = tw->next;
				}
			}
			break;

			/*
				Restore
			*/
			case none:
			{
				while(w)
				{
					TILE_WINDOW(w);

					placeWindow(w, tw->originalX, tw->originalY, tw->originalWidth, tw->originalHeight);

					tw->originalX = 0;
					tw->originalY = 0;
					tw->originalWidth = 0;
					tw->originalHeight = 0;

					w = tw->next;
				}
			}
			break;
		}
	}

	return TRUE;
}

static Bool tileTile(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0));

	if (s) {
		TILE_SCREEN(s);
		THIS_VIEWPORT(s).currentTileType = tile;
		applyTiling(s);
	}
	return FALSE;
}

static Bool tileVertically(CompDisplay * d, CompAction * ac, CompActionState state,CompOption * option, int nOption)
{
	CompScreen *s;
	s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0));

	if (s) {
		TILE_SCREEN(s);
		THIS_VIEWPORT(s).currentTileType = vert;
		applyTiling(s);
	}
	return FALSE;
}

static Bool tileHorizontally(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0));

	if (s) {
		TILE_SCREEN(s);
		THIS_VIEWPORT(s).currentTileType = horz;
		applyTiling(s);
	}
	return FALSE;
}

static Bool tileCascade(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0));

	if (s) {
		TILE_SCREEN(s);
		THIS_VIEWPORT(s).currentTileType = cascade;
		applyTiling(s);
	}
	return FALSE;
}

static Bool tileRestore(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0));

	if (s) {
		TILE_SCREEN(s);
		if(THIS_VIEWPORT(s).currentTileType != none)
		{
			THIS_VIEWPORT(s).currentTileType = none;
			applyTiling(s);
		}
	}
	return FALSE;
}

static Bool tileToggle(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0));

	if (s) {
		TILE_SCREEN(s);
		if(THIS_VIEWPORT(s).currentTileType != none)
		{
			THIS_VIEWPORT(s).currentTileType = none;
			applyTiling(s);
		}
		else
		{
			TILE_DISPLAY(d);
			THIS_VIEWPORT(s).currentTileType = td->currentToggleType;
			applyTiling(s);
		}
	}

	return FALSE;
}

static void tileDisplayInitOptions(TileDisplay * td)
{
	CompOption *o;

	o = &td->opt[TILE_DISPLAY_OPTION_VERTICALLY];
	o->name = "tile_vertically";
	o->group = N_("Key Bindings");
	o->subGroup = N_("Tile Windows Vertically");
	o->displayHints = "";
	o->shortDesc = N_("Tile Windows Vertically");
	o->longDesc = N_("Move and resize all visible windows so that they have full height, same width and occupy whole screen.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = tileVertically;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = 0;
	o->value.action.state = CompActionStateInitKey;
	// o->value.action.key.modifiers = TILE_VERTICALLY_DISPLAY_OPTION_INITIATE_MOD;
	// o->value.action.key.keysym =
	//		XStringToKeysym(TILE_VERTICALLY_DISPLAY_OPTION_INITIATE_KEY);

	o = &td->opt[TILE_DISPLAY_OPTION_HORIZONTALLY];
	o->name = "tile_horizontally";
	o->group = N_("Key Bindings");
	o->subGroup = N_("Tile Windows Horizontally");
	o->displayHints = "";
	o->shortDesc = N_("Tile Windows Horizontally");
	o->longDesc = N_("Move and resize all visible windows so that they have full width, same height and occupy whole screen.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = tileHorizontally;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = 0;
	o->value.action.state = CompActionStateInitKey;
	// o->value.action.key.modifiers = TILE_HORIZONTALLY_DISPLAY_OPTION_INITIATE_MOD;
	// o->value.action.key.keysym =
	//		XStringToKeysym(TILE_HORIZONTALLY_DISPLAY_OPTION_INITIATE_KEY);

	o = &td->opt[TILE_DISPLAY_OPTION_TILE];
	o->name = "tile_tile";
	o->group = N_("Key Bindings");
	o->subGroup = N_("Tile Windows");
	o->displayHints = "";
	o->shortDesc = N_("Tile Windows");
	o->longDesc = N_("Move and resize all visible windows both vertically and horizontally, so that the occupy whole screen and are in a grid.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = tileTile;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.key.modifiers = TILE_TILE_DISPLAY_OPTION_INITIATE_MOD;
	o->value.action.key.keysym =
			XStringToKeysym(TILE_TILE_DISPLAY_OPTION_INITIATE_KEY);

	o = &td->opt[TILE_DISPLAY_OPTION_CASCADE];
	o->name = "tile_cascade";
	o->group = N_("Key Bindings");
	o->subGroup = N_("Cascade Windows");
	o->displayHints = "";
	o->shortDesc = N_("Cascade Windows");
	o->longDesc = N_("Move and resize all visible windows with the delta value set for cascading.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = tileCascade;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.key.modifiers = TILE_CASCADE_DISPLAY_OPTION_INITIATE_MOD;
	o->value.action.key.keysym =
			XStringToKeysym(TILE_CASCADE_DISPLAY_OPTION_INITIATE_KEY);

	o = &td->opt[TILE_DISPLAY_OPTION_RESTORE];
	o->name = "tile_restore";
	o->group = N_("Key Bindings");
	o->subGroup = N_("Restore Windows");
	o->displayHints = "";
	o->shortDesc = N_("Restore Windows");
	o->longDesc = N_("Restore windows to original position before tiling.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = tileRestore;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.key.modifiers = TILE_RESTORE_DISPLAY_OPTION_INITIATE_MOD;
	o->value.action.key.keysym =
			XStringToKeysym(TILE_RESTORE_DISPLAY_OPTION_INITIATE_KEY);

	o = &td->opt[TILE_DISPLAY_OPTION_TOGGLE];
	o->name = "tile_toggle";
	o->group = N_("Key Bindings");
	o->subGroup = N_("Toggle tiling");
	o->displayHints = "";
	o->shortDesc = N_("Toggle windows tiling");
	o->longDesc = N_("Toggle between tiling and restoring.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = tileToggle;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.key.modifiers = TILE_TOGGLE_DISPLAY_OPTION_INITIATE_MOD;
	o->value.action.key.keysym =
			XStringToKeysym(TILE_TOGGLE_DISPLAY_OPTION_INITIATE_KEY);
	
	o = &td->opt[TILE_DISPLAY_OPTION_ANIMATE];
	o->advanced = False;
	o->name = "tile_animate";
	o->group = N_("Options");
	o->subGroup = N_("Animation");
	o->displayHints = "";
	o->shortDesc = N_("Animate windows when tiling");
	o->longDesc = N_("Adds animation to tiling process.");
	o->type = CompOptionTypeBool;
	o->value.b = TILE_DEFAULT_ANIMATE;

	o = &td->opt[TILE_DISPLAY_OPTION_TOGGLE_TYPE];
	o->advanced = False;
	o->name = "tile_toggle_type";
	o->group = N_("Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Tiling method used when toggling");
	o->longDesc = N_("Choose tiling type you want when using toggle.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(tileTypeString[TILE_DEFAULT_TOGGLE_TYPE]);
	o->rest.s.string = tileTypeString;
	o->rest.s.nString = TILE_TYPE_NUM;

	o = &td->opt[TILE_DISPLAY_OPTION_ANIMATION_TYPE];
	o->advanced = False;
	o->name = "tile_animation_type";
	o->group = N_("Options");
	o->subGroup = N_("Animation");
	o->displayHints = "";
	o->shortDesc = N_("Animation type");
	o->longDesc = N_("Select desired animation on tiling.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(animationTypeString[TILE_DEFAULT_ANIMATION]);
	o->rest.s.string = animationTypeString;
	o->rest.s.nString = NUM_ANIMATIONS;

	o = &td->opt[TILE_DISPLAY_OPTION_ANIMATION_DURATION];
	o->advanced = False;
	o->name = "tile_animation_duration";
	o->group = N_("Options");
	o->subGroup = N_("Animation");
	o->displayHints = "";
	o->shortDesc = N_("Animation Duration");
	o->longDesc = N_("Duration in millisecond the window resizing animation should take.");
	o->type = CompOptionTypeInt;
	o->value.i = TILE_ANIMATION_DURATION_DEFAULT;
	o->rest.i.min = TILE_ANIMATION_DURATION_MIN;
	o->rest.i.max = TILE_ANIMATION_DURATION_MAX;

	o = &td->opt[TILE_DISPLAY_OPTION_JOIN];
	o->advanced = False;
	o->name = "tile_join";
	o->group = N_("Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Join windows together (experimental !)");
	o->longDesc = N_("Tries to join the windows together when horizontal, vertical or left tiling is enabled so that when you resize a window surrounding windows resize accordingly. This may pwn your windows if you dont leave them enough space (will be fixed later).");
	o->type = CompOptionTypeBool;
	o->value.b = TILE_DEFAULT_JOIN;

	o = &td->opt[TILE_DISPLAY_OPTION_DELTA];
	o->advanced = False;
	o->name = "tile_delta";
	o->group = N_("Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Cascade Delta");
	o->longDesc = N_("Distance between windows when using cascade.");
	o->type = CompOptionTypeInt;
	o->value.i = TILE_DELTA_DEFAULT;
	o->rest.i.min = TILE_DELTA_MIN;
	o->rest.i.max = TILE_DELTA_MAX;

	o = &td->opt[TILE_DISPLAY_OPTION_LEFT_OCCUPANCY];
	o->advanced = False;
	o->name = "tile_left_occupancy";
	o->group = N_("Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Left Occupancy");
	o->longDesc = N_("Occupancy percentage for window placed left. This number is percentage of screen width, which the active window will have it as width when tiled. Applies to Left tiling type.");
	o->type = CompOptionTypeInt;
	o->value.i = TILE_LEFT_OCCUPANCY_DEFAULT;
	o->rest.i.min = TILE_LEFT_OCCUPANCY_MIN;
	o->rest.i.max = TILE_LEFT_OCCUPANCY_MAX;

	o = &td->opt[TILE_DISPLAY_OPTION_EXCLUDE_LIST];
	o->advanced = False;
	o->name = "exclude_list";
	o->group = N_("Options");
	o->subGroup = N_("Exclusions");
	o->displayHints = "";
	o->shortDesc = N_("WM_CLASS to exclude");
	o->longDesc = N_("Window classes which should not be tiled.");
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = 0;
	o->value.list.value = 0;
	o->rest.s.string = 0;
	o->rest.s.nString = 0;
}

static CompOption *tileGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display) {
		TILE_DISPLAY(display);

		*count = NUM_OPTIONS(td);
		return td->opt;
	} else {
		TileDisplay *td = malloc(sizeof(TileDisplay));
		tileDisplayInitOptions(td);

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

static Bool tileInitDisplay(CompPlugin * p, CompDisplay * d)
{
	//Generate a tile display
	TileDisplay *td = (TileDisplay *) malloc(sizeof(TileDisplay));
	//Allocate a private index
	td->screenPrivateIndex = allocateScreenPrivateIndex(d);

	//Check if its valid
	if (td->screenPrivateIndex < 0) {
		free(td);
		return FALSE;
	}

	tileDisplayInitOptions(td);

	td->animationDuration = 0;
	td->currentAnimationType = TILE_DEFAULT_ANIMATION;
	td->currentToggleType = TILE_DEFAULT_TOGGLE_TYPE;

	//Record the display
	d->privates[displayPrivateIndex].ptr = td;

	return TRUE;
}

static void tileFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	TILE_DISPLAY(d);

	//Free the private index
	freeScreenPrivateIndex(d, td->screenPrivateIndex);
	//Free the pointer
	free(td);
}

static Bool tileInitWindow(CompPlugin * p, CompWindow * w)
{
	TileWindow *tw;

	TILE_SCREEN(w->screen);

	tw = malloc(sizeof(TileWindow));
	if (!tw)
		return FALSE;

	tw->next = 0;
	tw->prev = 0;

	tw->originalX = 0;
	tw->originalY = 0;
	tw->originalWidth = 0;
	tw->originalHeight = 0;

	tw->previousX = 0;
	tw->previousY = 0;
	tw->previousWidth = 0;
	tw->previousHeight = 0;
	tw->isResizing = FALSE;
	tw->isOtherAnimationAtom = IPCS_GetAtom(IPCS_OBJECT(w), IPCS_BOOL, "IS_ANIMATED", True); // animation plugin sets is_animated to true while it animates the window 
	tw->prevState = 0;
	
	// random color, from group.c thanks to the author for doing it
	tw->outlineColor[0] = rand() % 0xFFFF;
	tw->outlineColor[1] = rand() % 0xFFFF;
	tw->outlineColor[2] = rand() % 0xFFFF;

	w->privates[ts->windowPrivateIndex].ptr = tw;

	return TRUE;
}

static void tileFiniWindow(CompPlugin * p, CompWindow * w)
{
	TILE_WINDOW(w);
	TILE_SCREEN(w->screen);

	if(tw->originalWidth > 0 && tw->originalHeight > 0)
	{
		// when one window is destroyed, join the linked list
		CompWindow *prev = tw->prev;
		CompWindow *next = tw->next;

		if(prev)
		{
			TileWindow *twprev = GET_TILE_WINDOW(prev, GET_TILE_SCREEN (prev->screen, GET_TILE_DISPLAY (prev->screen->display)));
			twprev->next = next;
		}
		else
		{
			THIS_VIEWPORT(w->screen).firstTiled = next;
		}
	}

	free(tw);
}

static Bool tileInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();

	if (displayPrivateIndex < 0)
		return FALSE;

	return TRUE;
}

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

CompPluginVTable tileVTable = {
	"tile",
	N_("Tile"),
	N_("Tile windows"),
	tileInit,
	tileFini,
	tileInitDisplay,
	tileFiniDisplay,
	tileInitScreen,
	tileFiniScreen,
	tileInitWindow,
	tileFiniWindow,
	tileGetDisplayOptions,
	tileSetDisplayOption,
	0,			/*tileGetScreenOptions */
	0,			/*tileSetScreenOption */
	0,
	0,
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins-unsupported"
#if BERYL_VERSION >= 35
	,"wm"
#endif
#if BERYL_VERSION >= 36
	,0,0
#endif
#if BERYL_VERSION >= 37
	,False
#endif
};

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


syntax highlighted by Code2HTML, v. 0.9.1