/*
 *
 * Compiz scale plugin addon plugin
 *
 * scaleaddon.c
 *
 * Copyright : (C) 2007 by Danny Baumann
 * E-mail    : maniac@opencompositing.org
 *
 * Organic scale mode taken from Beryl's scale.c, written by
 * Copyright : (C) 2006 Diogo Ferreira
 * E-mail    : diogo@underdev.org
 *
 * Rounded corner drawing taken from wall.c:
 * Copyright : (C) 2007 Robert Carr
 * E-mail    : racarr@beryl-project.org
 *
 * 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.
 *
 *
 */

#include <math.h>
#include <string.h>
#include <X11/Xatom.h>

#include <compiz.h>
#include <scale.h>
#include <text.h>

#include "scaleaddon_options.h"

static int displayPrivateIndex;
static int scaleDisplayPrivateIndex;

#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)

typedef struct _ScaleAddonDisplay {
    int screenPrivateIndex;

    HandleEventProc       handleEvent;
    HandleCompizEventProc handleCompizEvent;

    Window lastHoveredWindow;
} ScaleAddonDisplay;

typedef struct _ScaleAddonScreen {
    int windowPrivateIndex;

    ScaleLayoutSlotsAndAssignWindowsProc layoutSlotsAndAssignWindows;
    ScalePaintDecorationProc		 scalePaintDecoration;

    Pixmap      textPixmap;
    CompTexture textTexture;

    int textWidth;
    int textHeight;

    float scale;
} ScaleAddonScreen;

typedef struct _ScaleAddonWindow {
    ScaleSlot origSlot;

    Bool rescaled;

    CompWindow *oldAbove;
} ScaleAddonWindow;

#define GET_ADDON_DISPLAY(d)				          \
    ((ScaleAddonDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define ADDON_DISPLAY(d)		          \
    ScaleAddonDisplay *ad = GET_ADDON_DISPLAY (d)

#define GET_ADDON_SCREEN(s, ad)				              \
    ((ScaleAddonScreen *) (s)->privates[(ad)->screenPrivateIndex].ptr)

#define ADDON_SCREEN(s)						               \
    ScaleAddonScreen *as = GET_ADDON_SCREEN (s, GET_ADDON_DISPLAY (s->display))

#define GET_ADDON_WINDOW(w, as)				              \
    ((ScaleAddonWindow *) (w)->privates[(as)->windowPrivateIndex].ptr)

#define ADDON_WINDOW(w)						                 \
    ScaleAddonWindow *aw = GET_ADDON_WINDOW (w,                                  \
	    GET_ADDON_SCREEN (w->screen, GET_ADDON_DISPLAY (w->screen->display)))


static void
scaleaddonFreeWindowTitle (CompScreen *s)
{
    ADDON_SCREEN (s);

    if (!as->textPixmap)
	return;

    releasePixmapFromTexture(s, &as->textTexture);
    initTexture (s, &as->textTexture);
    XFreePixmap (s->display->display, as->textPixmap);
    as->textPixmap = None;
}

static void
scaleaddonRenderWindowTitle (CompWindow *w)
{
    CompTextAttrib tA;
    float          scale;
    int            stride;
    void*          data;

    ADDON_SCREEN (w->screen);
    SCALE_WINDOW (w);

    scaleaddonFreeWindowTitle(w->screen);

    if (!scaleaddonGetWindowTitle (w->screen))
	return;

    scale = sw->slot ? sw->slot->scale : sw->scale;
    tA.maxwidth = (w->attrib.width * scale) -
	          (2 * scaleaddonGetBorderSize (w->screen));
    tA.maxheight = (w->attrib.height * scale) -
     	           (2 * scaleaddonGetBorderSize (w->screen));
    tA.screen = w->screen;
    tA.size = scaleaddonGetTitleSize (w->screen);
    tA.color[0] = scaleaddonGetFontColorRed (w->screen);
    tA.color[1] = scaleaddonGetFontColorGreen (w->screen);
    tA.color[2] = scaleaddonGetFontColorBlue (w->screen);
    tA.color[3] = scaleaddonGetFontColorAlpha (w->screen);
    tA.style = (scaleaddonGetTitleBold (w->screen)) ?
	       TEXT_STYLE_BOLD : TEXT_STYLE_NORMAL;
    tA.family = "Sans";
    tA.ellipsize = TRUE;

    tA.renderMode = TextRenderWindowTitle;
    tA.data = (void*)w->id;

    if ((*w->screen->display->fileToImage) (w->screen->display, TEXT_ID, (char *)&tA,
					    &as->textWidth, &as->textHeight, &stride, &data))
    {
	as->textPixmap = (Pixmap)data;
	bindPixmapToTexture (w->screen, &as->textTexture, as->textPixmap,
			     as->textWidth, as->textHeight, 32);
    }
    else
    {
	as->textPixmap = None;
	as->textWidth = 0;
	as->textHeight = 0;
    }
}

static void
scaleaddonDrawWindowTitle (CompWindow *w)
{
    ADDON_SCREEN (w->screen);
    SCALE_WINDOW (w);

    GLboolean wasBlend;
    GLint     oldBlendSrc, oldBlendDst;

    float width = as->textWidth;
    float height = as->textHeight;
    float border = scaleaddonGetBorderSize (w->screen);

    float x = sw->tx + w->attrib.x +
	      ((w->attrib.width * sw->scale) / 2) - (as->textWidth / 2);
    float y = sw->ty + w->attrib.y +
	      ((w->attrib.height * sw->scale) / 2) - (as->textHeight / 2);

    x = floor (x);
    y = floor (y);
	
    wasBlend = glIsEnabled (GL_BLEND);
    glGetIntegerv (GL_BLEND_SRC, &oldBlendSrc);
    glGetIntegerv (GL_BLEND_DST, &oldBlendDst);

    if (!wasBlend)
	glEnable (GL_BLEND);

    glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

    glColor4us (scaleaddonGetBackColorRed (w->screen),
		scaleaddonGetBackColorGreen (w->screen),
		scaleaddonGetBackColorBlue (w->screen),
		scaleaddonGetBackColorAlpha (w->screen));

    glPushMatrix ();

    glTranslatef (x, y - height, 0.0f);
    glRectf (0.0f, height, width, 0.0f);
    glRectf (0.0f, 0.0f, width, -border);
    glRectf (0.0f, height + border, width, height);
    glRectf (-border, height, 0.0f, 0.0f);
    glRectf (width, height, width + border, 0.0f);
    glTranslatef (-border, -border, 0.0f);

#define CORNER(a,b) \
    for (k = a; k < b; k++) \
    {\
	float rad = k* (3.14159 / 180.0f);\
	glVertex2f (0.0f, 0.0f);\
	glVertex2f (cos (rad) * border, sin (rad) * border);\
	glVertex2f (cos ((k - 1) * (3.14159 / 180.0f)) * border, \
		    sin ((k - 1) * (3.14159 / 180.0f)) * border);\
    }

    /* Rounded corners */
    int k;

    glTranslatef (border, border, 0.0f);
    glBegin (GL_TRIANGLES);
    CORNER (180, 270) glEnd();
    glTranslatef (-border, -border, 0.0f);

    glTranslatef (width + border, border, 0.0f);
    glBegin (GL_TRIANGLES);
    CORNER (270, 360) glEnd();
    glTranslatef (-(width + border), -border, 0.0f);

    glTranslatef (border, height + border, 0.0f);
    glBegin (GL_TRIANGLES);
    CORNER (90, 180) glEnd();
    glTranslatef (-border, -(height + border), 0.0f);

    glTranslatef (width + border, height + border, 0.0f);
    glBegin (GL_TRIANGLES);
    CORNER (0, 90) glEnd();
    glTranslatef (-(width + border), -(height + border), 0.0f);

    glPopMatrix ();

#undef CORNER

    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glColor4f (1.0, 1.0, 1.0, 1.0);

    enableTexture (w->screen, &as->textTexture, COMP_TEXTURE_FILTER_GOOD);

    CompMatrix *m = &as->textTexture.matrix;

    glBegin (GL_QUADS);

    glTexCoord2f (COMP_TEX_COORD_X(m, 0),COMP_TEX_COORD_Y(m ,0));
    glVertex2f (x, y - height);
    glTexCoord2f (COMP_TEX_COORD_X(m, 0),COMP_TEX_COORD_Y(m, height));
    glVertex2f (x, y);
    glTexCoord2f (COMP_TEX_COORD_X(m, width),COMP_TEX_COORD_Y(m, height));
    glVertex2f (x + width, y);
    glTexCoord2f (COMP_TEX_COORD_X(m, width),COMP_TEX_COORD_Y(m, 0));
    glVertex2f (x + width, y - height);

    glEnd ();

    disableTexture (w->screen, &as->textTexture);
    glColor4usv (defaultColor);

    if (!wasBlend)
	glDisable (GL_BLEND);
    glBlendFunc (oldBlendSrc, oldBlendDst);
}

static void
scaleaddonDrawWindowHighlight (CompWindow *w)
{
    SCALE_WINDOW (w);
    ADDON_WINDOW (w);

    GLboolean wasBlend;
    GLint     oldBlendSrc, oldBlendDst;

    if (aw->rescaled)
	return;

    float x      = sw->tx + w->attrib.x - (w->input.left * sw->scale);
    float y      = sw->ty + w->attrib.y - (w->input.top * sw->scale);
    float width  = WIN_W(w) * sw->scale;
    float height = WIN_H(w) * sw->scale;

    /* we use a poor replacement for roundf()
     * (available in C99 only) here */
    x = floor (x + 0.5f);
    y = floor (y + 0.5f);

    wasBlend = glIsEnabled (GL_BLEND);
    glGetIntegerv (GL_BLEND_SRC, &oldBlendSrc);
    glGetIntegerv (GL_BLEND_DST, &oldBlendDst);

    if (!wasBlend)
	glEnable (GL_BLEND);

    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glColor4us (scaleaddonGetHighlightColorRed (w->screen),
		scaleaddonGetHighlightColorGreen (w->screen),
		scaleaddonGetHighlightColorBlue (w->screen),
		scaleaddonGetHighlightColorAlpha (w->screen));

    glRectf (x, y + height, x + width, y);

    glColor4usv (defaultColor);

    if (!wasBlend)
	glDisable (GL_BLEND);
    glBlendFunc (oldBlendSrc, oldBlendDst);
}

static void
scaleaddonCheckHoveredWindow (CompScreen *s)
{
    CompDisplay *d = s->display;

    ADDON_DISPLAY (d);
    SCALE_DISPLAY (d);

    if (sd->hoveredWindow != ad->lastHoveredWindow)
    {
	CompWindow *w, *lw;

	w = findWindowAtDisplay (d, sd->hoveredWindow);
	if (w)
	{
	    scaleaddonRenderWindowTitle (w);
	    addWindowDamage (w);
	}
	else
	    scaleaddonFreeWindowTitle (s);

	lw = findWindowAtDisplay (d, ad->lastHoveredWindow);
	if (lw)
	    addWindowDamage (lw);

	ad->lastHoveredWindow = sd->hoveredWindow;
    }
}

static CompWindow *
scaleaddonCheckForWindowAt (CompScreen * s, int x, int y)
{
    float      x1, y1, x2, y2;
    CompWindow *w;

    for (w = s->reverseWindows; w; w = w->prev)
    {
        SCALE_WINDOW(w);

        if (sw->slot)
	{
	    x1 = sw->tx + w->attrib.x - w->input.left * sw->scale;
            y1 = sw->ty + w->attrib.y - w->input.top * sw->scale;
            x2 = sw->tx + w->attrib.x +
		 (w->width + w->input.right) * sw->scale;
            y2 = sw->ty + w->attrib.y +
		 (w->height + w->input.bottom) * sw->scale;

            if (x1 <= x && y1 <= y && x2 > x && y2 > y)
                return w;
        }
    }

    return NULL;
}

static Bool
scaleaddonCloseWindow (CompDisplay     *d,
	               CompAction      *action,
		       CompActionState state,
		       CompOption      *option,
		       int             nOption)
{
    CompScreen *s;
    Window     xid;

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

    s = findScreenAtDisplay (d, xid);
    if (s)
    {
	CompWindow *w;

	SCALE_SCREEN (s);

	if (!ss->grabIndex)
	    return FALSE;

	if (state & CompActionStateInitKey)
	{
	    SCALE_DISPLAY (d);
	    w = findWindowAtDisplay (d, sd->hoveredWindow);
	}
	else
	    w = scaleaddonCheckForWindowAt (s, pointerX, pointerY);

        if (w)
	{
	    closeWindow (w, getCurrentTimeFromDisplay (d));
	    return TRUE;
	}
    }

    return FALSE;
}

static Bool
scaleaddonZoomWindow (CompDisplay     *d,
		      CompAction      *action,
		      CompActionState state,
		      CompOption      *option,
		      int             nOption)
{
    CompScreen *s;
    Window     xid;

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

    s = findScreenAtDisplay (d, xid);
    if (s)
    {
	CompWindow *w;

	SCALE_SCREEN (s);

	if (!ss->grabIndex)
	    return FALSE;

	if (state & CompActionStateInitKey)
	{
	    SCALE_DISPLAY (d);
	    w = findWindowAtDisplay (d, sd->hoveredWindow);
	}
	else
	    w = scaleaddonCheckForWindowAt (s, pointerX, pointerY);

        if (w)
	{
	    SCALE_WINDOW (w);
	    ADDON_WINDOW (w);

	    XRectangle outputRect;
	    BOX outputBox;
	    int head;

	    if (!sw->slot)
		return FALSE;

	    head = outputDeviceForPoint (s, sw->slot->x1, sw->slot->y1);
	    outputBox = w->screen->outputDev[head].region.extents;

	    outputRect.x      = outputBox.x1;
	    outputRect.y      = outputBox.y1;
	    outputRect.width  = outputBox.x2 - outputBox.x1;
	    outputRect.height = outputBox.y2 - outputBox.y1;

	    if (!aw->rescaled)
	    {
		aw->oldAbove = w->next;
		raiseWindow(w);
	
		/* backup old values */
		aw->origSlot = *sw->slot;

		aw->rescaled = TRUE;

		sw->slot->x1 = (outputRect.width / 2) - (WIN_W(w) / 2) +
			       w->input.left + outputRect.x;
		sw->slot->y1 = (outputRect.height / 2) - (WIN_H(w) / 2) +
			       w->input.top + outputRect.y;
		sw->slot->x2 = sw->slot->x1 + WIN_W(w);
		sw->slot->y2 = sw->slot->y1 + WIN_H(w);
		sw->slot->scale = 1.0f;
	    }
	    else
	    {
		if (aw->oldAbove)
		    restackWindowBelow (w, aw->oldAbove);

		aw->rescaled = FALSE;
		*(sw->slot) = aw->origSlot;
	    }

	    sw->adjust = TRUE;
	    ss->state = SCALE_STATE_OUT;

	    /* slot size may have changed, so
	     * update window title */
	    scaleaddonRenderWindowTitle (w);
	    damageScreen (w->screen);

	    return TRUE;
	}
    }

    return FALSE;
}

static void
scaleaddonHandleEvent (CompDisplay *d,
		       XEvent      *event)
{
    ADDON_DISPLAY (d);

    UNWRAP (ad, d, handleEvent);
    (*d->handleEvent) (d, event);
    WRAP (ad, d, handleEvent, scaleaddonHandleEvent);

    switch (event->type)
    {
    case PropertyNotify:
	{
	    SCALE_DISPLAY (d);
	    if (event->xproperty.window == sd->hoveredWindow &&
		event->xproperty.atom == XA_WM_NAME)
	    {
		CompWindow *w;

		w = findWindowAtDisplay (d, event->xproperty.window);
		if (w)
		{
		    SCALE_SCREEN (w->screen);
		    if (ss->grabIndex)
		    {
			scaleaddonRenderWindowTitle (w);
			addWindowDamage (w);
		    }
		}
	    }
	}
	break;
    case MotionNotify:
	{
	    CompScreen *s;
	    s = findScreenAtDisplay (d, event->xmotion.root);

	    if (s)
	    {
		SCALE_SCREEN (s);
		if (ss->grabIndex)
		    scaleaddonCheckHoveredWindow (s);
	    }
	}
	break;
    default:
	break;
    }
}

static void
scaleaddonScalePaintDecoration (CompWindow              *w,
				const WindowPaintAttrib *attrib,
				const CompTransform     *transform,
				Region                  region,
				unsigned int            mask)
{
    ADDON_SCREEN (w->screen);
    SCALE_SCREEN (w->screen);
    SCALE_DISPLAY (w->screen->display);

    UNWRAP (as, ss, scalePaintDecoration);
    (*ss->scalePaintDecoration) (w, attrib, transform, region, mask);
    WRAP (as, ss, scalePaintDecoration, scaleaddonScalePaintDecoration);

    scaleaddonCheckHoveredWindow (w->screen);

    if ((w->id == sd->hoveredWindow) &&
	((ss->state == SCALE_STATE_WAIT) || (ss->state == SCALE_STATE_OUT)))
    {
	if (scaleaddonGetWindowHighlight (w->screen))
	    scaleaddonDrawWindowHighlight (w);

	if (as->textPixmap)
	    scaleaddonDrawWindowTitle (w);
    }
}

static void
scaleaddonHandleCompizEvent (CompDisplay *d,
			     char        *pluginName,
			     char        *eventName,
			     CompOption  *option,
			     int         nOption)
{
    ADDON_DISPLAY(d);

    UNWRAP (ad, d, handleCompizEvent);
    (*d->handleCompizEvent) (d, pluginName, eventName, option, nOption);
    WRAP (ad, d, handleCompizEvent, scaleaddonHandleCompizEvent);

    if ((strcmp (pluginName, "scale") == 0) &&
	(strcmp (eventName, "activate") == 0))
    {
	Window xid = getIntOptionNamed (option, nOption, "root", 0);
	Bool activated = getIntOptionNamed (option, nOption, "activated", FALSE);
	CompScreen *s = findScreenAtDisplay (d, xid);

	if (s)
	{
	    if (activated)
	    {
		addScreenAction (s, scaleaddonGetClose (s->display));
		addScreenAction (s, scaleaddonGetZoom (s->display));
	    }
	    else
	    {
		CompWindow *w;

		for (w = s->windows; w; w = w->next)
		{
		    ADDON_WINDOW (w);
		    aw->rescaled = FALSE;
		}

		removeScreenAction (s, scaleaddonGetClose (s->display));
		removeScreenAction (s, scaleaddonGetZoom (s->display));
	    }
	}
    }
}

/**
 * experimental organic layout method
 * inspired by smallwindows (smallwindows.sf.net) by Jens Egeblad
 * */
#define ORGANIC_STEP 0.05

static int
organicCompareWindows (const void *elem1, const void *elem2)
{
    CompWindow *w1 = *((CompWindow **) elem1);
    CompWindow *w2 = *((CompWindow **) elem2);

    return (WIN_X(w1) + WIN_Y(w1)) - (WIN_X(w2) + WIN_Y(w2));
}

static double
layoutOrganicCalculateOverlap (CompScreen * s, int win, int x, int y)
{
    SCALE_SCREEN (s);
    ADDON_SCREEN (s);

    int i;
    int x1 = x;
    int y1 = y;
    int x2 = x1 + WIN_W (ss->windows[win]) * as->scale;
    int y2 = y1 + WIN_H (ss->windows[win]) * as->scale;
    int overlapx = 0, overlapy = 0;
    int xmin, xmax;
    int ymin, ymax;

    double result = -0.01;

    for (i = 0; i < ss->nWindows; i++)
    {
	if (i == win)
	    continue;
	overlapx = overlapy = 0;
	xmax = MAX (ss->slots[i].x1, x1);
	xmin = MIN (ss->slots[i].x1 + WIN_W(ss->windows[i]) * as->scale, x2);
	if (xmax <= xmin)
	    overlapx = xmin - xmax;

	ymax = MAX (ss->slots[i].y1, y1);
	ymin = MIN (ss->slots[i].y1 + WIN_H(ss->windows[i]) * as->scale, y2);

	if (ymax <= ymin)
	    overlapy = ymin - ymax;

	result += (double)overlapx * overlapy;
    }
    return result;
}

static double
layoutOrganicFindBestHorizontalPosition (CompScreen * s, int win,
					 int *bestx, int areaWidth)
{
    SCALE_SCREEN (s);
    ADDON_SCREEN (s);

    int i;
    int y1 = ss->slots[win].y1;
    int y2 = ss->slots[win].y1 + WIN_H(ss->windows[win]) * as->scale;

    double bestoverlap = 1e31, overlap;
    int w = WIN_W(ss->windows[win]) * as->scale;

    *bestx = ss->slots[win].x1;

    for (i = 0; i < ss->nWindows; i++)
    {
	if (i == win)
	    continue;

	if (ss->slots[i].y1 < y2 &&
	    ss->slots[i].y1 + WIN_H(ss->windows[i]) * as->scale > y1)
	{
	    if (ss->slots[i].x1 - w >= 0)
	    {
		double overlap = layoutOrganicCalculateOverlap (s, win,
								ss->slots[i].x1 - w, y1);

		if (overlap < bestoverlap)
		{
		    *bestx = ss->slots[i].x1 - w;
		    bestoverlap = overlap;
		}
	    }
	    if (WIN_W(ss->windows[i]) * as->scale +
		ss->slots[i].x1 + w < areaWidth)
	    {
		double overlap = layoutOrganicCalculateOverlap (s, win,
								ss->slots[i].x1 +
								WIN_W(ss->windows[i]) *
		     						as->scale, y1);

		if (overlap < bestoverlap)
		{
		    *bestx = ss->slots[i].x1 + WIN_W(ss->windows[i]) * as->scale;
		    bestoverlap = overlap;
		}
	    }
	}
    }

    overlap = layoutOrganicCalculateOverlap (s, win, 0, y1);
    if (overlap < bestoverlap)
    {
	*bestx = 0;
	bestoverlap = overlap;
    }

    overlap = layoutOrganicCalculateOverlap (s, win, areaWidth - w, y1);
    if (overlap < bestoverlap)
    {
	*bestx = areaWidth - w;
	bestoverlap = overlap;
    }

    return bestoverlap;
}

static double
layoutOrganicFindBestVerticalPosition (CompScreen * s, int win,
				       int *besty, int areaHeight)
{
    SCALE_SCREEN (s);
    ADDON_SCREEN (s);

    int i;
    int x1 = ss->slots[win].x1;
    int x2 = ss->slots[win].x1 + WIN_W(ss->windows[win]) * as->scale;

    double bestoverlap = 1e31, overlap;
    int h = WIN_H(ss->windows[win]) * as->scale;

    *besty = ss->slots[win].y1;

    for (i = 0; i < ss->nWindows; i++)
    {
	if (i == win)
	    continue;

	if (ss->slots[i].x1 < x2 &&
	    ss->slots[i].x1 + WIN_W(ss->windows[i]) * as->scale > x1)
	{
	    if (ss->slots[i].y1 - h >= 0 && ss->slots[i].y1 < areaHeight)
	    {
		double overlap = layoutOrganicCalculateOverlap (s, win, x1,
								ss->slots[i].y1 - h);
		if (overlap < bestoverlap)
		{
		    *besty = ss->slots[i].y1 - h;
		    bestoverlap = overlap;
		}
	    }
	    if (WIN_H(ss->windows[i]) * as->scale + ss->slots[i].y1 > 0 &&
		WIN_H(ss->windows[i]) * as->scale + h + ss->slots[i].y1 < areaHeight)
	    {
		double overlap = layoutOrganicCalculateOverlap (s, win, x1,
								WIN_H(ss->windows[i]) *
		     						as->scale +
								ss->slots[i].y1);

		if (overlap < bestoverlap)
		{
		    *besty = ss->slots[i].y1 + WIN_H(ss->windows[i]) * as->scale;
		    bestoverlap = overlap;
		}
	    }
	}
    }

    overlap = layoutOrganicCalculateOverlap (s, win, x1, 0);
    if (overlap < bestoverlap)
    {
	*besty = 0;
	bestoverlap = overlap;
    }

    overlap = layoutOrganicCalculateOverlap (s, win, x1, areaHeight - h);
    if (overlap < bestoverlap)
    {
	*besty = areaHeight - h;
	bestoverlap = overlap;
    }

    return bestoverlap;
}

static Bool
layoutOrganicLocalSearch (CompScreen * s, int areaWidth, int areaHeight)
{
    SCALE_SCREEN(s);
    Bool improvement;
    int i;
    double totaloverlap;

    do
    {
	improvement = FALSE;
	for (i = 0; i < ss->nWindows; i++)
	{
	    Bool improved;

	    do
	    {
		int newx, newy;
		double oldoverlap, overlaph, overlapv;

		improved = FALSE;
		oldoverlap = layoutOrganicCalculateOverlap (s, i,
 							    ss->slots[i].x1,
							    ss->slots[i].y1);

		overlaph =
		    layoutOrganicFindBestHorizontalPosition (s, i, &newx, areaWidth);
		overlapv =
		    layoutOrganicFindBestVerticalPosition (s, i, &newy, areaHeight);

		if (overlaph < oldoverlap - 0.1 ||
		    overlapv < oldoverlap - 0.1)
		{
		    improved = TRUE;
		    improvement = TRUE;
		    if (overlapv > overlaph)
			ss->slots[i].x1 = newx;
		    else
			ss->slots[i].y1 = newy;
		}

    	    }
	    while (improved);
	}
    }
    while (improvement);

    totaloverlap = 0.0;
    for (i = 0; i < ss->nWindows; i++)
    {
	totaloverlap += layoutOrganicCalculateOverlap (s,
						       i, ss->slots[i].x1,
						       ss->slots[i].y1);
    }
    return (totaloverlap > 0.1);
}

static void
layoutOrganicRemoveOverlap (CompScreen * s, int areaWidth, int areaHeight)
{
    int i;
    int spacing;
    CompWindow *w;

    SCALE_SCREEN (s);
    ADDON_SCREEN (s);

    spacing = ss->opt[SCALE_SCREEN_OPTION_SPACING].value.i;

    while (layoutOrganicLocalSearch (s, areaWidth, areaHeight))
    {
	for (i = 0; i < ss->nWindows; i++)
	{
	    int centerX, centerY;
	    int newX, newY, newWidth, newHeight;

	    w = ss->windows[i];

	    centerX = ss->slots[i].x1 + WIN_W(w) / 2;
	    centerY = ss->slots[i].y1 + WIN_H(w) / 2;

	    newWidth = (int)((1.0 - ORGANIC_STEP) *
			     (double)WIN_W(w)) - spacing / 2;
	    newHeight = (int)((1.0 - ORGANIC_STEP) *
			      (double)WIN_H(w)) - spacing / 2;
	    newX = centerX - (newWidth / 2);
	    newY = centerY - (newHeight / 2);

	    ss->slots[i].x1 = newX;
	    ss->slots[i].y1 = newY;
	    ss->slots[i].x2 = newX + WIN_W(w);
	    ss->slots[i].y2 = newY + WIN_H(w);
	}
	as->scale -= ORGANIC_STEP;
    }
}

static Bool
layoutOrganicThumbs(CompScreen * s)
{
    CompWindow *w;

    int i;
    int moMode;
    XRectangle workArea;

    SCALE_SCREEN (s);
    ADDON_SCREEN (s);

    moMode = ss->opt[SCALE_SCREEN_OPTION_MULTIOUTPUT_MODE].value.i;

    switch (moMode) {
    case SCALE_MOMODE_ALL:
	workArea = s->workArea;
	break;
    case SCALE_MOMODE_CURRENT:
    default:
	workArea = s->outputDev[s->currentOutputDev].workArea;
	break;
    }

    as->scale = 1.0f;

    qsort (ss->windows, ss->nWindows, sizeof(CompWindow *),
	   organicCompareWindows);

    for (i = 0; i < ss->nWindows; i++)
    {
	SCALE_WINDOW (ss->windows[i]);
	w = ss->windows[i];

	sw->slot = &ss->slots[i];
	ss->slots[i].x1 = WIN_X (w) - workArea.x;
	ss->slots[i].y1 = WIN_Y (w) - workArea.y;
	ss->slots[i].x2 = WIN_X (w) + WIN_W (w) - workArea.x;
	ss->slots[i].y2 = WIN_Y (w) + WIN_H (w) - workArea.y;

	if (ss->slots[i].x1 < 0)
	{
	    ss->slots[i].x2 += abs (ss->slots[i].x1);
	    ss->slots[i].x1 = 0;
	}
	if (ss->slots[i].x2 > workArea.width - workArea.x)
	{
	    ss->slots[i].x1 -= abs (ss->slots[i].x2 - workArea.width);
	    ss->slots[i].x2 = workArea.width - workArea.x;
	}

	if (ss->slots[i].y1 < 0)
	{
	    ss->slots[i].y2 += abs (ss->slots[i].y1);
	    ss->slots[i].y1 = 0;
	}
	if (ss->slots[i].y2 > workArea.height - workArea.y)
	{
	    ss->slots[i].y1 -= abs (ss->slots[i].y2 -
				    workArea.height - workArea.y);
	    ss->slots[i].y2 = workArea.height - workArea.y;
	}
    }

    ss->nSlots = ss->nWindows;

    layoutOrganicRemoveOverlap (s, workArea.width - workArea.x,
				workArea.height - workArea.y);
    for (i = 0; i < ss->nWindows; i++)
    {
	SCALE_WINDOW (ss->windows[i]);

	if (ss->type == ScaleTypeGroup)
	    raiseWindow (ss->windows[i]);

	ss->slots[i].x1 += ss->windows[i]->input.left + workArea.x;
	ss->slots[i].x2 += ss->windows[i]->input.left + workArea.x;
	ss->slots[i].y1 += ss->windows[i]->input.top + workArea.y;
	ss->slots[i].y2 += ss->windows[i]->input.top + workArea.y;
	sw->adjust = TRUE;
    }

    return TRUE;
}

static Bool
scaleaddonLayoutSlotsAndAssignWindows (CompScreen *s)
{
    Bool status;

    ADDON_SCREEN (s);
    SCALE_SCREEN (s);

    switch (scaleaddonGetLayoutMode (s))
    {
    case LayoutModeOrganicExperimental:
	status = layoutOrganicThumbs (s);
	break;
    case LayoutModeNormal:
    default:
	UNWRAP (as, ss, layoutSlotsAndAssignWindows);
	status = (*ss->layoutSlotsAndAssignWindows) (s);
	WRAP (as, ss, layoutSlotsAndAssignWindows,
	      scaleaddonLayoutSlotsAndAssignWindows);
	break;
    }

    return status;
}

static void
scaleaddonScreenOptionChanged (CompScreen              *s,
			       CompOption              *opt,
			       ScaleaddonScreenOptions num)
{
    switch (num)
    {
	case ScaleaddonScreenOptionTitleBold:
	case ScaleaddonScreenOptionTitleSize:
	case ScaleaddonScreenOptionBorderSize:
	case ScaleaddonScreenOptionFontColor:
	case ScaleaddonScreenOptionBackColor:
	    {
		ADDON_DISPLAY (s->display);

		scaleaddonFreeWindowTitle (s);
		ad->lastHoveredWindow = None;
	    }
	    break;
	default:
	    break;
    }
}

static Bool
scaleaddonInitDisplay (CompPlugin  *p,
	      	       CompDisplay *d)
{
    ScaleAddonDisplay *ad;
    CompPlugin        *scale = findActivePlugin ("scale");
    CompOption        *option;
    int               nOption;

    if (!scale || !scale->vTable->getDisplayOptions)
	return FALSE;

    option = (*scale->vTable->getDisplayOptions) (scale, d, &nOption);

    if (getIntOptionNamed (option, nOption, "abi", 0) != SCALE_ABIVERSION)
    {
	compLogMessage (d, "scaleaddon", CompLogLevelError,
			"scale ABI version mismatch");
	return FALSE;
    }

    scaleDisplayPrivateIndex = getIntOptionNamed (option, nOption, "index", -1);
    if (scaleDisplayPrivateIndex < 0)
	return FALSE;

    ad = malloc (sizeof (ScaleAddonDisplay));
    if (!ad)
	return FALSE;

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

    WRAP (ad, d, handleEvent, scaleaddonHandleEvent);
    WRAP (ad, d, handleCompizEvent, scaleaddonHandleCompizEvent);

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

    ad->lastHoveredWindow = None;

    scaleaddonSetCloseInitiate (d, scaleaddonCloseWindow);
    scaleaddonSetZoomInitiate (d, scaleaddonZoomWindow);

    return TRUE;
}

static void
scaleaddonFiniDisplay (CompPlugin  *p,
	      	       CompDisplay *d)
{
    ADDON_DISPLAY (d);

    UNWRAP (ad, d, handleEvent);
    UNWRAP (ad, d, handleCompizEvent);

    freeScreenPrivateIndex (d, ad->screenPrivateIndex);

    free (ad);
}

static Bool
scaleaddonInitScreen (CompPlugin *p,
	      	      CompScreen *s)
{
    ScaleAddonScreen *as;

    ADDON_DISPLAY (s->display);
    SCALE_SCREEN (s);

    as = malloc (sizeof (ScaleAddonScreen));
    if (!as)
	return FALSE;

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

    as->scale = 1.0f;

    as->textPixmap = None;
    initTexture (s, &as->textTexture);

    WRAP (as, ss, scalePaintDecoration, scaleaddonScalePaintDecoration);
    WRAP (as, ss, layoutSlotsAndAssignWindows,
	  scaleaddonLayoutSlotsAndAssignWindows);

    scaleaddonSetTitleBoldNotify (s, scaleaddonScreenOptionChanged);
    scaleaddonSetTitleSizeNotify (s, scaleaddonScreenOptionChanged);
    scaleaddonSetBorderSizeNotify (s, scaleaddonScreenOptionChanged);
    scaleaddonSetFontColorNotify (s, scaleaddonScreenOptionChanged);
    scaleaddonSetBackColorNotify (s, scaleaddonScreenOptionChanged);

    s->privates[ad->screenPrivateIndex].ptr = as;

    return TRUE;
}

static void
scaleaddonFiniScreen (CompPlugin *p,
	      	      CompScreen *s)
{
    ADDON_SCREEN (s);
    SCALE_SCREEN (s);

    UNWRAP (as, ss, scalePaintDecoration);
    UNWRAP (as, ss, layoutSlotsAndAssignWindows);

    scaleaddonFreeWindowTitle (s);

    freeWindowPrivateIndex (s, as->windowPrivateIndex);
    free (as);
}

static Bool
scaleaddonInitWindow (CompPlugin *p,
		      CompWindow *w)
{
    ScaleAddonWindow *aw;

    ADDON_SCREEN (w->screen);

    aw = malloc (sizeof (ScaleAddonWindow));
    if (!aw)
	return FALSE;

    aw->rescaled = FALSE;

    w->privates[as->windowPrivateIndex].ptr = aw;

    return TRUE;
}

static void
scaleaddonFiniWindow (CompPlugin *p,
		      CompWindow *w)
{
    ADDON_WINDOW (w);

    free (aw);
}

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

    return TRUE;
}

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

static int
scaleaddonGetVersion (CompPlugin *plugin,
	      	      int	 version)
{
    return ABIVERSION;
}

CompPluginVTable scaleaddonVTable = {
    "scaleaddon",
    scaleaddonGetVersion,
    0,
    scaleaddonInit,
    scaleaddonFini,
    scaleaddonInitDisplay,
    scaleaddonFiniDisplay,
    scaleaddonInitScreen,
    scaleaddonFiniScreen,
    scaleaddonInitWindow,
    scaleaddonFiniWindow,
    0,
    0,
    0,
    0
};

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


syntax highlighted by Code2HTML, v. 0.9.1