/**
 *
 * Beryl snow plugin
 *
 * snow.c
 *
 * Copyright (c) 2006 Eckhart P. <beryl@cornergraf.net>
 * Copyright (c) 2006 Brian Jørgensen <qte@fundanemt.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 **/

/*
 * Many thanks to Atie H. <atie.at.matrix@gmail.com> for providing
 * a clean plugin template
 * Also thanks to the folks from #beryl-dev, especially Quinn_Storm
 * for helping me make this possible
 */

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

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

#include <beryl.h>

// ------------------------------------------------------------  CONSTANTS  -----------------------------------------------------
#define MAX_SNOWFLAKES 10000

// ------------------------------------------------------------  WRAPPERS -----------------------------------------------------
#define GET_SNOW_DISPLAY(d)                            \
	((SnowDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define SNOW_DISPLAY(d)                                \
	SnowDisplay *sd = GET_SNOW_DISPLAY (d)

#define GET_SNOW_SCREEN(s, sd)                         \
	((SnowScreen *) (s)->privates[(sd)->screenPrivateIndex].ptr)

#define SNOW_SCREEN(s)                                 \
	SnowScreen *ss = GET_SNOW_SCREEN (s, GET_SNOW_DISPLAY (s->display))

// ------------------------------------------------------------  OPTIONS -----------------------------------------------------
#define SNOW_DISPLAY_OPTION_NUM_SNOWFLAKES	        0
#define SNOW_DISPLAY_OPTION_SNOW_SIZE        		1
#define SNOW_DISPLAY_OPTION_SNOW_SPEED        		2
#define SNOW_DISPLAY_OPTION_SCREEN_BOXING        	3
#define SNOW_DISPLAY_OPTION_SCREEN_DEPTH  		    4
#define SNOW_DISPLAY_OPTION_INITIATE                5
#define SNOW_DISPLAY_OPTION_ON_TOP				6
#define SNOW_DISPLAY_OPTION_USE_BLENDING 		7
#define SNOW_DISPLAY_OPTION_USE_TEXTURES 		8
#define SNOW_DISPLAY_OPTION_SNOW_UPDATE_DELAY	9
#define SNOW_DISPLAY_OPTION_SNOW_TEXTURES        10
#define SNOW_DISPLAY_OPTION_SNOW_DIRECTION        11
#define SNOW_DISPLAY_OPTION_ROTATION			12
#define SNOW_DISPLAY_OPTION_NUM					13

#define NUM_SNOW_DIRECTIONS			4

#define SNOW_DISPLAY_OPTION_SNOW_UPDATE_DELAY_DEFAULT 40
#define SNOW_DISPLAY_OPTION_NUM_SNOWFLAKES_DEFAULT	1500.0
#define SNOW_DISPLAY_OPTION_SNOW_SIZE_DEFAULT       		10.0
#define SNOW_DISPLAY_OPTION_SNOW_SPEED_DEFAULT        		85.0
#define SNOW_DISPLAY_OPTION_SCREEN_BOXING_DEFAULT        	400.0
#define SNOW_DISPLAY_OPTION_SCREEN_DEPTH_DEFAULT  		1000.0
#define SNOW_DISPLAY_OPTION_SNOW_TEXTURES_DEFAULT  ""
#define SNOW_DISPLAY_OPTION_INITIATE_KEY                    "F3"
#define SNOW_DISPLAY_OPTION_INITIATE_MOD                    CompSuperMask
#define SNOW_DISPLAY_OPTION_ON_TOP_DEFAULT			FALSE
#define SNOW_DISPLAY_OPTION_ROTATION_DEFAULT			TRUE
#define SNOW_DISPLAY_OPTION_USE_BLENDING_DEFAULT 		TRUE
#define SNOW_DISPLAY_OPTION_USE_TEXTURES_DEFAULT 		TRUE
#define SNOW_DISPLAY_OPTION_DIRECTION_DEFAULT		0

static char *snowDirections[] = {
	N_("Top to bottom"),
	N_("Bottom to top"),
	N_("Right to left"),
	N_("Left to right")
};

static char *snowTextures[] = {
	IMAGEDIR "/snowflake2.png"
};

#define N_SNOW_TEXTURES (sizeof (snowTextures) / sizeof (snowTextures[0]))

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

static int displayPrivateIndex = 0;

static int numFlakes;
static int snowRotate;
static int snowUpdateDelay;
static float snowSize;
static float snowSpeed;
static float boxing;
static float depth;
static Bool onTop;
static Bool useBlending;
static Bool useTextures;
static Bool displayListNeedsUpdating;

// ------------------------------------------------------------  STRUCTS -----------------------------------------------------
typedef struct _SnowDisplay
{
	int screenPrivateIndex;
	Bool useBlending;
	Bool useTextures;

	int snowTexNFiles;
	CompOptionValue *snowTexFiles;

	float snowSize;
	Bool displayListNeedsUpdating;

	CompOption opt[SNOW_DISPLAY_OPTION_NUM];

} SnowDisplay;

typedef struct _SnowScreen SnowScreen;

typedef struct _SnowTexture
{
	CompTexture tex;
	unsigned int width;
	unsigned int height;
	Bool loaded;
	GLuint dList;
} SnowTexture;

typedef struct _SnowFlake
{
	float x;
	float y;
	float z;
	float xs;
	float ys;
	float zs;
	float ra;					//rotation angle
	float rs;					//rotation speed

	SnowTexture *tex;
} SnowFlake;

static void InitiateSnowFlake(SnowScreen * ss, SnowFlake * sf);
static void setSnowflakeTexture(SnowScreen * ss, SnowFlake * sf);
static void beginRendering(SnowScreen * ss, CompScreen * s, int output);
static void setupDisplayList(SnowScreen * ss);

static void snowThink(SnowScreen * ss, SnowFlake * sf);
static void snowMove(SnowFlake * sf);

struct _SnowScreen
{
	CompScreen *s;

	Bool active;

	CompTimeoutHandle timeoutHandle;

	//PreparePaintScreenProc       preparePaintScreen;
	//DonePaintScreenProc          donePaintScreen;
	PaintScreenProc paintScreen;
	//PaintTransformedScreenProc   paintTransformedScreen;
	//ApplyScreenTransformProc       applyScreenTransform;
	//PaintBackgroundProc          paintBackground;
	PaintWindowProc paintWindow;
	DrawWindowProc drawWindow;
	//AddWindowGeometryProc        addWindowGeometry;
	//DrawWindowGeometryProc       drawWindowGeometry;
	DrawWindowTextureProc drawWindowTexture;
	//DamageWindowRectProc         damageWindowRect;
	//FocusWindowProc              focusWindow;

	SnowTexture *snowTex;
	int snowTexturesLoaded;

	GLuint displayList;

	SnowFlake allSnowFlakes[MAX_SNOWFLAKES];
};
static void snowThink(SnowScreen * ss, SnowFlake * sf)
{
	if (sf->y >= ss->s->height + boxing
		|| sf->x <= -boxing
		|| sf->y >= ss->s->width + boxing
		|| sf->z <= -(depth / 500.0) || sf->z >= 1)

	{
		InitiateSnowFlake(ss, sf);
	}
	snowMove(sf);
}

static void snowMove(SnowFlake * sf)
{
	float tmp = 1.0f / (100.0f - snowSpeed);

	sf->x += (sf->xs * (float)snowUpdateDelay) * tmp;
	sf->y += (sf->ys * (float)snowUpdateDelay) * tmp;
	sf->z += (sf->zs * (float)snowUpdateDelay) * tmp;
	sf->ra += ((float)snowUpdateDelay) / (10.0f - sf->rs);
}

static Bool stepSnowPositions(void *sc)
{
	CompScreen *s = sc;

	SNOW_SCREEN(s);
	if (!ss->active)
		return True;
	int i = 0;

	SnowFlake *snowFlake = ss->allSnowFlakes;

	for (i = 0; i < numFlakes; i++)
		snowThink(ss, snowFlake++);

	if (ss->active && !onTop)
	{
		CompWindow *w;

		for (w = s->windows; w; w = w->next)
		{
			if (w->type & CompWindowTypeDesktopMask)
			{
				addWindowDamage(w);
			}
		}
	}
	else if (ss->active)
		damageScreen(s);
	return True;
}

static Bool
snowToggle(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)
	{
		SNOW_SCREEN(s);
		ss->active = !ss->active;
		if (!ss->active)
			damageScreen(s);
	}

	return TRUE;
}

// -------------------------------------------------  HELPER FUNCTIONS ----------------------------------------------------

int GetRand(int min, int max);
int GetRand(int min, int max)
{
	return (rand() % (max - min + 1) + min);
}

float mmrand(int min, int max, float divisor);
float mmrand(int min, int max, float divisor)
{
	return ((float)GetRand(min, max)) / divisor;
};

// ------------------------------------------------- RENDERING ----------------------------------------------------
static void setupDisplayList(SnowScreen * ss)
{
	// ----------------- untextured list

	ss->displayList = glGenLists(1);

	glNewList(ss->displayList, GL_COMPILE);
	glBegin(GL_QUADS);

	glColor4f(1.0, 1.0, 1.0, 1.0);
	glVertex3f(0, 0, -0.0);
	glColor4f(1.0, 1.0, 1.0, 1.0);
	glVertex3f(0, snowSize, -0.0);
	glColor4f(1.0, 1.0, 1.0, 1.0);
	glVertex3f(snowSize, snowSize, -0.0);
	glColor4f(1.0, 1.0, 1.0, 1.0);
	glVertex3f(snowSize, 0, -0.0);

	glEnd();
	glEndList();

}

static void beginRendering(SnowScreen * ss, CompScreen * s, int output)
{
	glPushMatrix();
	glLoadIdentity();

	prepareXCoords(s, output, -DEFAULT_Z_CAMERA);

	if (useBlending)
	{
		glEnable(GL_BLEND);
	}

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	if (displayListNeedsUpdating)
	{
		setupDisplayList(ss);
		displayListNeedsUpdating = FALSE;
	}

	glColor4f(1.0, 1.0, 1.0, 1.0);
	if (ss->snowTexturesLoaded && useTextures)
	{
		int j = 0;

		for (j = 0; j < ss->snowTexturesLoaded; j++)
		{
			enableTexture(ss->s, &ss->snowTex[j].tex,
						  COMP_TEXTURE_FILTER_GOOD);

			int i = 0;
			SnowFlake *snowFlake = ss->allSnowFlakes;

			for (i = 0; i < numFlakes; i++)
			{
				if (snowFlake->tex == &ss->snowTex[j])
				{
					glTranslatef(snowFlake->x, snowFlake->y, snowFlake->z);
					if (snowRotate)
						glRotatef(snowFlake->ra, 0, 0, 1);
					glCallList(ss->snowTex[j].dList);
					if (snowRotate)
						glRotatef(-snowFlake->ra, 0, 0, 1);
					glTranslatef(-snowFlake->x, -snowFlake->y, -snowFlake->z);
				}
				snowFlake++;
			}
			disableTexture(ss->s, &ss->snowTex[j].tex);
		}
	}
	else if (!ss->snowTexturesLoaded || !useTextures)
	{
		int i = 0;
		SnowFlake *snowFlake = ss->allSnowFlakes;

		for (i = 0; i < numFlakes; i++)
		{
			glTranslatef(snowFlake->x, snowFlake->y, snowFlake->z);
			glRotatef(snowFlake->ra, 0, 0, 1);
			glCallList(ss->displayList);
			glRotatef(-snowFlake->ra, 0, 0, 1);
			glTranslatef(-snowFlake->x, -snowFlake->y, -snowFlake->z);
			snowFlake++;
		}
	}

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	if (useBlending)
	{
		glDisable(GL_BLEND);
		glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	}
	glPopMatrix();
}

// -------------------------------------------------  FUNCTIONS ----------------------------------------------------

/*static void snowPreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	SNOW_SCREEN(s);

	UNWRAP(ss, s, preparePaintScreen);
	(*s->preparePaintScreen) (s, msSinceLastPaint);
	WRAP(ss, s, preparePaintScreen, snowPreparePaintScreen);
}*/

/*static void snowDonePaintScreen(CompScreen * s)
{
	SNOW_SCREEN(s);

	UNWRAP(ss, s, donePaintScreen);
	(*s->donePaintScreen) (s);
	WRAP(ss, s, donePaintScreen, snowDonePaintScreen);
}*/

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

	SNOW_SCREEN(s);

	if (!onTop && ss->active)
	{
		// This line is essential. Without it the snow will be rendered above (some) other windows.
		mask |= PAINT_SCREEN_ORDER_BACK_TO_FRONT_MASK;
	}

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

	if (onTop && ss->active)
		beginRendering(ss, s, output);

	return status;
}

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

	SNOW_SCREEN(w->screen);

	// First draw Window as usual
	UNWRAP(ss, w->screen, paintWindow);
	status = (*w->screen->paintWindow) (w, attrib, region, mask);
	WRAP(ss, w->screen, paintWindow, snowPaintWindow);

	// Check whether this is the Desktop Window
	if (w->type & CompWindowTypeDesktopMask && ss->active && !onTop)
	{
		beginRendering(ss, w->screen, outputDeviceForWindow(w));
	}
	return status;
}

static void InitiateSnowFlake(SnowScreen * ss, SnowFlake * sf)
{
	//TODO: possibly place snowflakes based on FOV, instead of a cube.
	SNOW_DISPLAY(ss->s->display);
	CompOption *o = &sd->opt[SNOW_DISPLAY_OPTION_SNOW_DIRECTION];

	if (strcmp(snowDirections[0], o->value.s) == 0)
	{
		sf->x = mmrand(-boxing, ss->s->width + boxing, 1);
		sf->xs = mmrand(-1, 1, 500);
		sf->y = mmrand(-300, 0, 1);
		sf->ys = mmrand(1, 3, 1);
	}
	else if (strcmp(snowDirections[1], o->value.s) == 0)
	{
		sf->x = mmrand(-boxing, ss->s->width + boxing, 1);
		sf->xs = mmrand(-1, 1, 500);
		sf->y = mmrand(ss->s->height, ss->s->height + 300, 1);
		sf->ys = -mmrand(1, 3, 1);
	}
	else if (strcmp(snowDirections[2], o->value.s) == 0)
	{
		sf->x = mmrand(ss->s->width, ss->s->width + 300, 1);
		sf->xs = -mmrand(1, 3, 1);
		sf->y = mmrand(-boxing, ss->s->height + boxing, 1);
		sf->ys = mmrand(-1, 1, 500);
	}
	else if (strcmp(snowDirections[3], o->value.s) == 0)
	{
		sf->x = mmrand(-300, 0, 1);
		sf->xs = mmrand(1, 3, 1);
		sf->y = mmrand(-boxing, ss->s->height + boxing, 1);
		sf->ys = mmrand(-1, 1, 500);
	}

	sf->z = mmrand(-depth, 0.1, 5000);
	sf->zs = mmrand(-1000, 1000, 500000);
	sf->ra = mmrand(-1000, 1000, 50);
	sf->rs = mmrand(-1000, 1000, 1000);
}

static void setSnowflakeTexture(SnowScreen * ss, SnowFlake * sf)
{
	if (ss->snowTexturesLoaded)
		sf->tex = &ss->snowTex[rand() % ss->snowTexturesLoaded];
}

static void updateSnowTextures(CompScreen * s)
{
	SNOW_SCREEN(s);
	SNOW_DISPLAY(s->display);
	int i = 0;

	for (i = 0; i < ss->snowTexturesLoaded; i++)
	{
		finiTexture(s, &ss->snowTex[i].tex);
		glDeleteLists(ss->snowTex[i].dList, 1);
	}
	if (ss->snowTexturesLoaded)
		free(ss->snowTex);
	ss->snowTexturesLoaded = 0;

	ss->snowTex = calloc(1, sizeof(SnowTexture) * sd->snowTexNFiles);

	int count = 0;

	for (i = 0; i < sd->snowTexNFiles; i++)
	{
		ss->snowTex[count].loaded =
				readImageToTexture(s, &ss->snowTex[count].tex,
								   sd->snowTexFiles[i].s,
								   &ss->snowTex[count].width,
								   &ss->snowTex[count].height);
		if (!ss->snowTex[count].loaded)
		{
			printf("[Snow]: Texture not found : %s\n", sd->snowTexFiles[i].s);
			continue;
		}
		printf("[Snow]: Loaded Texture %s\n", sd->snowTexFiles[i].s);
		CompMatrix *mat = &ss->snowTex[count].tex.matrix;
		SnowTexture *sTex = &ss->snowTex[count];

		sTex->dList = glGenLists(1);

		glNewList(sTex->dList, GL_COMPILE);

		glBegin(GL_QUADS);

		glTexCoord2f(COMP_TEX_COORD_X(mat, 0), COMP_TEX_COORD_Y(mat, 0));
		glVertex2f(0, 0);
		glTexCoord2f(COMP_TEX_COORD_X(mat, 0),
					 COMP_TEX_COORD_Y(mat, sTex->height));
		glVertex2f(0, snowSize * sTex->height / sTex->width);
		glTexCoord2f(COMP_TEX_COORD_X(mat, sTex->width),
					 COMP_TEX_COORD_Y(mat, sTex->height));
		glVertex2f(snowSize, snowSize * sTex->height / sTex->width);
		glTexCoord2f(COMP_TEX_COORD_X(mat, sTex->width),
					 COMP_TEX_COORD_Y(mat, 0));
		glVertex2f(snowSize, 0);

		glEnd();
		glEndList();

		count++;
	}
	ss->snowTexturesLoaded = count;
	if (count < sd->snowTexNFiles)
		ss->snowTex = realloc(ss->snowTex, sizeof(SnowTexture) * count);

	SnowFlake *snowFlake = ss->allSnowFlakes;

	for (i = 0; i < MAX_SNOWFLAKES; i++)
	{
		setSnowflakeTexture(ss, snowFlake++);
	}
}

static Bool snowInitScreen(CompPlugin * p, CompScreen * s)
{
	SNOW_DISPLAY(s->display);

	SnowScreen *ss = (SnowScreen *) calloc(1, sizeof(SnowScreen));

	ss->s = s;
	s->privates[sd->screenPrivateIndex].ptr = ss;

	int i = 0;
	SnowFlake *snowFlake = ss->allSnowFlakes;

	for (i = 0; i < MAX_SNOWFLAKES; i++)
	{
		InitiateSnowFlake(ss, snowFlake);
		setSnowflakeTexture(ss, snowFlake);
		snowFlake++;
	}

	updateSnowTextures(s);

	setupDisplayList(ss);

	ss->active = FALSE;

	addScreenAction(s, &sd->opt[SNOW_DISPLAY_OPTION_INITIATE].value.action);

	WRAP(ss, s, paintScreen, snowPaintScreen);
	//WRAP(ss, s, paintTransformedScreen, snowPaintTransformedScreen);
	//WRAP(ss, s, preparePaintScreen, snowPreparePaintScreen);
	//WRAP(ss, s, donePaintScreen, snowDonePaintScreen);
	WRAP(ss, s, paintWindow, snowPaintWindow);

	ss->timeoutHandle = compAddTimeout(snowUpdateDelay, stepSnowPositions, s);

	return TRUE;
}

static void snowFiniScreen(CompPlugin * p, CompScreen * s)
{
	SNOW_SCREEN(s);
	SNOW_DISPLAY(s->display);

	compRemoveTimeout(ss->timeoutHandle);

	int i = 0;

	for (i = 0; i < ss->snowTexturesLoaded; i++)
	{
		finiTexture(s, &ss->snowTex[i].tex);
		glDeleteLists(ss->snowTex[i].dList, 1);
	}
	if (ss->snowTexturesLoaded)
		free(ss->snowTex);

	//Restore the original function
	UNWRAP(ss, s, paintScreen);
	//UNWRAP(ss, s, paintTransformedScreen);
	//UNWRAP(ss, s, preparePaintScreen);
	//UNWRAP(ss, s, donePaintScreen);
	UNWRAP(ss, s, paintWindow);

	removeScreenAction(s,
					   &sd->opt[SNOW_DISPLAY_OPTION_INITIATE].value.action);
	//Free the pointer
	free(ss);
}

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

	SNOW_DISPLAY(display);

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

	switch (index)
	{
	case SNOW_DISPLAY_OPTION_NUM_SNOWFLAKES:
		if (compSetFloatOption(o, value))
		{
			numFlakes =
					(int)sd->opt[SNOW_DISPLAY_OPTION_NUM_SNOWFLAKES].value.f;
			return TRUE;
		}
		break;

	case SNOW_DISPLAY_OPTION_SNOW_DIRECTION:
		if (compSetStringOption(o, value))
		{
			return TRUE;
		}
		break;
	case SNOW_DISPLAY_OPTION_SNOW_SIZE:
		if (compSetFloatOption(o, value))
		{
			snowSize = sd->opt[SNOW_DISPLAY_OPTION_SNOW_SIZE].value.f;
			displayListNeedsUpdating = TRUE;
			CompScreen *s = display->screens;

			updateSnowTextures(s);

			return TRUE;
		}
		break;
	case SNOW_DISPLAY_OPTION_SNOW_SPEED:
		if (compSetFloatOption(o, value))
		{
			snowSpeed = sd->opt[SNOW_DISPLAY_OPTION_SNOW_SPEED].value.f;
			return TRUE;
		}
		break;
	case SNOW_DISPLAY_OPTION_SNOW_UPDATE_DELAY:
		if (compSetIntOption(o, value))
		{
			CompScreen *s = display->screens;

			SNOW_SCREEN(s);
			snowUpdateDelay =
					sd->opt[SNOW_DISPLAY_OPTION_SNOW_UPDATE_DELAY].value.i;
			if (ss->timeoutHandle)
				compRemoveTimeout(ss->timeoutHandle);
			ss->timeoutHandle =
					compAddTimeout(snowUpdateDelay, stepSnowPositions, s);
			return TRUE;
		}
	case SNOW_DISPLAY_OPTION_SCREEN_BOXING:
		if (compSetFloatOption(o, value))
		{
			boxing = sd->opt[SNOW_DISPLAY_OPTION_SCREEN_BOXING].value.f;
			return TRUE;
		}
		break;
	case SNOW_DISPLAY_OPTION_SCREEN_DEPTH:
		if (compSetFloatOption(o, value))
		{
			depth = sd->opt[SNOW_DISPLAY_OPTION_SCREEN_DEPTH].value.f;
			return TRUE;
		}
		break;
	case SNOW_DISPLAY_OPTION_SNOW_TEXTURES:
		if (compSetOptionList(o, value))
		{
			CompScreen *s = display->screens;

			sd->snowTexFiles =
					sd->opt[SNOW_DISPLAY_OPTION_SNOW_TEXTURES].value.list.
					value;
			sd->snowTexNFiles =
					sd->opt[SNOW_DISPLAY_OPTION_SNOW_TEXTURES].value.list.
					nValue;

			updateSnowTextures(s);
			return TRUE;
		}
		break;

	case SNOW_DISPLAY_OPTION_INITIATE:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	case SNOW_DISPLAY_OPTION_ON_TOP:
		if (compSetBoolOption(o, value))
		{
			onTop = sd->opt[SNOW_DISPLAY_OPTION_ON_TOP].value.b;
			return TRUE;
		}
		break;
	case SNOW_DISPLAY_OPTION_ROTATION:
		if (compSetBoolOption(o, value))
		{
			snowRotate = sd->opt[SNOW_DISPLAY_OPTION_ROTATION].value.b;
			return TRUE;
		}
		break;
	case SNOW_DISPLAY_OPTION_USE_BLENDING:
		if (compSetBoolOption(o, value))
		{
			useBlending = sd->opt[SNOW_DISPLAY_OPTION_USE_BLENDING].value.b;
			return TRUE;
		}
	case SNOW_DISPLAY_OPTION_USE_TEXTURES:
		if (compSetBoolOption(o, value))
		{
			useTextures = sd->opt[SNOW_DISPLAY_OPTION_USE_TEXTURES].value.b;
			return TRUE;
		}
		break;
	default:
		break;
	}
	return FALSE;
}

static void snowDisplayInitOptions(SnowDisplay * sd)
{
	CompOption *o;

	o = &sd->opt[SNOW_DISPLAY_OPTION_NUM_SNOWFLAKES];
	o->advanced = False;
	o->name = "num_snowflakes";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Number of Snowflakes");
	o->longDesc = N_("Number of Snowflakes");
	o->type = CompOptionTypeFloat;
	o->value.f = SNOW_DISPLAY_OPTION_NUM_SNOWFLAKES_DEFAULT;
	o->rest.f.min = 0;
	o->rest.f.max = 10000;
	o->rest.f.precision = 1;

	o = &sd->opt[SNOW_DISPLAY_OPTION_SNOW_SIZE];
	o->advanced = False;
	o->name = "snow_size";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Size of snowflakes");
	o->longDesc = N_("Size of snowflakes");
	o->type = CompOptionTypeFloat;
	o->value.f = SNOW_DISPLAY_OPTION_SNOW_SIZE_DEFAULT;
	o->rest.f.min = 0;
	o->rest.f.max = 50;
	o->rest.f.precision = 0.1;

	o = &sd->opt[SNOW_DISPLAY_OPTION_SNOW_SPEED];
	o->advanced = False;
	o->name = "snow_speed";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Speed of falling snow");
	o->longDesc = N_("Speed of falling snow");
	o->type = CompOptionTypeFloat;
	o->value.f = SNOW_DISPLAY_OPTION_SNOW_SPEED_DEFAULT;
	o->rest.f.min = 0;
	o->rest.f.max = 100;
	o->rest.f.precision = 1;

	o = &sd->opt[SNOW_DISPLAY_OPTION_SNOW_UPDATE_DELAY];
	o->advanced = False;
	o->name = "snow_update_delay";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Update Delay");
	o->longDesc =
			N_
			("Delay in ms between screen updates. Deacreasing this value may make snow fall more smoothly, but will also increase CPU usage.");
	o->type = CompOptionTypeInt;
	o->value.i = SNOW_DISPLAY_OPTION_SNOW_UPDATE_DELAY_DEFAULT;
	o->rest.i.min = 10;
	o->rest.i.max = 200;

	o = &sd->opt[SNOW_DISPLAY_OPTION_SCREEN_BOXING];
	o->advanced = False;
	o->name = "screen_boxing";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Screen Boxing");
	o->longDesc =
			N_
			("How far outside the screen resolution snowflakes can be before being removed. Needed because of FOV.");
	o->type = CompOptionTypeFloat;
	o->value.f = SNOW_DISPLAY_OPTION_SCREEN_BOXING_DEFAULT;
	o->rest.f.min = -2000;
	o->rest.f.max = 2000;
	o->rest.f.precision = 1;

	o = &sd->opt[SNOW_DISPLAY_OPTION_SCREEN_DEPTH];
	o->advanced = False;
	o->name = "screen_depth";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Screen Depth");
	o->longDesc =
			N_
			("How deep into the screen snowflakes can be drawn before being removed.");
	o->type = CompOptionTypeFloat;
	o->value.f = SNOW_DISPLAY_OPTION_SCREEN_DEPTH_DEFAULT;
	o->rest.f.min = 0;
	o->rest.f.max = SNOW_DISPLAY_OPTION_SCREEN_DEPTH_DEFAULT * 2;
	o->rest.f.precision = 1;

	o = &sd->opt[SNOW_DISPLAY_OPTION_SNOW_TEXTURES];
	o->advanced = False;
	o->name = "snow_textures";
	o->group = N_("Textures");
	o->subGroup = N_("");
	o->displayHints = "file;image;pngonly;";
	o->shortDesc = N_("Snow Textures");
	o->longDesc = N_("Snow textures");
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_SNOW_TEXTURES;
	o->value.list.value = malloc(sizeof(CompOptionValue) * N_SNOW_TEXTURES);
	int i = 0;

	for (i = 0; i < N_SNOW_TEXTURES; i++)
		o->value.list.value[i].s = strdup(snowTextures[i]);
	o->rest.s.string = 0;
	o->rest.s.nString = 0;

	o = &sd->opt[SNOW_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "Initiate";
	o->group = N_("Key Bindings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("snow toggle key");
	o->longDesc = N_("snow toggle key");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = snowToggle;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.key.modifiers = SNOW_DISPLAY_OPTION_INITIATE_MOD;
	o->value.action.key.keysym =
			XStringToKeysym(SNOW_DISPLAY_OPTION_INITIATE_KEY);

	o = &sd->opt[SNOW_DISPLAY_OPTION_ON_TOP];
	o->advanced = False;
	o->name = "snow_over_windows";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Snow over windows.");
	o->longDesc = N_("Snow is drawn above windows.");
	o->type = CompOptionTypeBool;
	o->value.b = SNOW_DISPLAY_OPTION_ON_TOP_DEFAULT;

	o = &sd->opt[SNOW_DISPLAY_OPTION_ROTATION];
	o->advanced = False;
	o->name = "snow_rotation";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Rotate flakes");
	o->longDesc = N_("Flakes rotate when checked.");
	o->type = CompOptionTypeBool;
	o->value.b = SNOW_DISPLAY_OPTION_ROTATION_DEFAULT;

	o = &sd->opt[SNOW_DISPLAY_OPTION_SNOW_DIRECTION];
	o->advanced = False;
	o->name = "snow_direction";
	o->group = N_("Settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Snow Direction");
	o->longDesc = N_("Select direction in which the snow should fly.");
	o->type = CompOptionTypeString;
	o->value.s =
			strdup(snowDirections[SNOW_DISPLAY_OPTION_DIRECTION_DEFAULT]);
	o->rest.s.string = snowDirections;
	o->rest.s.nString = NUM_SNOW_DIRECTIONS;

	o = &sd->opt[SNOW_DISPLAY_OPTION_USE_BLENDING];
	o->advanced = False;
	o->name = "use_blending";
	o->group = N_("Debug");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Enable Blending");
	o->longDesc = N_("Enables alpha blending of snowflakes");
	o->type = CompOptionTypeBool;
	o->value.b = SNOW_DISPLAY_OPTION_USE_BLENDING_DEFAULT;

	o = &sd->opt[SNOW_DISPLAY_OPTION_USE_TEXTURES];
	o->advanced = False;
	o->name = "use_textures";
	o->group = N_("Debug");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Enable Textures");
	o->longDesc =
			N_
			("Enables textured snowflakes. Otherwise color gradients are used.");
	o->type = CompOptionTypeBool;
	o->value.b = SNOW_DISPLAY_OPTION_USE_TEXTURES_DEFAULT;
}

static CompOption *snowGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		SNOW_DISPLAY(display);

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

		snowDisplayInitOptions(sd);

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

static Bool snowInitDisplay(CompPlugin * p, CompDisplay * d)
{
	//Generate a snow display
	SnowDisplay *sd = (SnowDisplay *) malloc(sizeof(SnowDisplay));

	//Allocate a private index
	sd->screenPrivateIndex = allocateScreenPrivateIndex(d);

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

	numFlakes = SNOW_DISPLAY_OPTION_NUM_SNOWFLAKES_DEFAULT;
	snowRotate = SNOW_DISPLAY_OPTION_ROTATION_DEFAULT;
	snowSize = SNOW_DISPLAY_OPTION_SNOW_SIZE_DEFAULT;
	snowSpeed = SNOW_DISPLAY_OPTION_SNOW_SPEED_DEFAULT;
	snowUpdateDelay = SNOW_DISPLAY_OPTION_SNOW_UPDATE_DELAY_DEFAULT;
	boxing = SNOW_DISPLAY_OPTION_SCREEN_BOXING_DEFAULT;
	depth = SNOW_DISPLAY_OPTION_SCREEN_DEPTH_DEFAULT;
	onTop = SNOW_DISPLAY_OPTION_ON_TOP_DEFAULT;

	displayListNeedsUpdating = FALSE;
	useBlending = SNOW_DISPLAY_OPTION_USE_BLENDING_DEFAULT;
	useTextures = SNOW_DISPLAY_OPTION_USE_TEXTURES_DEFAULT;
	snowDisplayInitOptions(sd);

	sd->snowTexFiles =
			sd->opt[SNOW_DISPLAY_OPTION_SNOW_TEXTURES].value.list.value;
	sd->snowTexNFiles =
			sd->opt[SNOW_DISPLAY_OPTION_SNOW_TEXTURES].value.list.nValue;

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

	return TRUE;
}

static void snowFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	SNOW_DISPLAY(d);

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

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

	if (displayPrivateIndex < 0)
		return FALSE;

	return TRUE;
}

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

CompPluginDep snowDeps[] = {
	{CompPluginRuleAfterCategory, "imageformat"}
	,
};

CompPluginVTable snowVTable = {
	"snow",
	N_("Snow"),
	N_("XSnow for Beryl"),
	snowInit,
	snowFini,
	snowInitDisplay,
	snowFiniDisplay,
	snowInitScreen,
	snowFiniScreen,
	0,
	0,
	snowGetDisplayOptions,
	snowSetDisplayOption,
	0,							/*snowGetScreenOptions */
	0,							/*snowSetScreenOption */
	snowDeps,
	sizeof(snowDeps) / sizeof(snowDeps[0]),
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins-unsupported",
	"misc",
	0,
	0,
	False,
};

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


syntax highlighted by Code2HTML, v. 0.9.1