/*-
 * 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 "pager.h"

/*
 * pager position information from the rcfile is stored here
 * and then freed after all the pagers are created.
 */
static struct scrposinfo {
	int	posinfocnt;
	int	*posinfovalid;
	point_t	*posinfo;
} *scrposinfo = NULL;

/* per screen structure */
struct pagerscr	*pagerscr;

/* the colors to use for the pager */
static Pixel	*nonselclr;
static Pixel	*selclr;
static Pixel	*gridclr;
static Pixel	*pagedwinclr;
static Pixel	*pagedborderclr;
static Pixel	*pagedfocwinclr;
static Pixel	*pagedfocborderclr;

/* button stuffs */
static int	pager_wspacebutton;
static int	pager_dragbutton;

/* read pager positioning information from the rc file */
static int get_position_info(param_t *posparam) {
	param_t *param;
	void *tmp;
	char *p;
	int screen, desk, x, y;
	int i;

	scrposinfo = calloc(sizeof(struct scrposinfo), ScreenCount(display));
	if (!scrposinfo)
		return -1;

	/*
	 * each pager positioning info shows up as a
	 * name value pair with the format:
	 *
	 * "screen_number,desktop_number" "pagerx,pagery"
	 */
	SUBPARAMS_FOREACH(i, param, &posparam->subparams) {
		/* read in the values */
		screen = atoi(param->name);
		if ((p = strchr(param->name, ',')) == NULL) {
			PWARN("confusing parameter while trying to get"
				" pager positions");
			continue;
		}
		desk = atoi(++p);
		x = atoi(param->value);
		if ((p = strchr(param->value, ',')) == NULL) {
			PWARN("confusing parameter while trying to get"
				" pager positions");
			continue;
		}
		y = atoi(++p);

		/* validate */
		if (screen >= ScreenCount(display) || screen < 0)
			continue;

		/* get space */
		if (desk >= scrposinfo[screen].posinfocnt) {
			tmp = realloc(scrposinfo[screen].posinfo,
				sizeof(point_t) * ++scrposinfo[screen].posinfocnt);
			if (!tmp)
				return -1;
			scrposinfo[screen].posinfo = tmp;
			tmp = realloc(scrposinfo[screen].posinfovalid,
				sizeof(int) * scrposinfo[screen].posinfocnt);
			if (!tmp)
				return -1;
			scrposinfo[screen].posinfovalid = tmp;
		}

		/* set the data into the posinfo structure */
		scrposinfo[screen].posinfo[desk].x = x;
		scrposinfo[screen].posinfo[desk].y = y;
		scrposinfo[screen].posinfovalid[desk] = 1;
	}

	return 0;
}

/* free the scrposinfo stuff */
static void free_position_info() {
	int i;

	if (!scrposinfo)
		return;

	for (i = 0; i < ScreenCount(display); i++) {
		if (scrposinfo[i].posinfo)
			free(scrposinfo[i].posinfo);
		if (scrposinfo[i].posinfovalid)
			free(scrposinfo[i].posinfovalid);
	}
	free(scrposinfo);
	scrposinfo = NULL;
}

/* new clients arriving */
static int window_birth(int pcall, client_t *client) {
	pager_t *pager;

	if (client->state != NormalState || client->flags.internal)
		return PLUGIN_OK;

	pager = pagerscr[client->screen->num].pagers[client->workspace->desktop->num];
	pager_addpaged(pager, client);

	return PLUGIN_OK;
}

/* old clients departing */
static int window_death(int pcall, client_t *client) {
	pager_t *pager;
	paged_t *paged;

	if (XFindContext(display, client->window, paged_context, (XPointer *) &paged))
		return PLUGIN_OK;

	pager = pagerscr[client->screen->num].pagers[client->workspace->desktop->num];
	if (paged_focused == paged)
		paged_focused = NULL;
	pager_rmpaged(pager, paged, client);

	return PLUGIN_OK;
}

/* changes in a client's geometry */
static int geometry_change(int pcall, client_t *client) {
	pager_t *pager;
	paged_t *paged;

	/*
	 * we need to clear the window of moved pagers if we are
	 * doing parent relative background stuffs.
	 */
	if (client->flags.internal) {
		if (!pager_parentrel)
			return PLUGIN_OK;
		if (XFindContext(display, client->window, pager_context, (XPointer *) &pager))
			return PLUGIN_OK;

		XClearWindow(display, pager->win);
		pager_expose(pager, pagerscr[client->screen->num].drawgc, NULL);
	} else if (client->state == NormalState) {
		if (XFindContext(display, client->window, paged_context, (XPointer *) &paged))
			return PLUGIN_OK;

		pager = pagerscr[client->screen->num].pagers[client->workspace->desktop->num];
		pager_sizepaged(pager, paged);
	}

	return PLUGIN_OK;
}

/* window raises */
static int raise_notify(int pcall, client_t *client, client_t *lowest) {
	paged_t *paged;

	if (!XFindContext(display, client->window, paged_context, (XPointer *) &paged))
		pager_raisepaged(paged, lowest);

	return PLUGIN_OK;
}

/* focusing changes */
static int focus_change(int pcall, client_t *client) {
	paged_t *paged;

	if (!client)
		pager_focuspaged(NULL);
	else if (!XFindContext(display, client->window,
			paged_context, (XPointer *) &paged))
		pager_focuspaged(paged);

	return PLUGIN_OK;
}

/* desktop changing */
static int desktop_change(int pcall, screen_t *screen, desktop_t *olddesk) {
	client_t *client;
	paged_t *paged;
	int i;

	/* move sticky client paged_t's to the new pager */
	LIST_FOREACH(client, &client_list, c_list) {
		if (!client->flags.sticky)
			continue;
		if (!client->workspace)
			continue;
		if (client->workspace->desktop != screen->desktop)
			continue;
		if (XFindContext(display, client->window, paged_context,
				(XPointer *) &paged))
			continue;
		pager_movepaged(pagerscr[screen->num].pagers[olddesk->num], paged,
			pagerscr[screen->num].pagers[screen->desktop->num], 1);
	}

	/* find the olddesk pager, expose it and the newdesk pager */
	for (i = 0; i < pagerscr[screen->num].pager_count; i++)
		if (pagerscr[screen->num].pagers[i]->desktop == olddesk
				|| pagerscr[screen->num].pagers[i]->desktop == screen->desktop) {
			XClearWindow(display, pagerscr[screen->num].pagers[i]->win);
			pager_expose(pagerscr[screen->num].pagers[i],
				pagerscr[screen->num].drawgc, NULL);
		}

	return PLUGIN_OK;
}

/* workspace changes; we need to check for sticky windows */
static int workspace_change(int pcall, screen_t *screen, desktop_t *desktop) {
	pager_t *pager;
	paged_t *paged;
	client_t *client;

	pager = pagerscr[screen->num].pagers[desktop->num];

	/* find sticky windows and size their paged windows */
	LIST_FOREACH(client, &client_list, c_list) {
		if (!client->flags.sticky)
			continue;
		if (!client->workspace)
			continue;
		if (client->workspace != desktop->current_space)
			continue;
		if (XFindContext(display, client->window, paged_context,
				(XPointer *) &paged))
			continue;
		pager_sizepaged(pager, paged);
	}

	/* expose the pager */
	XClearWindow(display, pager->win);
	pager_expose(pager, pagerscr[screen->num].drawgc, NULL);

	return PLUGIN_OK;
}

/* xevent handler for pager window exposures */
static __inline void expose(XExposeEvent *e) {
	pager_t *pager;

	if (XFindContext(display, e->window, pager_context, (XPointer *) &pager))
		return;
	pager_expose(pager, pagerscr[pager->client->screen->num].drawgc, e);
}

/* xevent handler for pager or paged button presses */
static __inline void button_press(XButtonEvent *e) {	
	pager_t *pager;
	paged_t *paged;

	/* check if dragging windows */
	if (e->button != pager_dragbutton || e->subwindow == None)
		return;

	/* get the pager_t and paged_t */
	if (XFindContext(display, e->window, pager_context, (XPointer *) &pager))
		return;
	if (XFindContext(display, e->subwindow, paged_context, (XPointer *) &paged))
		return;

	/* pass on to the pager drag routine */
	pager_drag(pager, paged, e);
}

/* xevent handler for pager or paged button releases */
static __inline void button_release(XButtonEvent *e) {
	pager_t *pager;

	/* pass button1 clicks on to pager_click */
	if (e->button != pager_wspacebutton)
		return;
	if (XFindContext(display, e->window, pager_context, (XPointer *) &pager))
		return;
	pager_click(pager, e->x, e->y);
}

/*
 * x event handler for windows we deal with; just
 * call out to the appropriate handler.
 */
int xevent_handler(XEvent *e) {
	switch (e->type) {
	case Expose:
		expose(&e->xexpose);
		break;
	case ButtonPress:
		button_press(&e->xbutton);
		break;
	case ButtonRelease:
		button_release(&e->xbutton);
		break;
	default:
		PTRACE("unhandled event %d", e->type);
	}

	return PLUGIN_OK;
}

/* get parameters, init pager system */
int init() {
	param_t *posparam;

	/* read all parameters */
	OPTIONAL_PARAM(&plugin_this->params, "parentrelative", bool, pager_parentrel, 0);
	OPTIONAL_PARAM(&plugin_this->params, "drawgrid", bool, pager_drawgrid, 1);
	OPTIONAL_PARAM(&plugin_this->params, "nomove", bool, pager_nomove, 1);
	OPTIONAL_PARAM(&plugin_this->params, "drag_button", int, pager_dragbutton, Button2);
	OPTIONAL_PARAM(&plugin_this->params, "wspace_button", int, pager_wspacebutton, Button1);
	OPTIONAL_PARAM(&plugin_this->params, "size_ratio", double, pager_ratio, 0.04);
	OPTIONAL_PARAM(&plugin_this->params, "grid_color", color, gridclr, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "select_color", color, selclr, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "nonselect_color", color, nonselclr, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "win_color", color, pagedwinclr, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "winborder_color", color, pagedborderclr, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "focwin_color", color, pagedfocwinclr, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "focwinborder_color", color, pagedfocborderclr, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "winborder_width", int, pager_pagedbdrwidth, 1);
	OPTIONAL_PARAM(&plugin_this->params, "select_pixmap", pixmap, pager_selpixmap, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "nonselect_pixmap", pixmap, pager_nonselpixmap, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "back_pixmap", pixmap, pager_backpixmap, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "back_scale", bool, pager_backscale, 0);
	OPTIONAL_PARAM(&plugin_this->params, "win_pixmap", pixmap, pager_winpixmap, NULL);
	OPTIONAL_PARAM(&plugin_this->params, "win_scale", bool, pager_winscale, 0);
	OPTIONAL_PARAM(&plugin_this->params, "focwin_pixmap", pixmap, pager_focwinpixmap,
		pager_winpixmap);
	OPTIONAL_PARAM(&plugin_this->params, "focwin_scale", bool, pager_focwinscale, 0);
	OPTIONAL_PARAM(&plugin_this->params, "pager_dgroup", dgroup, pager_dgroup,
		options.dgroup_internal);
	OPTIONAL_PARAM(&plugin_this->params, "pager_stacklayer", stacklayer,
		pager_stacklayer, STACKLAYER_BELOW);

	/* pager positioning information is in it's own subparameter block */
	posparam = plugin_find_param(&plugin_this->params, "positions");
	if (posparam && get_position_info(posparam) == -1)
		PERR("memory allocation error in get_position_info");

	/* set up the pager system */
	pager_init();

	return PLUGIN_OK;
}

/* handle plugin shutdowns */
void shutdown() {
	int i, n;

	/* free up memory */
	if (pagerscr) {
		for (i = 0; i < ScreenCount(display); i++) {
			for (n = 0; n < pagerscr[i].pager_count; n++)
				pager_delete(pagerscr[i].pagers[n]);
			XFreeGC(display, pagerscr[i].drawgc);
			free(pagerscr[i].pagers);

			/* destroy images we may have created */
			if (pagerscr[i].sel_img)
				image_destroy(pagerscr[i].sel_img);
			if (pagerscr[i].nonsel_img)
				image_destroy(pagerscr[i].nonsel_img);
		}
		free(pagerscr);
	}

	/*
	 * some stuff that wont be free yet if start()
	 * hasn't been called.
	 */
	if (nonselclr)		free(nonselclr);
	if (selclr)		free(selclr);
	if (gridclr)		free(gridclr);
	if (pagedwinclr)	free(pagedwinclr);
	if (pagedborderclr)	free(pagedborderclr);
	if (pagedfocwinclr)	free(pagedfocwinclr);
	if (pagedfocborderclr)	free(pagedfocborderclr);

	free_position_info();
}

/* start up our pagers, make our screen structures, etc */
int start() {
	XGCValues gcvalues;
	screen_t *screen;
	desktop_t *desktop;
	pager_t *pager;
	image_t *image;
	struct pagerscr *thisscr;
	int i;

	/* register callbacks */
	plugin_callback_add(plugin_this, PCALL_WINDOW_BIRTH, window_birth);
	plugin_callback_add(plugin_this, PCALL_WINDOW_DEATH, window_death);
	plugin_callback_add(plugin_this, PCALL_ICONIFY_NOTIFY, window_death);
	plugin_callback_add(plugin_this, PCALL_RESTORE_NOTIFY, window_birth);
	plugin_callback_add(plugin_this, PCALL_GEOMETRY_CHANGE, geometry_change);
	plugin_callback_add(plugin_this, PCALL_ZOOM_NOTIFY, geometry_change);
	plugin_callback_add(plugin_this, PCALL_UNZOOM_NOTIFY, geometry_change);
	plugin_callback_add(plugin_this, PCALL_RAISE_NOTIFY, raise_notify);
	plugin_callback_add(plugin_this, PCALL_LOWER_NOTIFY, raise_notify);
	plugin_callback_add(plugin_this, PCALL_FOCUS_CHANGE, focus_change);
	plugin_callback_add(plugin_this, PCALL_DESKTOP_CHANGE, desktop_change);
	plugin_callback_add(plugin_this, PCALL_WORKSPACE_CHANGE, workspace_change);

	/* get the per-screen structure */
	pagerscr = calloc(screen_count, sizeof(struct pagerscr));
	if (!pagerscr)
		return PLUGIN_UNLOAD;

	/* fill in each screen */
	TAILQ_FOREACH(screen, &screen_list, s_list) {
		thisscr = &pagerscr[screen->num];
	
		/*
		 * if the user has specified pixmaps from the rcfile,
		 * get them as image_ts and scale them.  the original
		 * image_ts get trashed.
		 */
		if (pager_selpixmap) {
			image = image_frompixmap(pager_selpixmap, screen);
			if (!image) {
				/* XXX I assume this leaks memory */
				return PLUGIN_UNLOAD;
			}
			thisscr->sel_img = image_scale(image,
				pager_ratio * screen->width,
				pager_ratio * screen->height);
			image_destroy(image);
		}
		if (pager_nonselpixmap) {
			image = image_frompixmap(pager_nonselpixmap, screen);
			if (!image) {
				/* XXX I assume this leaks memory */
				return PLUGIN_UNLOAD;
			}
			thisscr->nonsel_img = image_scale(image,
				pager_ratio * screen->width,
				pager_ratio * screen->height);
			image_destroy(image);
		}

		/* get colors */
		thisscr->nonsel_color = nonselclr ? nonselclr[screen->num]
			: BlackPixel(display, screen->num);
		thisscr->sel_color = selclr ? selclr[screen->num]
			: WhitePixel(display, screen->num);
		thisscr->grid_color = gridclr ? gridclr[screen->num]
			: BlackPixel(display, screen->num);
		thisscr->pagedwin_color = pagedwinclr ? pagedwinclr[screen->num]
			: BlackPixel(display, screen->num);
		thisscr->pagedbdr_color = pagedborderclr ? pagedborderclr[screen->num]
			: WhitePixel(display, screen->num);
		thisscr->pagedfocwin_color = pagedfocwinclr ? pagedfocwinclr[screen->num]
			: thisscr->pagedwin_color;
		thisscr->pagedfocbdr_color = pagedfocborderclr ? pagedfocborderclr[screen->num]
			: thisscr->pagedbdr_color;

		/* get drawing graphics context */
		gcvalues.foreground = thisscr->nonsel_color;
		thisscr->drawgc = XCreateGC(display, RootWindow(display, screen->num),
			GCForeground, &gcvalues);

		/* make one pager per desktop */
		thisscr->pagers = calloc(screen->desktop_count, sizeof(pager_t *));
		thisscr->pager_count = screen->desktop_count;
		i = 0;
		TAILQ_FOREACH(desktop, &screen->s_desklist, d_list) {
			if (scrposinfo && scrposinfo[screen->num].posinfocnt > i) {
				pager = pager_create(screen, desktop,
					scrposinfo[screen->num].posinfovalid[i],
					scrposinfo[screen->num].posinfo[i].x,
					scrposinfo[screen->num].posinfo[i].y);
			} else
				pager = pager_create(screen, desktop, 0, 0, 0);
			if (!pager)
				PERR("error while creating pager");
			thisscr->pagers[i++] = pager;
		}

		/* eliminate images that are no longer needed */
		if (thisscr->nonsel_img) {
			image_destroy(thisscr->nonsel_img);
			thisscr->nonsel_img = NULL;
		}
	}

	/* free colors */
	if (nonselclr)		free(nonselclr),	nonselclr = NULL;
	if (selclr)		free(selclr),		selclr = NULL;
	if (gridclr)		free(gridclr),		gridclr = NULL;
	if (pagedwinclr)	free(pagedwinclr),	pagedwinclr = NULL;
	if (pagedborderclr)	free(pagedborderclr),	pagedborderclr = NULL;
	if (pagedfocwinclr)	free(pagedfocwinclr),	pagedfocwinclr = NULL;
	if (pagedfocborderclr)	free(pagedfocborderclr),pagedfocborderclr = NULL;

	free_position_info();

	return PLUGIN_OK;
}
