/*
 * Copyright © 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Author: David Reveman <davidr@novell.com>
 */

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

#include <beryl.h>

#define CLONE_INITIATE_BUTTON_DEFAULT           Button1
#define CLONE_INITIATE_BUTTON_MODIFIERS_DEFAULT (CompSuperMask | ShiftMask)

static int displayPrivateIndex;

#define CLONE_DISPLAY_OPTION_INITIATE 0
#define CLONE_DISPLAY_OPTION_NUM      1

typedef struct _CloneDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	CompOption opt[CLONE_DISPLAY_OPTION_NUM];
} CloneDisplay;

typedef struct _CloneClone
{
	int src;
	int dst;
	Region region;
	Window input;
} CloneClone;

typedef struct _CloneScreen
{
	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	PaintScreenProc paintScreen;
	PaintWindowProc paintWindow;
	OutputChangeNotifyProc outputChangeNotify;

	int grabIndex;
	Bool grab;

	float offset;

	Bool transformed;

	CloneClone *clone;
	int nClone;

	int x, y;
	int grabbedOutput;
	int src, dst;
} CloneScreen;

#define GET_CLONE_DISPLAY(d)				      \
    ((CloneDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define CLONE_DISPLAY(d)		     \
    CloneDisplay *cd = GET_CLONE_DISPLAY (d)

#define GET_CLONE_SCREEN(s, cd)				          \
    ((CloneScreen *) (s)->privates[(cd)->screenPrivateIndex].ptr)

#define CLONE_SCREEN(s)						           \
    CloneScreen *cs = GET_CLONE_SCREEN (s, GET_CLONE_DISPLAY (s->display))

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

static void cloneRemove(CompScreen * s, int i)
{
	CloneClone *clone;

	CLONE_SCREEN(s);

	clone = malloc(sizeof(CloneClone) * (cs->nClone - 1));
	if (clone)
	{
		int j, k = 0;

		for (j = 0; j < cs->nClone; j++)
			if (j != i)
				memcpy(&clone[k++], &cs->clone[j], sizeof(CloneClone));

		XDestroyRegion(cs->clone[i].region);
		XDestroyWindow(s->display->display, cs->clone[i].input);

		free(cs->clone);

		cs->clone = clone;
		cs->nClone--;
	}
}

static void cloneFinish(CompScreen * s)
{
	CloneClone *clone;
	int i;

	CLONE_SCREEN(s);

	cs->grab = FALSE;

	if (cs->src != cs->dst)
	{
		clone = NULL;

		/* check if we should replace current clone */
		for (i = 0; i < cs->nClone; i++)
		{
			if (cs->clone[i].dst == cs->dst)
			{
				clone = &cs->clone[i];
				break;
			}
		}

		/* no existing clone for this destination, we must allocate one */
		if (!clone)
		{
			Region region;

			region = XCreateRegion();
			if (region)
			{
				clone = realloc(cs->clone,
								sizeof(CloneClone) * (cs->nClone + 1));
				if (clone)
				{
					XSetWindowAttributes attr;
					int x, y;

					cs->clone = clone;
					clone = &cs->clone[cs->nClone++];
					clone->region = region;

					attr.override_redirect = TRUE;

					x = s->outputDev[cs->dst].region.extents.x1;
					y = s->outputDev[cs->dst].region.extents.y1;

					clone->input =
							XCreateWindow(s->display->
										  display, s->root,
										  x, y,
										  s->outputDev[cs->
													   dst].
										  width,
										  s->outputDev[cs->
													   dst].
										  height, 0, 0,
										  InputOnly,
										  CopyFromParent,
										  CWOverrideRedirect, &attr);
					XMapRaised(s->display->display, clone->input);
				}
				else
				{
					XDestroyRegion(region);
				}
			}
		}

		if (clone)
		{
			clone->src = cs->src;
			clone->dst = cs->dst;
		}
	}

	if (cs->grabbedOutput != cs->dst)
	{
		/* remove clone */
		for (i = 0; i < cs->nClone; i++)
		{
			if (cs->clone[i].dst == cs->grabbedOutput)
			{
				cloneRemove(s, i);
				break;
			}
		}
	}
}
static void clonePreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	int i;

	CLONE_SCREEN(s);

	if (cs->grab)
	{
		if (cs->grabIndex)
		{
			cs->offset -= msSinceLastPaint * 0.005f;
			if (cs->offset < 0.0f)
				cs->offset = 0.0f;
		}
		else
		{
			cs->offset += msSinceLastPaint * 0.005f;
			if (cs->offset >= 1.0f)
			{
				cs->offset = 1.0f;
				cloneFinish(s);
			}
		}

		damageScreen(s);
	}

	UNWRAP(cs, s, preparePaintScreen);
	(*s->preparePaintScreen) (s, msSinceLastPaint);
	WRAP(cs, s, preparePaintScreen, clonePreparePaintScreen);

	for (i = 0; i < cs->nClone; i++)
	{
		CompOutput *src = &s->outputDev[cs->clone[i].src];
		CompOutput *dst = &s->outputDev[cs->clone[i].dst];
		int dx, dy;

		dx = dst->region.extents.x1 - src->region.extents.x1;
		dy = dst->region.extents.y1 - src->region.extents.y1;

		if (s->damageMask & COMP_SCREEN_DAMAGE_REGION_MASK)
		{
			if (src->width != dst->width || src->height != dst->height)
			{
				XSubtractRegion(&dst->region,
								getEmptyRegion(), cs->clone[i].region);
				XUnionRegion(s->damage, cs->clone[i].region, s->damage);
				XSubtractRegion(&src->region,
								getEmptyRegion(), cs->clone[i].region);
			}
			else
			{
				XSubtractRegion(s->damage, &dst->region, cs->clone[i].region);
				XOffsetRegion(cs->clone[i].region, dx, dy);
				XUnionRegion(s->damage, cs->clone[i].region, s->damage);
				XSubtractRegion(s->damage, &src->region, cs->clone[i].region);
				XOffsetRegion(cs->clone[i].region, -dx, -dy);
			}
		}
		else
		{
			XSubtractRegion(&src->region, getEmptyRegion(),
							cs->clone[i].region);
		}
	}
}

static void cloneDonePaintScreen(CompScreen * s)
{
	CLONE_SCREEN(s);

	if (cs->grab && cs->offset)
		damageScreen(s);

	UNWRAP(cs, s, donePaintScreen);
	(*s->donePaintScreen) (s);
	WRAP(cs, s, donePaintScreen, cloneDonePaintScreen);
}

static Bool
clonePaintScreen(CompScreen * s,
				 const ScreenPaintAttrib * sAttrib,
				 Region region, int output, unsigned int mask)
{
	Bool status;
	int i, dst = output;

	CLONE_SCREEN(s);

	if (!cs->grab || cs->grabbedOutput != output)
	{
		for (i = 0; i < cs->nClone; i++)
		{
			if (cs->clone[i].dst == output)
			{
				region = cs->clone[i].region;
				dst = cs->clone[i].src;

				if (s->outputDev[dst].width !=
					s->outputDev[output].width
					|| s->outputDev[dst].height !=
					s->outputDev[output].height)
					cs->transformed = TRUE;
				else
					cs->transformed = FALSE;

				break;
			}
		}
	}

	UNWRAP(cs, s, paintScreen);
	status = (*s->paintScreen) (s, sAttrib, region, dst, mask);
	WRAP(cs, s, paintScreen, clonePaintScreen);

	if (cs->grab)
	{
		CompWindow *w;
		GLenum filter;
		float zoom1, zoom2x, zoom2y, x1, y1, x2, y2;
		float zoomX, zoomY;
		int dx, dy;

		zoom1 = 160.0f / s->outputDev[cs->src].height;

		x1 = cs->x - (s->outputDev[cs->src].region.extents.x1 * zoom1);
		y1 = cs->y - (s->outputDev[cs->src].region.extents.y1 * zoom1);

		x1 -= (s->outputDev[cs->src].width * zoom1) / 2;
		y1 -= (s->outputDev[cs->src].height * zoom1) / 2;

		if (cs->grabIndex)
		{
			x2 = s->outputDev[cs->grabbedOutput].region.
					extents.x1 - s->outputDev[cs->src].region.extents.x1;
			y2 = s->outputDev[cs->grabbedOutput].region.
					extents.y1 - s->outputDev[cs->src].region.extents.y1;

			zoom2x = (float)s->outputDev[cs->grabbedOutput].width /
					s->outputDev[cs->src].width;
			zoom2y = (float)s->outputDev[cs->grabbedOutput].
					height / s->outputDev[cs->src].height;
		}
		else
		{
			x2 = s->outputDev[cs->dst].region.extents.x1 -
					s->outputDev[cs->src].region.extents.x1;
			y2 = s->outputDev[cs->dst].region.extents.y1 -
					s->outputDev[cs->src].region.extents.y1;

			zoom2x = (float)s->outputDev[cs->dst].width /
					s->outputDev[cs->src].width;
			zoom2y = (float)s->outputDev[cs->dst].height /
					s->outputDev[cs->src].height;
		}

		/* XXX: hmm.. why do I need this.. */
		if (x2 < 0.0f)
			x2 *= zoom2x;
		if (y2 < 0.0f)
			y2 *= zoom2y;

		dx = x1 * (1.0f - cs->offset) + x2 * cs->offset;
		dy = y1 * (1.0f - cs->offset) + y2 * cs->offset;

		zoomX = zoom1 * (1.0f - cs->offset) + zoom2x * cs->offset;
		zoomY = zoom1 * (1.0f - cs->offset) + zoom2y * cs->offset;

		glPushMatrix();

		glTranslatef(-0.5f, -0.5f, -DEFAULT_Z_CAMERA);
		glScalef(1.0f / s->outputDev[output].width,
				 -1.0f / s->outputDev[output].height, 1.0f);
		glTranslatef(dx - s->outputDev[output].region.extents.x1,
					 dy - s->outputDev[output].region.extents.y2, 0.0f);
		glScalef(zoomX, zoomY, 1.0f);

		filter = s->display->textureFilter;

		if (cs->offset == 0.0f)
			s->display->textureFilter = GL_LINEAR_MIPMAP_LINEAR;

		for (w = s->windows; w; w = w->next)
		{
			if (w->destroyed)
				continue;

			if (!w->shaded)
			{
				if (w->attrib.map_state != IsViewable || !w->damaged)
					continue;
			}

			(*s->paintWindow) (w, &w->paint,
							   &s->outputDev[cs->src].region,
							   PAINT_WINDOW_ON_TRANSFORMED_SCREEN_MASK);
		}

		s->display->textureFilter = filter;

		glPopMatrix();
	}

	return status;
}

static Bool
clonePaintWindow(CompWindow * w,
				 const WindowPaintAttrib * attrib,
				 Region region, unsigned int mask)
{
	Bool status;

	CLONE_SCREEN(w->screen);

	if (cs->nClone && cs->transformed)
		mask |= PAINT_WINDOW_ON_TRANSFORMED_SCREEN_MASK;

	UNWRAP(cs, w->screen, paintWindow);
	status = (*w->screen->paintWindow) (w, attrib, region, mask);
	WRAP(cs, w->screen, paintWindow, clonePaintWindow);

	return status;
}

static Bool
cloneInitiate(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)
	{
		int i;

		CLONE_SCREEN(s);

		if (cs->grab || otherScreenGrabExist(s, "clone", 0))
			return FALSE;

		if (!cs->grabIndex)
			cs->grabIndex = pushScreenGrab(s, None, "clone");

		cs->grab = TRUE;

		cs->x = getIntOptionNamed(option, nOption, "x", 0);
		cs->y = getIntOptionNamed(option, nOption, "y", 0);

		cs->src = cs->grabbedOutput = outputDeviceForPoint(s, cs->x, cs->y);

		/* trace source */
		i = 0;
		while (i < cs->nClone)
		{
			if (cs->clone[i].dst == cs->src)
			{
				cs->src = cs->clone[i].src;
				i = 0;
			}
			else
			{
				i++;
			}
		}

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

	return FALSE;
}

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

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

	for (s = d->screens; s; s = s->next)
	{
		CLONE_SCREEN(s);

		if (xid && s->root != xid)
			continue;

		if (cs->grabIndex)
		{
			int x, y;

			removeScreenGrab(s, cs->grabIndex, NULL);
			cs->grabIndex = 0;

			x = getIntOptionNamed(option, nOption, "x", 0);
			y = getIntOptionNamed(option, nOption, "y", 0);

			cs->dst = outputDeviceForPoint(s, x, y);

			damageScreen(s);
		}
	}

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

	return FALSE;
}

static void cloneSetStrutsForCloneWindow(CompScreen * s, CloneClone * clone)
{
	CompOutput *output = &s->outputDev[clone->dst];
	XRectangle *rect = NULL;
	CompStruts *struts;
	CompWindow *w;

	w = findWindowAtScreen(s, clone->input);
	if (!w)
		return;

	struts = malloc(sizeof(CompStruts));
	if (!struts)
		return;

	if (w->struts)
		free(w->struts);

	struts->left.x = 0;
	struts->left.y = 0;
	struts->left.width = 0;
	struts->left.height = s->height;

	struts->right.x = s->width;
	struts->right.y = 0;
	struts->right.width = 0;
	struts->right.height = s->height;

	struts->top.x = 0;
	struts->top.y = 0;
	struts->top.width = s->width;
	struts->top.height = 0;

	struts->bottom.x = 0;
	struts->bottom.y = s->height;
	struts->bottom.width = s->width;
	struts->bottom.height = 0;

	/* create struts relative to a screen edge that this output is next to */
	if (output->region.extents.x1 == 0)
		rect = &struts->left;
	else if (output->region.extents.x2 == s->width)
		rect = &struts->right;
	else if (output->region.extents.y1 == 0)
		rect = &struts->top;
	else if (output->region.extents.y2 == s->height)
		rect = &struts->bottom;

	if (rect)
	{
		rect->x = output->region.extents.x1;
		rect->y = output->region.extents.y1;
		rect->width = output->width;
		rect->height = output->height;
	}

	w->struts = struts;
}

static void cloneHandleMotionEvent(CompScreen * s, int xRoot, int yRoot)
{
	CLONE_SCREEN(s);

	if (cs->grabIndex)
	{
		cs->x = xRoot;
		cs->y = yRoot;

		damageScreen(s);
	}
}

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

	CLONE_DISPLAY(d);

	switch (event->type)
	{
	case MotionNotify:
		s = findScreenAtDisplay(d, event->xmotion.root);
		if (s)
			cloneHandleMotionEvent(s, d->pointerX, d->pointerY);
		break;
	case EnterNotify:
	case LeaveNotify:
		s = findScreenAtDisplay(d, event->xcrossing.root);
		if (s)
			cloneHandleMotionEvent(s, d->pointerX, d->pointerY);
	default:
		break;
	}

	UNWRAP(cd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(cd, d, handleEvent, cloneHandleEvent);

	switch (event->type)
	{
	case CreateNotify:
		s = findScreenAtDisplay(d, event->xcreatewindow.parent);
		if (s)
		{
			int i;

			CLONE_SCREEN(s);

			for (i = 0; i < cs->nClone; i++)
				if (event->xcreatewindow.window == cs->clone[i].input)
					cloneSetStrutsForCloneWindow(s, &cs->clone[i]);
		}
	default:
		break;
	}
}

static void cloneOutputChangeNotify(CompScreen * s)
{
	int i;

	CLONE_SCREEN(s);

	/* remove clones with destination or source that doesn't exist */
	for (i = 0; i < cs->nClone; i++)
	{
		if (cs->clone[i].dst >= s->nOutputDev ||
			cs->clone[i].src >= s->nOutputDev)
		{
			cloneRemove(s, i);
			i = 0;
			continue;
		}
	}

	UNWRAP(cs, s, outputChangeNotify);
	(*s->outputChangeNotify) (s);
	WRAP(cs, s, outputChangeNotify, cloneOutputChangeNotify);
}

static void cloneDisplayInitOptions(CloneDisplay * cd)
{
	CompOption *o;

	o = &cd->opt[CLONE_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Misc. settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Clone Selection");
	o->longDesc = N_("Initiate Clone selection.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = cloneInitiate;
	o->value.action.terminate = cloneTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.button.modifiers =
			CLONE_INITIATE_BUTTON_MODIFIERS_DEFAULT;
	o->value.action.button.button = CLONE_INITIATE_BUTTON_DEFAULT;
}

static CompOption *cloneGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		CLONE_DISPLAY(display);

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

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

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

	CLONE_DISPLAY(display);

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

	switch (index)
	{
	case CLONE_DISPLAY_OPTION_INITIATE:
		if (setDisplayAction(display, o, value))
			return TRUE;
	default:
		break;
	}

	return FALSE;
}

static Bool cloneInitDisplay(CompPlugin * p, CompDisplay * d)
{
	CloneDisplay *cd;

	cd = malloc(sizeof(CloneDisplay));
	if (!cd)
		return FALSE;

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

	cloneDisplayInitOptions(cd);

	WRAP(cd, d, handleEvent, cloneHandleEvent);

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

	return TRUE;
}

static void cloneFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	CLONE_DISPLAY(d);

	freeScreenPrivateIndex(d, cd->screenPrivateIndex);

	UNWRAP(cd, d, handleEvent);

	free(cd);
}

static Bool cloneInitScreen(CompPlugin * p, CompScreen * s)
{
	CloneScreen *cs;

	CLONE_DISPLAY(s->display);

	cs = malloc(sizeof(CloneScreen));
	if (!cs)
		return FALSE;

	cs->grabIndex = 0;
	cs->grab = FALSE;

	cs->offset = 1.0f;

	cs->transformed = FALSE;

	cs->nClone = 0;
	cs->clone = NULL;

	cs->src = 0;

	addScreenAction(s, &cd->opt[CLONE_DISPLAY_OPTION_INITIATE].value.action);

	WRAP(cs, s, preparePaintScreen, clonePreparePaintScreen);
	WRAP(cs, s, donePaintScreen, cloneDonePaintScreen);
	WRAP(cs, s, paintScreen, clonePaintScreen);
	WRAP(cs, s, paintWindow, clonePaintWindow);
	WRAP(cs, s, outputChangeNotify, cloneOutputChangeNotify);

	s->privates[cd->screenPrivateIndex].ptr = cs;

	return TRUE;
}

static void cloneFiniScreen(CompPlugin * p, CompScreen * s)
{
	CLONE_SCREEN(s);


	UNWRAP(cs, s, preparePaintScreen);
	UNWRAP(cs, s, donePaintScreen);
	UNWRAP(cs, s, paintScreen);
	UNWRAP(cs, s, paintWindow);
	UNWRAP(cs, s, outputChangeNotify);

	free(cs);
}

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

	return TRUE;
}

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

CompPluginVTable cloneVTable = {
	"clone",
	N_("Clone Output"),
	N_("Output clone handler"),
	cloneInit,
	cloneFini,
	cloneInitDisplay,
	cloneFiniDisplay,
	cloneInitScreen,
	cloneFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	cloneGetDisplayOptions,
	cloneSetDisplayOption,
	0,							/* GetScreenOptions */
	0,							/* SetScreenOption */
	0,							/* Deps */
	0,							/* nDeps */
	0,							/* Features */
	0,							/* nFeatures */
	BERYL_ABI_INFO,
	"beryl-plugins",
	"desktop",
	0,
	0,
	False,
};

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


syntax highlighted by Code2HTML, v. 0.9.1