/*
 * 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>
 */

#ifdef HAVE_CONFIG_H
#  include "../config.h"
#endif

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

#include <beryl.h>

#define TEXTURE_SIZE 256

#define PROGRAM_BUMP	      0
#define PROGRAM_BUMP_RECT     1
#define PROGRAM_BUMP_SAT      2
#define PROGRAM_BUMP_SAT_RECT 3
#define PROGRAM_WATER	      4
#define PROGRAM_NUM	      5

#define TEXTURE_NUM  3

#define TINDEX(ws, i) (((ws)->tIndex + (i)) % TEXTURE_NUM)

#define CLAMP(v, min, max) \
    if ((v) > (max))	   \
(v) = (max);	   \
else if ((v) < (min))  \
(v) = (min)

#define WATER_INITIATE_MODIFIERS_DEFAULT (ControlMask | CompSuperMask)

#define WATER_TOGGLE_RAIN_KEY_DEFAULT       "F9"
#define WATER_TOGGLE_RAIN_MODIFIERS_DEFAULT ShiftMask

#define WATER_TOGGLE_WIPER_KEY_DEFAULT       "F8"
#define WATER_TOGGLE_WIPER_MODIFIERS_DEFAULT ShiftMask

#define WATER_OFFSET_SCALE_DEFAULT    1.0f
#define WATER_OFFSET_SCALE_MIN        0.0f
#define WATER_OFFSET_SCALE_MAX       10.0f
#define WATER_OFFSET_SCALE_PRECISION  0.1f

#define WATER_TITLE_AMPL_DEFAULT      0.1f
#define WATER_TITLE_AMPL_MIN          0.01f
#define WATER_TITLE_AMPL_MAX          1.0f
#define WATER_TITLE_AMPL_PRECISION    0.01f

#define WATER_RAIN_AMPL_DEFAULT       0.8f
#define WATER_RAIN_AMPL_MIN           0.01f
#define WATER_RAIN_AMPL_MAX           1.0f
#define WATER_RAIN_AMPL_PRECISION     0.01f

#define WATER_VISCOS_DEFAULT          0.2f
#define WATER_VISCOS_MIN              0.01f
#define WATER_VISCOS_MAX              0.6f
#define WATER_VISCOS_PRECISION        0.01f

#define WATER_INIT_VERTICES_DEFAULT   3000
#define WATER_INIT_VERTICES_MIN       100
#define WATER_INIT_VERTICES_MAX       10000

#define WATER_RAIN_DELAY_DEFAULT 250
#define WATER_RAIN_DELAY_MIN	 10
#define WATER_RAIN_DELAY_MAX	 60000	// max of 1 minute...hope this is good

#define WATER_TITLE_WAVE_BELL_DEFAULT FALSE
#define WATER_UNGRAB_WAVE_DEFAULT     FALSE
#define WATER_SHIVER_WIN_DEFAULT      TRUE

static int displayPrivateIndex;

static int waterLastPointerX = 0;
static int waterLastPointerY = 0;

#define WATER_DISPLAY_OPTION_INITIATE      0
#define WATER_DISPLAY_OPTION_TOGGLE_RAIN   1
#define WATER_DISPLAY_OPTION_TOGGLE_WIPER  2
#define WATER_DISPLAY_OPTION_OFFSET_SCALE  3
#define WATER_DISPLAY_OPTION_RAIN_DELAY	   4
#define WATER_DISPLAY_OPTION_TITLE_WAVE    5
#define WATER_DISPLAY_OPTION_POINT         6
#define WATER_DISPLAY_OPTION_LINE          7
#define WATER_DISPLAY_OPTION_QUAD          8
#define WATER_DISPLAY_OPTION_WIN           9
#define WATER_DISPLAY_OPTION_UNGRAB_WAVE   10
#define WATER_DISPLAY_OPTION_SHIVER_WIN    11
#define WATER_DISPLAY_OPTION_TITLE_AMPL    12
#define WATER_DISPLAY_OPTION_RAIN_AMPL     13
#define WATER_DISPLAY_OPTION_VISCOSITY     14
#define WATER_DISPLAY_OPTION_INIT_VERTICES 15
#define WATER_DISPLAY_OPTION_NUM           16

typedef struct _WaterDisplay
{
	int screenPrivateIndex;

	CompOption opt[WATER_DISPLAY_OPTION_NUM];

	HandleEventProc handleEvent;
} WaterDisplay;

typedef struct _WaterScreen
{
	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	DrawWindowTextureProc drawWindowTexture;
	WindowUngrabNotifyProc windowUngrabNotify;

	int grabIndex;
	int width, height;

	GLuint program[PROGRAM_NUM];
	GLuint texture[TEXTURE_NUM];

	int tIndex;
	GLenum target;
	GLfloat tx, ty;

	int count;

	GLuint fbo;
	GLint fboStatus;

	void *data;
	float *d0;
	float *d1;
	unsigned char *t0;

	CompTimeoutHandle rainHandle;
	CompTimeoutHandle wiperHandle;

	float wiperAngle;
	float wiperSpeed;

	Window current;

} WaterScreen;

#define GET_WATER_DISPLAY(d)				      \
    ((WaterDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define WATER_DISPLAY(d)		     \
    WaterDisplay *wd = GET_WATER_DISPLAY (d)

#define GET_WATER_SCREEN(s, wd)					  \
    ((WaterScreen *) (s)->privates[(wd)->screenPrivateIndex].ptr)

#define WATER_SCREEN(s)							   \
    WaterScreen *ws = GET_WATER_SCREEN (s, GET_WATER_DISPLAY (s->display))

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

static Bool waterRainTimeout(void *closure);

static Bool waterWiperTimeout(void *closure);

static const char *saturateFpString =
		"MUL temp, rgb, { 1.0, 1.0, 1.0, 0.0 };"
		"DP3 temp, temp, program.local[1];"
		"LRP rgb.xyz, program.local[1].w, rgb, temp;";

static const char *bumpFpString =
		"!!ARBfp1.0"
		"PARAM light0color = state.light[0].diffuse;"
		"TEMP rgb, normal, temp, total, bump, offset;"
/* get normal from normal map */
		"TEX normal, fragment.texcoord[1], texture[1], %s;"
/* save height */
		"MOV offset, normal;"
/* remove scale and bias from normal */
		"SUB normal, normal, 0.5;" "MUL normal, normal, 2.0;"
/* normalize the normal map */
		"DP3 temp, normal, normal;"
		"RSQ temp, temp.x;" "MUL normal, normal, temp;"
/* scale down normal by height and constant and use as offset in texture */
		"MUL offset, normal, offset.w;"
		"MUL offset, offset, program.local[0];"
		"ADD offset, fragment.texcoord[0].xyzw, offset.yxzz;"
/* get texture data */
		"TEX rgb, offset, texture[0], %s;"
/* normal dot lightdir, this should eventually be changed to a real light
   vector */
		"DP3 bump, normal, { 0.707, 0.707, 0.0, 0.0 };"
		"MUL bump, bump, light0color;"
/* diffuse per-vertex lighting, opacity and brightness
   and add lightsource bump color */
		"MAD rgb, fragment.color, rgb, bump;"
/* saturation */
		"%s"
/* output */
		"MOV result.color, rgb;" "END";

static const char *waterFpString =
		"!!ARBfp1.0"
		"PARAM param = program.local[0];"
		"ATTRIB t11  = fragment.texcoord[0];"
		"TEMP t01, t21, t10, t12;"
		"TEMP c11, c01, c21, c10, c12;"
		"TEMP prev, v, temp, accel;"
		"TEX prev, t11, texture[0], %s;" "TEX c11,  t11, texture[1], %s;"
/* sample offsets */
		"ADD t01, t11, { - %f, 0.0, 0.0, 0.0 };"
		"ADD t21, t11, {   %f, 0.0, 0.0, 0.0 };"
		"ADD t10, t11, { 0.0, - %f, 0.0, 0.0 };"
		"ADD t12, t11, { 0.0,   %f, 0.0, 0.0 };"
/* fetch nesseccary samples */
		"TEX c01, t01, texture[1], %s;"
		"TEX c21, t21, texture[1], %s;"
		"TEX c10, t10, texture[1], %s;" "TEX c12, t12, texture[1], %s;"
/* x/y normals from height */
		"MOV v, { 0.0, 0.0, 0.75, 0.0 };"
		"SUB v.x, c12.w, c10.w;" "SUB v.y, c01.w, c21.w;"
/* bumpiness */
		"MUL v, v, 1.5;"
/* normalize */
		"MAD temp, v.x, v.x, 1.0;"
		"MAD temp, v.y, v.y, temp;" "RSQ temp, temp.x;" "MUL v, v, temp;"
/* add scale and bias to normal */
		"MAD v, v, 0.5, 0.5;"
/* done with computing the normal, continue with computing the next
   height value */
		"ADD accel, c10, c12;"
		"ADD accel, c01, accel;"
		"ADD accel, c21, accel;" "MAD accel, -4.0, c11, accel;"
/* store new height in alpha component */
		"MAD v.w, 2.0, c11, -prev.w;" "MAD v.w, accel, param.x, v.w;"
/* fade out height */
		"MUL v.w, v.w, param.y;" "MOV result.color, v;" "END";

static int
loadFragmentProgram(CompScreen * s, GLuint * program, const char *string)
{
	GLint errorPos;

	/* clear errors */
	glGetError();

	if (!*program)
		(*s->genPrograms) (1, program);

	(*s->bindProgram) (GL_FRAGMENT_PROGRAM_ARB, *program);
	(*s->programString) (GL_FRAGMENT_PROGRAM_ARB,
						 GL_PROGRAM_FORMAT_ASCII_ARB, strlen(string), string);

	glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errorPos);
	if (glGetError() != GL_NO_ERROR || errorPos != -1)
	{
		fprintf(stderr,
				"%s: water: failed to load bump map program\n",
				getProgramName());

		(*s->deletePrograms) (1, program);
		*program = 0;

		return 0;
	}

	return 1;
}

static int loadWaterProgram(CompScreen * s)
{
	char buffer[1024];

	WATER_SCREEN(s);

	if (ws->target == GL_TEXTURE_2D)
		sprintf(buffer, waterFpString,
				"2D", "2D",
				1.0f / ws->width, 1.0f / ws->width,
				1.0f / ws->height, 1.0f / ws->height, "2D", "2D", "2D", "2D");
	else
		sprintf(buffer, waterFpString,
				"RECT", "RECT",
				1.0f, 1.0f, 1.0f, 1.0f, "RECT", "RECT", "RECT", "RECT");

	return loadFragmentProgram(s, &ws->program[PROGRAM_WATER], buffer);
}

static int loadBumpMapProgram(CompScreen * s, int program)
{
	const char *normalTarget = "2D";
	const char *textureTarget = "2D";
	const char *saturation = "";
	char buffer[1024];

	WATER_SCREEN(s);

	if (!s->fragmentProgram)
	{
		fprintf(stderr,
				"%s: water: GL_ARB_fragment_program is missing\n",
				getProgramName());
		return 0;
	}

	switch (program)
	{
	case PROGRAM_BUMP_SAT:
		saturation = saturateFpString;
		/* fall-through */
	case PROGRAM_BUMP:
		break;
	case PROGRAM_BUMP_SAT_RECT:
		saturation = saturateFpString;
		/* fall-through */
	case PROGRAM_BUMP_RECT:
		textureTarget = "RECT";
		break;
	}

	if (ws->target != GL_TEXTURE_2D)
		normalTarget = "RECT";

	sprintf(buffer, bumpFpString, normalTarget, textureTarget, saturation);

	return loadFragmentProgram(s, &ws->program[program], buffer);
}

static int ensureBumpMapProgram(CompScreen * s, int program)
{
	WATER_SCREEN(s);

	if (!ws->program[program])
		return loadBumpMapProgram(s, program);

	return 1;
}

static void allocTexture(CompScreen * s, int index)
{
	WATER_SCREEN(s);

	glGenTextures(1, &ws->texture[index]);
	glBindTexture(ws->target, ws->texture[index]);

	glTexParameteri(ws->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(ws->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(ws->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(ws->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glTexImage2D(ws->target, 0, GL_RGBA, ws->width, ws->height, 0, GL_BGRA,
#if IMAGE_BYTE_ORDER == MSBFirst
				 GL_UNSIGNED_INT_8_8_8_8_REV,
#else
				 GL_UNSIGNED_BYTE,
#endif
				 ws->t0);
}

static int fboPrologue(CompScreen * s, int tIndex)
{
	WATER_SCREEN(s);

	if (!ws->fbo)
		return 0;

	if (!ws->texture[tIndex])
		allocTexture(s, tIndex);

	(*s->bindFramebuffer) (GL_FRAMEBUFFER_EXT, ws->fbo);

	(*s->framebufferTexture2D) (GL_FRAMEBUFFER_EXT,
								GL_COLOR_ATTACHMENT0_EXT,
								ws->target, ws->texture[tIndex], 0);

	glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);

	/* check status the first time */
	if (!ws->fboStatus)
	{
		ws->fboStatus = (*s->checkFramebufferStatus) (GL_FRAMEBUFFER_EXT);
		if (ws->fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT)
		{
			fprintf(stderr,
					"%s: water: framebuffer incomplete\n", getProgramName());

			(*s->bindFramebuffer) (GL_FRAMEBUFFER_EXT, 0);
			(*s->deleteFramebuffers) (1, &ws->fbo);

			glDrawBuffer(GL_BACK);
			glReadBuffer(GL_BACK);

			ws->fbo = 0;

			return 0;
		}
	}

	glViewport(0, 0, ws->width, ws->height);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	return 1;
}

static void fboEpilogue(CompScreen * s)
{
	(*s->bindFramebuffer) (GL_FRAMEBUFFER_EXT, 0);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glDepthRange(0, 1);
	glViewport(-1, -1, 2, 2);
	glRasterPos2f(0, 0);

	s->rasterX = s->rasterY = 0;

	setDefaultViewport(s);
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	glDrawBuffer(GL_BACK);
	glReadBuffer(GL_BACK);
}

static int fboUpdate(CompScreen * s, float dt, float fade)
{
	WATER_SCREEN(s);
	WATER_DISPLAY(s->display);

	if (!fboPrologue(s, TINDEX(ws, 1)))
		return 0;

	if (!ws->texture[TINDEX(ws, 2)])
		allocTexture(s, TINDEX(ws, 2));

	if (!ws->texture[TINDEX(ws, 0)])
		allocTexture(s, TINDEX(ws, 0));

	glEnable(ws->target);

	(*s->activeTexture) (GL_TEXTURE0_ARB);
	glBindTexture(ws->target, ws->texture[TINDEX(ws, 2)]);

	glTexParameteri(ws->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(ws->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	(*s->activeTexture) (GL_TEXTURE1_ARB);
	glBindTexture(ws->target, ws->texture[TINDEX(ws, 0)]);
	glTexParameteri(ws->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(ws->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glEnable(GL_FRAGMENT_PROGRAM_ARB);
	(*s->bindProgram) (GL_FRAGMENT_PROGRAM_ARB, ws->program[PROGRAM_WATER]);

	(*s->programLocalParameter4f) (GL_FRAGMENT_PROGRAM_ARB, 0,
								   dt *
								   wd->
								   opt[WATER_DISPLAY_OPTION_VISCOSITY].
								   value.f, fade, 1.0f, 1.0f);

	glBegin(GL_QUADS);

	glTexCoord2f(0.0f, 0.0f);
	glVertex2f(0.0f, 0.0f);
	glTexCoord2f(ws->tx, 0.0f);
	glVertex2f(1.0f, 0.0f);
	glTexCoord2f(ws->tx, ws->ty);
	glVertex2f(1.0f, 1.0f);
	glTexCoord2f(0.0f, ws->ty);
	glVertex2f(0.0f, 1.0f);

	glEnd();

	glDisable(GL_FRAGMENT_PROGRAM_ARB);

	glBindTexture(ws->target, 0);
	glTexParameteri(ws->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(ws->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	(*s->activeTexture) (GL_TEXTURE0_ARB);
	glTexParameteri(ws->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(ws->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(ws->target, 0);

	fboEpilogue(s);

	/* increment texture index */
	ws->tIndex = TINDEX(ws, 1);

	return 1;
}

static int
fboVertices(CompScreen * s, GLenum type, XPoint * p, int n, float v)
{
	WATER_SCREEN(s);

	if (!fboPrologue(s, TINDEX(ws, 0)))
		return 0;

	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
	glColor4f(0.0f, 0.0f, 0.0f, v);

	glPointSize(3.0f);
	glLineWidth(1.0f);

	glScalef(1.0f / ws->width, 1.0f / ws->height, 1.0);
	glTranslatef(0.5f, 0.5f, 0.0f);

	glBegin(type);

	while (n--)
	{
		glVertex2i(p->x, p->y);
		p++;
	}

	glEnd();

	glColor4usv(defaultColor);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

	fboEpilogue(s);

	return 1;
}

static void softwareUpdate(CompScreen * s, float dt, float fade)
{
	float *dTmp;
	int i, j;
	float v0, v1, inv;
	float accel, value;
	unsigned char *t0, *t;
	int dWidth, dHeight;
	float *d01, *d10, *d11, *d12;

	WATER_SCREEN(s);
	WATER_DISPLAY(s->display);

	if (!ws->texture[TINDEX(ws, 0)])
		allocTexture(s, TINDEX(ws, 0));

	dt *= wd->opt[WATER_DISPLAY_OPTION_VISCOSITY].value.f * 2.0f;
	fade *= 0.99f;

	dWidth = ws->width + 2;
	dHeight = ws->height + 2;

#define D(d, j) (*((d) + (j)))

	d01 = ws->d0 + dWidth;
	d10 = ws->d1;
	d11 = d10 + dWidth;
	d12 = d11 + dWidth;

	for (i = 1; i < dHeight - 1; i++)
	{
		for (j = 1; j < dWidth - 1; j++)
		{
			accel = dt * (D(d10, j) +
						  D(d12, j) +
						  D(d11, j - 1) + D(d11, j + 1) - 4.0f * D(d11, j));

			value = (2.0f * D(d11, j) - D(d01, j) + accel) * fade;

			CLAMP(value, 0.0f, 1.0f);

			D(d01, j) = value;
		}

		d01 += dWidth;
		d10 += dWidth;
		d11 += dWidth;
		d12 += dWidth;
	}

	/* update border */
	memcpy(ws->d0, ws->d0 + dWidth, dWidth * sizeof(GLfloat));
	memcpy(ws->d0 + dWidth * (dHeight - 1),
		   ws->d0 + dWidth * (dHeight - 2), dWidth * sizeof(GLfloat));

	d01 = ws->d0 + dWidth;

	for (i = 1; i < dHeight - 1; i++)
	{
		D(d01, 0) = D(d01, 1);
		D(d01, dWidth - 1) = D(d01, dWidth - 2);

		d01 += dWidth;
	}

	d10 = ws->d1;
	d11 = d10 + dWidth;
	d12 = d11 + dWidth;

	t0 = ws->t0;

	/* update texture */
	for (i = 0; i < ws->height; i++)
	{
		for (j = 0; j < ws->width; j++)
		{
			v0 = (D(d12, j) - D(d10, j)) * 1.5f;
			v1 = (D(d11, j - 1) - D(d11, j + 1)) * 1.5f;

			/* 0.5 for scale */
			inv = 0.5f / sqrtf(v0 * v0 + v1 * v1 + 1.0f);

			/* add scale and bias to normal */
			v0 = v0 * inv + 0.5f;
			v1 = v1 * inv + 0.5f;

			/* store normal map in RGB components */
			t = t0 + (j * 4);
			t[0] = (unsigned char)((inv + 0.5f) * 255.0f);
			t[1] = (unsigned char)(v1 * 255.0f);
			t[2] = (unsigned char)(v0 * 255.0f);

			/* store height in A component */
			t[3] = (unsigned char)(D(d11, j) * 255.0f);
		}

		d10 += dWidth;
		d11 += dWidth;
		d12 += dWidth;

		t0 += ws->width * 4;
	}

#undef D

	/* swap height maps */
	dTmp = ws->d0;
	ws->d0 = ws->d1;
	ws->d1 = dTmp;

	if (ws->texture[TINDEX(ws, 0)])
	{
		glBindTexture(ws->target, ws->texture[TINDEX(ws, 0)]);
		glTexImage2D(ws->target,
					 0, GL_RGBA, ws->width, ws->height, 0, GL_BGRA,
#if IMAGE_BYTE_ORDER == MSBFirst
					 GL_UNSIGNED_INT_8_8_8_8_REV,
#else
					 GL_UNSIGNED_BYTE,
#endif
					 ws->t0);
	}
}


#define SET(x, y, v) *((ws->d1) + (ws->width + 2) * (y + 1) + (x + 1)) = (v)

static void softwarePoints(CompScreen * s, XPoint * p, int n, float add)
{
	WATER_SCREEN(s);

	while (n--)
	{
		SET(p->x - 1, p->y - 1, add);
		SET(p->x, p->y - 1, add);
		SET(p->x + 1, p->y - 1, add);

		SET(p->x - 1, p->y, add);
		SET(p->x, p->y, add);
		SET(p->x + 1, p->y, add);

		SET(p->x - 1, p->y + 1, add);
		SET(p->x, p->y + 1, add);
		SET(p->x + 1, p->y + 1, add);

		p++;
	}
}

/* bresenham */
static void softwareLines(CompScreen * s, XPoint * p, int n, float v)
{
	int x1, y1, x2, y2;
	Bool steep;
	int tmp;
	int deltaX, deltaY;
	int error = 0;
	int yStep;
	int x, y;

	WATER_SCREEN(s);

#define SWAP(v0, v1) \
    tmp = v0;	     \
    v0 = v1;	     \
    v1 = tmp

	while (n > 1)
	{
		x1 = p->x;
		y1 = p->y;

		p++;
		n--;

		x2 = p->x;
		y2 = p->y;

		p++;
		n--;

		steep = abs(y2 - y1) > abs(x2 - x1);
		if (steep)
		{
			SWAP(x1, y1);
			SWAP(x2, y2);
		}

		if (x1 > x2)
		{
			SWAP(x1, x2);
			SWAP(y1, y2);
		}
#undef SWAP

		deltaX = x2 - x1;
		deltaY = abs(y2 - y1);

		y = y1;
		if (y1 < y2)
			yStep = 1;
		else
			yStep = -1;

		for (x = x1; x <= x2; x++)
		{
			if (steep)
			{
				SET(y, x, v);
			}
			else
			{
				SET(x, y, v);
			}

			error += deltaY;
			if (2 * error >= deltaX)
			{
				y += yStep;
				error -= deltaX;
			}
		}
	}
}

#undef SET
static void softwareQuads(CompScreen * s, XPoint * p, int n, float v)
{
	/* this seemed the most logical way to handle this */

	/* the quad is defined by its four corners
	 * rather than redesign a new algorithm to
	 * handlle this, I just munge the data to work
	 * with softwareLines
	 */
	XPoint pq[8];

	pq[0] = p[0];
	pq[1] = p[1];

	pq[2] = p[1];
	pq[3] = p[2];

	pq[4] = p[2];
	pq[5] = p[3];

	pq[6] = p[3];
	pq[7] = p[0];

	softwareLines(s, pq, 8, v);
}

static void
softwareVertices(CompScreen * s, GLenum type, XPoint * p, int n, float v)
{
	switch (type)
	{
	case GL_POINTS:
		softwarePoints(s, p, n, v);
		break;
	case GL_LINES:
		softwareLines(s, p, n, v);
		break;
	case GL_QUADS:
		softwareQuads(s, p, n, v);
		break;
	}
}

static void waterUpdate(CompScreen * s, float dt)
{
	GLfloat fade = 1.0f;

	WATER_SCREEN(s);

	if (ws->count < 1000)
	{
		if (ws->count > 1)
			fade = 0.90f + ws->count / 10000.0f;
		else
			fade = 0.0f;
	}

	if (!fboUpdate(s, dt, fade))
		softwareUpdate(s, dt, fade);
}

static void scaleVertices(CompScreen * s, XPoint * p, int n)
{
	WATER_SCREEN(s);

	while (n--)
	{
		p[n].x = (ws->width * p[n].x) / s->width;
		p[n].y = (ws->height * p[n].y) / s->height;
	}
}

static void
waterVertices(CompScreen * s, GLenum type, XPoint * p, int n, float v)
{
	WATER_SCREEN(s);
	WATER_DISPLAY(s->display);

	if (!ensureBumpMapProgram(s, PROGRAM_BUMP))
		return;

	scaleVertices(s, p, n);

	if (!fboVertices(s, type, p, n, v))
		softwareVertices(s, type, p, n, v);

	if (ws->count < wd->opt[WATER_DISPLAY_OPTION_INIT_VERTICES].value.i)
		ws->count = wd->opt[WATER_DISPLAY_OPTION_INIT_VERTICES].value.i;
}

static Bool waterRainTimeout(void *closure)
{
	CompScreen *s = closure;
	XPoint p;

	WATER_SCREEN(s);
	WATER_DISPLAY(s->display);

	if (ws->current)
		ws->current = 0;

	p.x = (int)(s->width * (rand() / (float)RAND_MAX));
	p.y = (int)(s->height * (rand() / (float)RAND_MAX));

	waterVertices(s, GL_POINTS, &p, 1,
				  wd->opt[WATER_DISPLAY_OPTION_RAIN_AMPL].value.f *
				  (rand() / (float)RAND_MAX));

	damageScreen(s);

	return TRUE;
}

static Bool waterWiperTimeout(void *closure)
{
	CompScreen *s = closure;

	WATER_SCREEN(s);

	if (ws->count)
	{
		if (ws->wiperAngle == 0.0f)
			ws->wiperSpeed = 2.5f;
		else if (ws->wiperAngle == 180.0f)
			ws->wiperSpeed = -2.5f;
		if (ws->current)
			ws->current = 0;
	}

	return TRUE;
}

static void waterReset(CompScreen * s)
{
	int size, i, j;

	WATER_SCREEN(s);

	ws->height = TEXTURE_SIZE;
	ws->width = (ws->height * s->width) / s->height;

	if (s->textureNonPowerOfTwo ||
		(POWER_OF_TWO(ws->width) && POWER_OF_TWO(ws->height)))
	{
		ws->target = GL_TEXTURE_2D;
		ws->tx = ws->ty = 1.0f;
	}
	else
	{
		ws->target = GL_TEXTURE_RECTANGLE_NV;
		ws->tx = ws->width;
		ws->ty = ws->height;
	}

	if (!loadBumpMapProgram(s, PROGRAM_BUMP))
		return;

	if (s->fbo)
	{
		loadWaterProgram(s);
		if (!ws->fbo)
			(*s->genFramebuffers) (1, &ws->fbo);
	}

	ws->fboStatus = 0;

	for (i = 0; i < TEXTURE_NUM; i++)
	{
		if (ws->texture[i])
		{
			glDeleteTextures(1, &ws->texture[i]);
			ws->texture[i] = 0;
		}
	}

	if (ws->data)
		free(ws->data);

	size = (ws->width + 2) * (ws->height + 2);

	ws->data = calloc(1, (sizeof(float) * size * 2) +
					  (sizeof(GLubyte) * ws->width * ws->height * 4));
	if (!ws->data)
		return;

	ws->d0 = ws->data;
	ws->d1 = (ws->d0 + (size));
	ws->t0 = (unsigned char *)(ws->d1 + (size));

	for (i = 0; i < ws->height; i++)
	{
		for (j = 0; j < ws->width; j++)
		{
			(ws->t0 + (ws->width * 4 * i + j * 4))[0] = 0xff;
		}
	}
}

static void
waterDrawWindowTexture(CompWindow * w,
					   CompTexture * texture,
					   const WindowPaintAttrib * attrib, unsigned int mask)
{
	WATER_DISPLAY(w->screen->display);
	WATER_SCREEN(w->screen);

	if (w->id == ws->current)
	{
		UNWRAP(ws, w->screen, drawWindowTexture);
		(*w->screen->drawWindowTexture) (w, texture, attrib, mask);
		WRAP(ws, w->screen, drawWindowTexture, waterDrawWindowTexture);

		return;
	}

	if (ws->count)
	{
		Bool lighting = w->screen->lighting;
		GLfloat plane[4];
		int program;

		screenLighting(w->screen, TRUE);

		glPushMatrix();

		enableTexture(w->screen, texture,
					  w->screen->filter[WINDOW_TRANS_FILTER]);

		(*w->screen->activeTexture) (GL_TEXTURE1_ARB);

		glBindTexture(ws->target, ws->texture[TINDEX(ws, 0)]);

		plane[1] = plane[2] = 0.0f;
		plane[0] = ws->tx / (GLfloat) w->screen->width;
		plane[3] = 0.0f;

		glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
		glTexGenfv(GL_S, GL_EYE_PLANE, plane);
		glEnable(GL_TEXTURE_GEN_S);

		plane[0] = plane[2] = 0.0f;
		plane[1] = ws->ty / (GLfloat) w->screen->height;
		plane[3] = 0.0f;

		glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
		glTexGenfv(GL_T, GL_EYE_PLANE, plane);
		glEnable(GL_TEXTURE_GEN_T);

		if (mask & PAINT_WINDOW_TRANSFORMED_MASK)
		{
			glTranslatef(w->attrib.x, w->attrib.y, 0.0f);
			glScalef(attrib->xScale, attrib->yScale, 0.0f);
			glTranslatef(attrib->xTranslate / attrib->xScale -
						 w->attrib.x,
						 attrib->yTranslate / attrib->yScale -
						 w->attrib.y, 0.0f);
		}

		glEnable(GL_FRAGMENT_PROGRAM_ARB);

		if (w->screen->canDoSaturated && attrib->saturation != COLOR)
		{
			if (texture->target == GL_TEXTURE_2D)
				program = PROGRAM_BUMP_SAT;
			else
				program = PROGRAM_BUMP_SAT_RECT;

			ensureBumpMapProgram(w->screen, program);

			(*w->screen->bindProgram) (GL_FRAGMENT_PROGRAM_ARB,
									   ws->program[program]);

			(*w->screen->
			 programLocalParameter4f) (GL_FRAGMENT_PROGRAM_ARB,
									   0,
									   texture->matrix.yy *
									   wd->
									   opt
									   [WATER_DISPLAY_OPTION_OFFSET_SCALE].
									   value.f * 50.0f,
									   -texture->matrix.xx *
									   wd->
									   opt
									   [WATER_DISPLAY_OPTION_OFFSET_SCALE].
									   value.f * 50.0f, 0.0f, 0.0f);

			(*w->screen->
			 programLocalParameter4f) (GL_FRAGMENT_PROGRAM_ARB,
									   1,
									   RED_SATURATION_WEIGHT,
									   GREEN_SATURATION_WEIGHT,
									   BLUE_SATURATION_WEIGHT,
									   attrib->saturation / 65535.0f);
		}
		else
		{
			if (texture->target == GL_TEXTURE_2D)
				program = PROGRAM_BUMP;
			else
				program = PROGRAM_BUMP_RECT;

			ensureBumpMapProgram(w->screen, program);

			(*w->screen->bindProgram) (GL_FRAGMENT_PROGRAM_ARB,
									   ws->program[program]);

			(*w->screen->
			 programLocalParameter4f) (GL_FRAGMENT_PROGRAM_ARB,
									   0,
									   texture->matrix.yy *
									   wd->
									   opt
									   [WATER_DISPLAY_OPTION_OFFSET_SCALE].
									   value.f * 50.0f,
									   -texture->matrix.xx *
									   wd->
									   opt
									   [WATER_DISPLAY_OPTION_OFFSET_SCALE].
									   value.f * 50.0f, 0.0f, 0.0f);
		}

		if (mask & PAINT_WINDOW_TRANSLUCENT_MASK)
		{
			glEnable(GL_BLEND);
			if (attrib->opacity != OPAQUE || attrib->brightness != BRIGHT)
			{
				GLushort color;

				color = (attrib->opacity * attrib->brightness) >> 16;

				glColor4us(color, color, color, attrib->opacity);

				(*w->screen->drawWindowGeometry) (w);

				glColor4usv(defaultColor);
			}
			else
			{
				(*w->screen->drawWindowGeometry) (w);
			}

			glDisable(GL_BLEND);
		}
		else if (attrib->brightness != BRIGHT)
		{
			screenTexEnvMode(w->screen, GL_MODULATE);
			glColor4us(attrib->brightness, attrib->brightness,
					   attrib->brightness, BRIGHT);

			(*w->screen->drawWindowGeometry) (w);

			glColor4usv(defaultColor);
			screenTexEnvMode(w->screen, GL_REPLACE);
		}
		else
		{
			(*w->screen->drawWindowGeometry) (w);
		}

		glDisable(GL_FRAGMENT_PROGRAM_ARB);

		glDisable(GL_TEXTURE_GEN_T);
		glDisable(GL_TEXTURE_GEN_S);

		glBindTexture(ws->target, 0);
		(*w->screen->activeTexture) (GL_TEXTURE0_ARB);
		disableTexture(w->screen, texture);

		glPopMatrix();

		screenLighting(w->screen, lighting);
	}
	else
	{
		UNWRAP(ws, w->screen, drawWindowTexture);
		(*w->screen->drawWindowTexture) (w, texture, attrib, mask);
		WRAP(ws, w->screen, drawWindowTexture, waterDrawWindowTexture);
	}
}

/* TODO: a way to control the speed */
static void waterPreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	WATER_SCREEN(s);

	if (ws->count)
	{
		ws->count -= 10;
		if (ws->count < 0)
			ws->count = 0;

		if (ws->wiperHandle)
		{
			float step, angle0, angle1;
			Bool wipe = FALSE;
			XPoint p[3];

			p[1].x = s->width / 2;
			p[1].y = s->height;

			step = ws->wiperSpeed * msSinceLastPaint / 20.0f;

			if (ws->wiperSpeed > 0.0f)
			{
				if (ws->wiperAngle < 180.0f)
				{
					angle0 = ws->wiperAngle;

					ws->wiperAngle += step;
					ws->wiperAngle = MIN(ws->wiperAngle, 180.0f);

					angle1 = ws->wiperAngle;

					wipe = TRUE;
				}
			}
			else
			{
				if (ws->wiperAngle > 0.0f)
				{
					angle1 = ws->wiperAngle;

					ws->wiperAngle += step;
					ws->wiperAngle = MAX(ws->wiperAngle, 0.0f);

					angle0 = ws->wiperAngle;

					wipe = TRUE;
				}
			}

#define TAN(a) (tanf ((a) * (M_PI / 180.0f)))

			if (wipe)
			{
				if (angle0 > 0.0f)
				{
					p[2].x = s->width / 2 - s->height / TAN(angle0);
					p[2].y = 0;
				}
				else
				{
					p[2].x = 0;
					p[2].y = s->height;
				}

				if (angle1 < 180.0f)
				{
					p[0].x = s->width / 2 - s->height / TAN(angle1);
					p[0].y = 0;
				}
				else
				{
					p[0].x = s->width;
					p[0].y = s->height;
				}

				/* software rasterizer doesn't support triangles yet so wiper
				   effect will only work with FBOs right now */
				waterVertices(s, GL_TRIANGLES, p, 3, 0.0f);
			}
#undef TAN

		}

		waterUpdate(s, 0.8f);
	}

	UNWRAP(ws, s, preparePaintScreen);
	(*s->preparePaintScreen) (s, msSinceLastPaint);
	WRAP(ws, s, preparePaintScreen, waterPreparePaintScreen);
}

static void waterDonePaintScreen(CompScreen * s)
{
	WATER_SCREEN(s);

	if (ws->count)
		damageScreen(s);
	else
		ws->current = 0;

	UNWRAP(ws, s, donePaintScreen);
	(*s->donePaintScreen) (s);
	WRAP(ws, s, donePaintScreen, waterDonePaintScreen);
}

static void waterHandleMotionEvent(CompDisplay * d, Window root)
{
	CompScreen *s;

	s = findScreenAtDisplay(d, root);
	if (s)
	{
		WATER_SCREEN(s);

		if (ws->grabIndex)
		{
			XPoint p[2];

			p[0].x = waterLastPointerX;
			p[0].y = waterLastPointerY;

			p[1].x = waterLastPointerX = d->pointerX;
			p[1].y = waterLastPointerY = d->pointerY;

			waterVertices(s, GL_LINES, p, 2, 0.2f);

			damageScreen(s);
		}
	}
}

static Bool
waterInitiate(CompDisplay * d,
			  CompAction * action,
			  CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	unsigned int ui;
	Window root, child;
	int xRoot, yRoot, i;

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

		if (otherScreenGrabExist(s, "water", 0))
			continue;

		if (!ws->grabIndex)
			ws->grabIndex = pushScreenGrab(s, None, "water");

		if (ws->current)
			ws->current = 0;

		if (XQueryPointer
			(d->display, s->root, &root, &child, &xRoot, &yRoot, &i, &i, &ui))
		{
			XPoint p;

			p.x = waterLastPointerX = xRoot;
			p.y = waterLastPointerY = yRoot;

			waterVertices(s, GL_POINTS, &p, 1, 0.8f);

			damageScreen(s);
		}
	}

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

	if (state & CompActionStateInitKey)
		action->state |= CompActionStateTermKey;

	return FALSE;
}

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

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

		if (ws->grabIndex)
		{
			removeScreenGrab(s, ws->grabIndex, 0);
			ws->grabIndex = 0;
		}
		ws->current = 0;
	}

	return FALSE;
}

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

	WATER_DISPLAY(d);

	s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0));
	if (s)
	{
		WATER_SCREEN(s);

		if (!ws->rainHandle)
		{
			int delay;

			delay = wd->opt[WATER_DISPLAY_OPTION_RAIN_DELAY].value.i;
			ws->rainHandle = compAddTimeout(delay, waterRainTimeout, s);
		}
		else
		{
			compRemoveTimeout(ws->rainHandle);
			ws->rainHandle = 0;
		}
	}

	return FALSE;
}

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

	s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0));
	if (s)
	{
		WATER_SCREEN(s);

		if (!ws->wiperHandle)
		{
			int delay;

			delay = 2000;
			ws->wiperHandle = compAddTimeout(delay, waterWiperTimeout, s);
		}
		else
		{
			compRemoveTimeout(ws->wiperHandle);
			ws->wiperHandle = 0;
		}
	}

	return FALSE;
}

static Bool
waterTitleWave(CompDisplay * d,
			   CompAction * action,
			   CompActionState state, CompOption * option, int nOption)
{
	CompWindow *w;
	int xid;

	xid = getIntOptionNamed(option, nOption, "window", d->activeWindow);

	w = findWindowAtDisplay(d, xid);
	if (w)
	{
		WATER_DISPLAY(w->screen->display);
		XPoint p[2];

		p[0].x = w->attrib.x - w->input.left;
		p[0].y = w->attrib.y - (w->input.top >> 2);

		p[1].x = w->attrib.x + w->width + w->input.right;
		p[1].y = p[0].y;

		waterVertices(w->screen, GL_LINES, p, 2,
					  wd->opt[WATER_DISPLAY_OPTION_TITLE_AMPL].value.f);

		damageScreen(w->screen);
	}

	return FALSE;
}

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

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

	s = findScreenAtDisplay(d, xid);
	if (s)
	{
		XPoint p;
		float amp;

		p.x = getIntOptionNamed(option, nOption, "x", s->width / 2);
		p.y = getIntOptionNamed(option, nOption, "y", s->height / 2);

		amp = getFloatOptionNamed(option, nOption, "amplitude", 0.5f);

		waterVertices(s, GL_POINTS, &p, 1, amp);

		damageScreen(s);
	}

	return FALSE;
}

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

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

	s = findScreenAtDisplay(d, xid);
	if (s)
	{
		XPoint p[2];
		float amp;

		p[0].x = getIntOptionNamed(option, nOption, "x0", s->width / 4);
		p[0].y = getIntOptionNamed(option, nOption, "y0", s->height / 2);

		p[1].x = getIntOptionNamed(option, nOption, "x1",
								   s->width - s->width / 4);
		p[1].y = getIntOptionNamed(option, nOption, "y1", s->height / 2);


		amp = getFloatOptionNamed(option, nOption, "amplitude", 0.25f);

		waterVertices(s, GL_LINES, p, 2, amp);

		damageScreen(s);
	}

	return FALSE;
}



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

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

	s = findScreenAtDisplay(d, xid);
	if (s)
	{
		XPoint p[4];
		float amp;

		/* top left corner */
		p[0].x = getIntOptionNamed(option, nOption, "x0", s->width / 4);
		p[0].y = getIntOptionNamed(option, nOption, "y0", s->height / 2);

		/* top right corner */
		p[1].x = getIntOptionNamed(option, nOption, "x1",
								   s->width - s->width / 4);
		p[1].y = getIntOptionNamed(option, nOption, "y1", s->height / 2);

		/* bottom right corner */
		p[2].x = getIntOptionNamed(option, nOption, "x2", s->width / 4);
		p[2].y = getIntOptionNamed(option, nOption, "y2", s->height / 2);

		/* bottom left corner */
		p[3].x = getIntOptionNamed(option, nOption, "x3",
								   s->width - s->width / 4);
		p[3].y = getIntOptionNamed(option, nOption, "y3", s->height / 2);

		amp = getFloatOptionNamed(option, nOption, "amplitude", 0.25f);

		waterVertices(s, GL_QUADS, p, 4, amp);

		damageScreen(s);
	}

	return FALSE;
}


static Bool
waterWin(CompDisplay * d,
		 CompAction * action,
		 CompActionState state, CompOption * option, int nOption)
{
	CompWindow *w;
	int xid;

	xid = getIntOptionNamed(option, nOption, "window", d->activeWindow);

	if (!xid)
		xid = d->activeWindow;

	w = findWindowAtDisplay(d, xid);
	if (w)
	{
		CompOption o[10];

		WATER_DISPLAY(d);
		WATER_SCREEN(w->screen);

		ws->current = xid;

		o[0].type = CompOptionTypeInt;
		o[0].name = "root";
		o[0].value.i = w->screen->root;

		/* top left */
		o[1].type = CompOptionTypeInt;
		o[1].name = "x0";
		o[1].value.i = w->attrib.x - w->input.left;

		o[2].type = CompOptionTypeInt;
		o[2].name = "y0";
		o[2].value.i = w->attrib.y - w->input.top;

		/* top right */
		o[3].type = CompOptionTypeInt;
		o[3].name = "x1";
		o[3].value.i = w->attrib.x + w->width + w->input.right;

		o[4].type = CompOptionTypeInt;
		o[4].name = "y1";
		o[4].value.i = w->attrib.y - w->input.top;

		/* bottom right */
		o[5].type = CompOptionTypeInt;
		o[5].name = "x2";
		o[5].value.i = w->attrib.x + w->width + w->input.right;

		o[6].type = CompOptionTypeInt;
		o[6].name = "y2";
		o[6].value.i = w->attrib.y + w->height + w->input.bottom;

		/* bottom left */
		o[7].type = CompOptionTypeInt;
		o[7].name = "x3";
		o[7].value.i = w->attrib.x - w->input.left;

		o[8].type = CompOptionTypeInt;
		o[8].name = "y3";
		o[8].value.i = w->attrib.y + w->height + w->input.bottom;

		o[9].type = CompOptionTypeFloat;
		o[9].name = "amplitude";
		o[9].value.f =
				getFloatOptionNamed(option, nOption, "amplitude", 0.25f);

		if (wd->opt[WATER_DISPLAY_OPTION_SHIVER_WIN].value.b)
		{
			/* Send a client message to wobbly */
			/* I'm not sure if this is the best way to do this, but it is simple and it works */
			XClientMessageEvent xev;

			xev.type = ClientMessage;
			xev.window = w->id;
			xev.message_type =
					XInternAtom(w->screen->display->display,
								"_BERYL_WOBBLY_SHIVER", 0);
			xev.format = 32;

			XSendEvent(w->screen->display->display,
					   w->screen->root, 0, StructureNotifyMask,
					   (XEvent *) & xev);
		}

		waterQuad(d, NULL, 0, o, 10);
	}

	return FALSE;
}

static void waterWindowUngrabNotify(CompWindow * w)
{

	WATER_DISPLAY(w->screen->display);
	WATER_SCREEN(w->screen);

	if (wd->opt[WATER_DISPLAY_OPTION_UNGRAB_WAVE].value.b &&
		!(w->state & CompWindowStateOffscreenMask))
	{
		/* generate a wave from the windows edges */
		CompOption o[2];

		o[0].type = CompOptionTypeInt;
		o[0].name = "window";
		o[0].value.i = w->id;

		o[1].type = CompOptionTypeFloat;
		o[1].name = "amplitude";
		o[1].value.f = wd->opt[WATER_DISPLAY_OPTION_TITLE_AMPL].value.f;

		waterWin(w->screen->display, NULL, 0, o, 2);
	}

	UNWRAP(ws, w->screen, windowUngrabNotify);
	(*w->screen->windowUngrabNotify) (w);
	WRAP(ws, w->screen, windowUngrabNotify, waterWindowUngrabNotify);
}

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

	WATER_DISPLAY(d);

	switch (event->type)
	{
	case ButtonPress:
		s = findScreenAtDisplay(d, event->xbutton.root);
		if (s)
		{
			WATER_SCREEN(s);

			if (ws->grabIndex)
			{
				XPoint p;

				p.x = d->pointerX;
				p.y = d->pointerY;

				waterVertices(s, GL_POINTS, &p, 1, 0.8f);
				damageScreen(s);
			}
		}
		break;
	case EnterNotify:
	case LeaveNotify:
		waterHandleMotionEvent(d, event->xcrossing.root);
		break;
	case MotionNotify:
		waterHandleMotionEvent(d, event->xmotion.root);
	default:
		break;
	}

	UNWRAP(wd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(wd, d, handleEvent, waterHandleEvent);
}

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

	WATER_DISPLAY(display);

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

	switch (index)
	{
	case WATER_DISPLAY_OPTION_INITIATE:
	case WATER_DISPLAY_OPTION_TOGGLE_RAIN:
	case WATER_DISPLAY_OPTION_TOGGLE_WIPER:
	case WATER_DISPLAY_OPTION_TITLE_WAVE:
	case WATER_DISPLAY_OPTION_POINT:
	case WATER_DISPLAY_OPTION_LINE:
	case WATER_DISPLAY_OPTION_QUAD:
	case WATER_DISPLAY_OPTION_WIN:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	case WATER_DISPLAY_OPTION_OFFSET_SCALE:
	case WATER_DISPLAY_OPTION_TITLE_AMPL:
	case WATER_DISPLAY_OPTION_RAIN_AMPL:
	case WATER_DISPLAY_OPTION_VISCOSITY:
		if (compSetFloatOption(o, value))
			return TRUE;
		break;
	case WATER_DISPLAY_OPTION_INIT_VERTICES:
		if (compSetIntOption(o, value))
			return TRUE;
		break;
	case WATER_DISPLAY_OPTION_RAIN_DELAY:
		if (compSetIntOption(o, value))
		{
			CompScreen *s;

			for (s = display->screens; s; s = s->next)
			{
				WATER_SCREEN(s);

				if (!ws->rainHandle)
					continue;

				compRemoveTimeout(ws->rainHandle);
				ws->rainHandle =
						compAddTimeout(o->value.i, waterRainTimeout, s);
			}
			return TRUE;
		}
		break;
	case WATER_DISPLAY_OPTION_UNGRAB_WAVE:
	case WATER_DISPLAY_OPTION_SHIVER_WIN:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static void waterDisplayInitOptions(WaterDisplay * wd)
{
	CompOption *o;

	o = &wd->opt[WATER_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Pointer wave");
	o->subGroup = N_("Initiate");
	o->displayHints = "";
	o->shortDesc = N_("Initiate");
	o->longDesc = N_("Enable pointer Water Effects.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = waterInitiate;
	o->value.action.terminate = waterTerminate;
	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 = WATER_INITIATE_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = 0;

	o = &wd->opt[WATER_DISPLAY_OPTION_TOGGLE_RAIN];
	o->advanced = False;
	o->name = "toggle_rain";
	o->group = N_("Rain");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Toggle Rain");
	o->longDesc = N_("Toggle Rain Effect (random drops of water).");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = waterToggleRain;
	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 = WATER_TOGGLE_RAIN_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(WATER_TOGGLE_RAIN_KEY_DEFAULT);

	o = &wd->opt[WATER_DISPLAY_OPTION_TOGGLE_WIPER];
	o->advanced = False;
	o->name = "toggle_wiper";
	o->group = N_("Rain");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Toggle Wiper");
	o->longDesc = N_("Toggle Wiper Effect.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = waterToggleWiper;
	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 = WATER_TOGGLE_WIPER_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(WATER_TOGGLE_WIPER_KEY_DEFAULT);

	o = &wd->opt[WATER_DISPLAY_OPTION_OFFSET_SCALE];
	o->advanced = False;
	o->name = "offset_scale";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Offset Scale");
	o->longDesc = N_("Adjusts the blur effect under the wave.");
	o->type = CompOptionTypeFloat;
	o->value.f = WATER_OFFSET_SCALE_DEFAULT;
	o->rest.f.min = WATER_OFFSET_SCALE_MIN;
	o->rest.f.max = WATER_OFFSET_SCALE_MAX;
	o->rest.f.precision = WATER_OFFSET_SCALE_PRECISION;

	o = &wd->opt[WATER_DISPLAY_OPTION_TITLE_AMPL];
	o->advanced = False;
	o->name = "title_wave_amplitude";
	o->group = N_("Title wave");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Title Wave Amplitude");
	o->longDesc = N_("Amplitude of title and window Wave.");
	o->type = CompOptionTypeFloat;
	o->value.f = WATER_TITLE_AMPL_DEFAULT;
	o->rest.f.min = WATER_TITLE_AMPL_MIN;
	o->rest.f.max = WATER_TITLE_AMPL_MAX;
	o->rest.f.precision = WATER_TITLE_AMPL_PRECISION;

	o = &wd->opt[WATER_DISPLAY_OPTION_RAIN_AMPL];
	o->advanced = False;
	o->name = "rain_wave_amplitude";
	o->group = N_("Rain");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Rain Wave Maximum Amplitude");
	o->longDesc = N_("Amplitude of Rain Wave.");
	o->type = CompOptionTypeFloat;
	o->value.f = WATER_RAIN_AMPL_DEFAULT;
	o->rest.f.min = WATER_RAIN_AMPL_MIN;
	o->rest.f.max = WATER_RAIN_AMPL_MAX;
	o->rest.f.precision = WATER_RAIN_AMPL_PRECISION;

	o = &wd->opt[WATER_DISPLAY_OPTION_VISCOSITY];
	o->advanced = False;
	o->name = "water_viscosity";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Water Viscosity");
	o->longDesc =
			N_("Water Viscosity (higher number results in larger wave).");
	o->type = CompOptionTypeFloat;
	o->value.f = WATER_VISCOS_DEFAULT;
	o->rest.f.min = WATER_VISCOS_MIN;
	o->rest.f.max = WATER_VISCOS_MAX;
	o->rest.f.precision = WATER_VISCOS_PRECISION;

	o = &wd->opt[WATER_DISPLAY_OPTION_INIT_VERTICES];
	o->advanced = False;
	o->name = "initial_vertices";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Initial Vertices");
	o->longDesc =
			N_
			("Initial number of Water Vertices (larger number results in larger wave duration).");
	o->type = CompOptionTypeInt;
	o->value.i = WATER_INIT_VERTICES_DEFAULT;
	o->rest.i.min = WATER_INIT_VERTICES_MIN;
	o->rest.i.max = WATER_INIT_VERTICES_MAX;

	o = &wd->opt[WATER_DISPLAY_OPTION_RAIN_DELAY];
	o->advanced = False;
	o->name = "rain_delay";
	o->group = N_("Rain");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Rain Delay");
	o->longDesc = N_("Delay (in ms) between each Raindrop.");
	o->type = CompOptionTypeInt;
	o->value.i = WATER_RAIN_DELAY_DEFAULT;
	o->rest.i.min = WATER_RAIN_DELAY_MIN;
	o->rest.i.max = WATER_RAIN_DELAY_MAX;

	o = &wd->opt[WATER_DISPLAY_OPTION_TITLE_WAVE];
	o->advanced = False;
	o->name = "title_wave";
	o->group = N_("Title wave");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Title Wave");
	o->longDesc = N_("Wave effect from window Title.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = waterTitleWave;
	o->value.action.terminate = 0;
	o->value.action.bell = WATER_TITLE_WAVE_BELL_DEFAULT;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeNone;
	o->value.action.state = CompActionStateInitBell;

	o = &wd->opt[WATER_DISPLAY_OPTION_POINT];
	o->advanced = False;
	o->name = "point";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Point");
	o->longDesc = N_("Add Point.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = waterPoint;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeNone;
	o->value.action.state = 0;

	o = &wd->opt[WATER_DISPLAY_OPTION_LINE];
	o->advanced = False;
	o->name = "line";
	o->group = N_("");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Line");
	o->longDesc = N_("Add Line.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = waterLine;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeNone;
	o->value.action.state = 0;

	o = &wd->opt[WATER_DISPLAY_OPTION_QUAD];
	o->advanced = False;
	o->name = "quad";
	o->group = N_("");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Quad");
	o->longDesc = N_("Add Quad.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = waterQuad;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeNone;
	o->value.action.state = 0;

	o = &wd->opt[WATER_DISPLAY_OPTION_WIN];
	o->advanced = False;
	o->name = "window_wave";
	o->group = N_("Window wave");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Window Wave");
	o->longDesc = N_("Wave effect from Window.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = waterWin;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.state |= CompActionStateInitBell;
	o->value.action.type = CompBindingTypeNone;

	o = &wd->opt[WATER_DISPLAY_OPTION_UNGRAB_WAVE];
	o->advanced = False;
	o->name = "ungrab_wave";
	o->group = N_("Window wave");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Ungrab Wave Effect");
	o->longDesc = N_("Wave when Ungrabing windows.");
	o->type = CompOptionTypeBool;
	o->value.b = WATER_UNGRAB_WAVE_DEFAULT;

	o = &wd->opt[WATER_DISPLAY_OPTION_SHIVER_WIN];
	o->advanced = False;
	o->name = "shiver_window";
	o->group = N_("Window wave");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Shiver Window");
	o->longDesc = N_("Wobbly Shiver when doing Window or ungrab wave.");
	o->type = CompOptionTypeBool;
	o->value.b = WATER_SHIVER_WIN_DEFAULT;
}

static CompOption *waterGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		WATER_DISPLAY(display);

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

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


static Bool waterInitDisplay(CompPlugin * p, CompDisplay * d)
{
	WaterDisplay *wd;

	wd = malloc(sizeof(WaterDisplay));
	if (!wd)
		return FALSE;

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

	waterDisplayInitOptions(wd);

	WRAP(wd, d, handleEvent, waterHandleEvent);

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

	return TRUE;
}

static void waterFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	WATER_DISPLAY(d);

	freeScreenPrivateIndex(d, wd->screenPrivateIndex);

	UNWRAP(wd, d, handleEvent);

	free(wd);
}

static Bool waterInitScreen(CompPlugin * p, CompScreen * s)
{
	WaterScreen *ws;

	WATER_DISPLAY(s->display);

	ws = calloc(1, sizeof(WaterScreen));
	if (!ws)
		return FALSE;

	ws->grabIndex = 0;
	ws->current = 0;

	addScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_INITIATE].value.action);
	addScreenAction(s,
					&wd->opt[WATER_DISPLAY_OPTION_TOGGLE_RAIN].value.action);
	addScreenAction(s,
					&wd->opt[WATER_DISPLAY_OPTION_TOGGLE_WIPER].value.action);
	addScreenAction(s,
					&wd->opt[WATER_DISPLAY_OPTION_TITLE_WAVE].value.action);
	addScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_POINT].value.action);
	addScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_LINE].value.action);
	addScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_QUAD].value.action);
	addScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_WIN].value.action);

	WRAP(ws, s, preparePaintScreen, waterPreparePaintScreen);
	WRAP(ws, s, donePaintScreen, waterDonePaintScreen);
	WRAP(ws, s, drawWindowTexture, waterDrawWindowTexture);
	WRAP(ws, s, windowUngrabNotify, waterWindowUngrabNotify);

	s->privates[wd->screenPrivateIndex].ptr = ws;

	waterReset(s);

	return TRUE;
}

static void waterFiniScreen(CompPlugin * p, CompScreen * s)
{
	int i;

	WATER_SCREEN(s);
	WATER_DISPLAY(s->display);

	if (ws->rainHandle)
		compRemoveTimeout(ws->rainHandle);

	if (ws->wiperHandle)
		compRemoveTimeout(ws->wiperHandle);

	if (ws->fbo)
		(*s->deleteFramebuffers) (1, &ws->fbo);

	for (i = 0; i < TEXTURE_NUM; i++)
	{
		if (ws->texture[i])
			glDeleteTextures(1, &ws->texture[i]);
	}

	for (i = 0; i < PROGRAM_NUM; i++)
	{
		if (ws->program[i])
			(*s->deletePrograms) (1, &ws->program[i]);
	}

	if (ws->data)
		free(ws->data);

	UNWRAP(ws, s, preparePaintScreen);
	UNWRAP(ws, s, donePaintScreen);
	UNWRAP(ws, s, drawWindowTexture);
	UNWRAP(ws, s, windowUngrabNotify);

	removeScreenAction(s,
					   &wd->opt[WATER_DISPLAY_OPTION_INITIATE].value.action);
	removeScreenAction(s,
					   &wd->opt[WATER_DISPLAY_OPTION_TOGGLE_RAIN].
					   value.action);
	removeScreenAction(s,
					   &wd->opt[WATER_DISPLAY_OPTION_TOGGLE_WIPER].
					   value.action);
	removeScreenAction(s,
					   &wd->opt[WATER_DISPLAY_OPTION_TITLE_WAVE].value.
					   action);
	removeScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_POINT].value.action);
	removeScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_LINE].value.action);
	removeScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_QUAD].value.action);
	removeScreenAction(s, &wd->opt[WATER_DISPLAY_OPTION_WIN].value.action);

	free(ws);
}

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

	return TRUE;
}

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

static CompPluginVTable waterVTable = {
	"water",
	N_("Water Effect"),
	N_("Adds water effects to different desktop actions"),
	waterInit,
	waterFini,
	waterInitDisplay,
	waterFiniDisplay,
	waterInitScreen,
	waterFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	waterGetDisplayOptions,
	waterSetDisplayOption,
	0,							/* GetScreenOptions */
	0,							/* SetScreenOption */
	NULL,
	0,
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins",
	"misc",
	0,
	0,
	False,
};

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


syntax highlighted by Code2HTML, v. 0.9.1