/*-
 * Copyright (c) 2001 Jordan DeLong
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "plugutil.h"

/* steps for animations */
#define NUMSTEPS	15
#define TRIMHEIGHT	20

/* per-screen structure */
static struct slidescr {
	Window	top;		/* top window */
	Window	bottom;		/* bottom window */
	Window	toptrim;	/* top trim window */
	Window	bottomtrim;	/* bottom trim window */

	Pixmap	bgpixmap;	/* background pixmap */
	Pixmap	logopixmap;	/* the logo pixmap */
	Pixmap	trimpixmap;	/* the trim stuff */

	int	height;		/* dimensions of screen */
	int	width;
} *slidescr;

/* number of screens to deal with */
static int	cnt	= 0;

/*
 * make our windows and stuff.  We don't use the golem internal client stuff because,
 * a) at init() we aren't allowed to try to make golem clients (screen_init() hasn't
 * been called yet), and b) we are making override_redirect windows.
 */
int init() {
	XSetWindowAttributes attr;
	XEvent event;
	Window dumwin;
	int dumint, screen;
	int pmwidth = -1, pmheight;
	char *bgfn, *logofn, *trimfn;

	/* get the pixmaps */
	REQUIRED_PARAM(&plugin_this->params, "pixmap", string, bgfn);
	REQUIRED_PARAMTO(&plugin_this->params, "logo", string, logofn, free1);
	REQUIRED_PARAMTO(&plugin_this->params, "trim", string, trimfn, free2);

	/* get num of screens, and malloc our window array */
	cnt = ScreenCount(display);
	slidescr = calloc(cnt, sizeof(struct slidescr));
	if (!slidescr)
		goto free3;

	/* create and map the windows */
	for (screen = 0; screen < cnt; screen++) {
		/* we need to know the screen dimensions */
		XGetGeometry(display, RootWindow(display, screen), &dumwin,
			&dumint, &dumint, &slidescr[screen].width, &slidescr[screen].height, &dumint, &dumint);

		/* get our pixmaps */
		XpmReadFileToPixmap(display, RootWindow(display, screen), bgfn, &slidescr[screen].bgpixmap, NULL, NULL);
		XpmReadFileToPixmap(display, RootWindow(display, screen), logofn, &slidescr[screen].logopixmap, NULL, NULL);
		XpmReadFileToPixmap(display, RootWindow(display, screen), trimfn, &slidescr[screen].trimpixmap, NULL, NULL);

		/* create the windows */
		attr.override_redirect = 1;
		attr.background_pixmap = slidescr[screen].bgpixmap;
		attr.save_under = 1;
		slidescr[screen].top = XCreateWindow(display, RootWindow(display, screen), 0, 0,
			slidescr[screen].width, slidescr[screen].height / 2, 0, CopyFromParent, CopyFromParent, CopyFromParent,
			CWSaveUnder | CWOverrideRedirect | CWBackPixmap, &attr);
		slidescr[screen].bottom = XCreateWindow(display, RootWindow(display, screen), 0, slidescr[screen].height / 2,
			slidescr[screen].width, slidescr[screen].height / 2, 0, CopyFromParent, CopyFromParent, CopyFromParent,
			CWSaveUnder | CWOverrideRedirect | CWBackPixmap, &attr);
		XSelectInput(display, slidescr[screen].top, StructureNotifyMask);

		/* the trim windows */
		slidescr[screen].toptrim = XCreateSimpleWindow(display, slidescr[screen].top, 0,
			(slidescr[screen].height / 2) - TRIMHEIGHT, slidescr[screen].width, TRIMHEIGHT, 0,
			BlackPixel(display, screen), BlackPixel(display, screen));
		slidescr[screen].bottomtrim = XCreateSimpleWindow(display, slidescr[screen].bottom, 0, 0,
			slidescr[screen].width, TRIMHEIGHT, 0, BlackPixel(display, screen), BlackPixel(display, screen));
		XSetWindowBackgroundPixmap(display, slidescr[screen].toptrim, slidescr[screen].trimpixmap);
		XSetWindowBackgroundPixmap(display, slidescr[screen].bottomtrim, slidescr[screen].trimpixmap);
	}

	/* this is in a seperate loop so that it appears that all screens come up at once */
	for (screen = 0; screen < cnt; screen++) {
		/* map the windows */
		XMapRaised(display, slidescr[screen].top);
		XMapRaised(display, slidescr[screen].bottom);
		XMapWindow(display, slidescr[screen].toptrim);
		XMapWindow(display, slidescr[screen].bottomtrim);

		/* no longer need events here */
		XSelectInput(display, slidescr[screen].top, NoEventMask);

		/* draw the logo */
		if (pmwidth == -1)
			XGetGeometry(display, slidescr[screen].logopixmap, &dumwin, &dumint, &dumint, &pmwidth, &pmheight,
				&dumint, &dumint);

		/* wait for it to be mapped */
		while (1) {
			/*
			 * it's safe to throw away other events because we're in init(), and thus nothing is
			 * happening except plugin init()'s (which should use their events they need within
			 * their own callbacks), and the rcfile parsing.  Doing something like this is
			 * unnacceptable (and unneccesary) anywhere other than like this.
			 */
			XNextEvent(display, &event);
			if (event.type == MapNotify && event.xmap.window == slidescr[screen].top)
				break;
		}
		XCopyArea(display, slidescr[screen].logopixmap, slidescr[screen].top, DefaultGC(display, screen), 
			0, 0, pmwidth, pmheight, 0, 0);
	}

	/* filenames need to be freed */
	free(trimfn);
	free(logofn);
	free(bgfn);
	return PLUGIN_OK;

free3:
	free(trimfn);
free2:
	free(logofn);
free1:
	free(bgfn);
	return PLUGIN_UNLOAD;
}

/*
 * when start() returns PLUGIN_UNLOAD the main golem code will call this function
 * before dlclosing us: we free any of our allocated memory.
 */
void shutdown() {
	int screen;

	/* we also destroy the windows */
	if (slidescr) {
		for (screen = 0; screen < cnt; screen++) {
			XDestroyWindow(display, slidescr[screen].top);
			XDestroyWindow(display, slidescr[screen].bottom);
			XDestroyWindow(display, slidescr[screen].toptrim);
			XDestroyWindow(display, slidescr[screen].bottomtrim);
			XFreePixmap(display, slidescr[screen].bgpixmap);
			XFreePixmap(display, slidescr[screen].logopixmap);
			XFreePixmap(display, slidescr[screen].trimpixmap);
		}

		free(slidescr);
	}
}

/*
 * this is called right after rcfile parsing and screen intialization, so when golem
 * is ready for interactive use by the user.  We make a little animatory thing, and
 * then unload.
 */
int start() {
	int screen;
	int yt[cnt], yb[cnt], mod[cnt];
	int i;

	XSync(display, 0);

	/* first we go through and figure out yt and yb and mod */
	for (screen = 0; screen < cnt; screen++) {
		yt[screen] = 0;
		yb[screen] = slidescr[screen].height / 2;
		mod[screen] = (slidescr[screen].height / 2) / NUMSTEPS;
	}

	/* wait a few frames before scrolling it */
	usleep(options.anim_delay * 3);

	/* slide our two windows up and down */
	for (i = 0; i < NUMSTEPS; i++) {
		for (screen = 0; screen < cnt; screen++) {
			yt[screen] -= mod[screen];
			yb[screen] += mod[screen];

			XMoveWindow(display, slidescr[screen].top, 0, yt[screen]);
			XMoveWindow(display, slidescr[screen].bottom, 0, yb[screen]);
		}

		XSync(display, 0);
		usleep(20);
	}
	
	return PLUGIN_UNLOAD;
}
