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

/* list of piers we have running */
static SLIST_HEAD(, pier) pier_list = SLIST_HEAD_INITIALIZER(&pier_list);

/* pixmap for pier tiles */
static pixmap_t	*pier_tile	= NULL;

/* context for piers */
static XContext	pier_context;

/* size of the pieritem boxes */
int		pier_size	= 0;

/* list of commands we are waiting for */
comtablist_t	comtab_list	= SLIST_HEAD_INITIALIZER(comtab_list);

/* set up the pier system */
int pier_init(int size, pixmap_t *pixmap) {
	pier_context = XUniqueContext();
	pier_tile = pixmap;
	pier_size = size;
	return 0;
}

/* shutdown the pier system */
void pier_shutdown() {
	comtab_t *comtab, *tmptab;
	pier_t *pier;

	/* free any comtabs that may still be here */
	comtab = LIST_FIRST(&comtab_list);
	while (comtab) {
		tmptab = LIST_NEXT(comtab, ct_list);
		free(comtab);
		comtab = tmptab;
	}
	LIST_INIT(&comtab_list);

	/* delete every pier */
	while (!SLIST_EMPTY(&pier_list)) {
		pier = SLIST_FIRST(&pier_list);
		pier_delete(pier);
	}
}

/* create a pier */
pier_t *pier_create(int screen, int type, int x, int y) {
	pier_t *pier;

	pier = calloc(1, sizeof(pier_t));
	if (!pier)
		return NULL;
	pier->screen = screen;
	pier->type = type;
	pier->x = x;
	pier->y = y;

	SLIST_INSERT_HEAD(&pier_list, pier, p_list);
	return pier;
}

/* get rid of a pier */
void pier_delete(pier_t *pier) {
	int i;

	/* free each pieritem */
	for (i = 0; i < pier->item_count; i++)
		pier_freeitem(pier->items[i]);

	/* kill the pier window */
	if (pier->window)
		XDestroyWindow(display, pier->window);
	if (pier->items)
		free(pier->items);

	/* remove the pier structure */
	SLIST_REMOVE(&pier_list, pier, pier, p_list);
	free(pier);
}

/* add an item to a pier */
pieritem_t *pier_additem(pier_t *pier, int type, char *cmd, char *res_name, char *res_class, char *xpmfn) {
	pieritem_t *item;
	pieritem_t **tmp;

	item = calloc(1, sizeof(pieritem_t));
	if (!item)
		return NULL;
	item->type = type;
	item->cmd = cmd;
	item->classhint.res_name = res_name;
	item->classhint.res_class = res_class;

	/* get the pixmap for this item */
	if ((item->type == PI_LAUNCH || item->type == PI_NOTHING) && xpmfn) {
		XpmReadFileToPixmap(display, RootWindow(display, pier->screen), xpmfn,
			&item->pixmap, &item->shapemask, NULL);
		free(xpmfn);
	}

	/* add it to the items for this pier */
	tmp = realloc(pier->items, (pier->item_count + 1) * sizeof(pieritem_t *));
	if (!tmp)
		goto free1;
	pier->items = tmp;
	pier->items[pier->item_count++] = item;

	return item;
free1:
	free(item);
	return NULL;
}

/* just free an item: don't bother updating a pier_t structure */
void pier_freeitem(pieritem_t *item) {
	if (item->frame) {
		XDeleteContext(display, item->frame, pier_context);
		plugin_rmcontext(item->frame);
		XDestroyWindow(display, item->frame);
	}
	if (item->window)
		XDestroyWindow(display, item->window);
	if (item->cmd)
		free(item->cmd);
	if (item->classhint.res_name)
		free(item->classhint.res_name);
	if (item->classhint.res_class)
		free(item->classhint.res_class);
	if (item->pid) {
		if (kill(item->pid, SIGTERM) == -1)
			PWARN("couldn't kill dock/swallow pid");
	}
	free(item);
}

/* realize a pier */
int pier_realize(pier_t *pier) {
	XSetWindowAttributes attr;
	comtab_t *comtab;
	Window dumwin;
	long mask;
	pieritem_t *item;
	int i, x, y;
	int wid, hei, dumint;

	/* 'realizing' a pier w/ no items deletes it */
	if (pier->item_count == 0)
		pier_delete(pier);

	/* get dimensions */
	switch (pier->type) {
	case PT_HORIZ:
		pier->width = pier_size * pier->item_count;
		pier->height = pier_size;
		break;
	case PT_VERT:
		pier->width = pier_size;
		pier->height = pier_size * pier->item_count;
		break;
	}
	if (pier->x == -1)
		pier->x = DisplayWidth(display, pier->screen) - pier->width;
	if (pier->y == -1)
		pier->y = DisplayHeight(display, pier->screen) - pier->height;

	/* make the top-level frame window */
	attr.override_redirect = 1;
	pier->window = XCreateWindow(display, RootWindow(display, pier->screen), pier->x, pier->y,
		pier->width, pier->height, 0, CopyFromParent, CopyFromParent, CopyFromParent,
		CWOverrideRedirect, &attr);

	/* create the frame for each item */
	for (i = 0, x = 0, y = 0; i < pier->item_count; i++) {
		item = pier->items[i];
		
		mask = CWOverrideRedirect;
		attr.override_redirect = 1;
		if (pier_tile) {
			attr.background_pixmap = pier_tile->pixmaps[pier->screen];
			mask |= CWBackPixmap;
		} else {
			attr.background_pixel = BlackPixel(display, pier->screen);
			mask |= CWBackPixel;
		}
		item->frame = XCreateWindow(display, pier->window, x, y, pier_size,
			pier_size, 0, CopyFromParent, CopyFromParent, CopyFromParent, mask, &attr);
		plugin_setcontext(plugin_this, item->frame);
		XSaveContext(display, item->frame, pier_context, (XPointer) pier);
		XSelectInput(display, item->frame, ButtonReleaseMask | ButtonPressMask | Button2MotionMask);

		/* per-type realization */
		switch (item->type) {
		case PI_NOTHING:
		case PI_LAUNCH:
			/* if this is a launcher, and has a pixmap, we'll use it in item->window */
			if (item->pixmap) {
				XGetGeometry(display, item->pixmap, &dumwin, &dumint, &dumint,
					&wid, &hei, &dumint, &dumint);
				item->window = XCreateSimpleWindow(display, item->frame, pier_size / 2 - wid / 2,
					pier_size / 2 - hei / 2, wid, hei, 0,
					BlackPixel(display, pier->screen), BlackPixel(display, pier->screen));
				XSetWindowBackgroundPixmap(display, item->window, item->pixmap);
				XShapeCombineMask(display, item->window, ShapeBounding, 0, 0, item->shapemask, ShapeSet);
				XMapWindow(display, item->window);
			}
			break;
		case PI_SWALLOWED:
		case PI_WMDOCK:
			/* launch the program and put it on our table of classes to look for */
			comtab = malloc(sizeof(comtab_t));
			if (!comtab) {
				PWARN("couldn't allocate comtab entry");
				break;
			}
			comtab->classhint.res_name = item->classhint.res_name;
			comtab->classhint.res_class = item->classhint.res_class;
			comtab->pier = pier;
			comtab->idx = i;
			LIST_INSERT_HEAD(&comtab_list, comtab, ct_list);
			PWARN("launched %s", item->cmd);
			item->pid = action_exec(pier->screen, item->cmd);
			break;
		}

		XMapWindow(display, item->frame);

		if (pier->type == PT_HORIZ)
			x += pier_size;
		else if (pier->type == PT_VERT)
			y += pier_size;
	}

	XMapWindow(display, pier->window);
	return 0;
}

/* realize all created piers: should only be called at start() */
int pier_realize_all() {
	pier_t *pier;

	SLIST_FOREACH(pier, &pier_list, p_list)
		if (pier_realize(pier) == -1)
			return -1;

	return 0;
}

/* try to find a pier using pier_context */
pier_t *pier_findpier(Window wnd) {
	pier_t *pier;

	if (XFindContext(display, wnd, pier_context, (XPointer *) &pier) != 0)
		return NULL;
	return pier;
}

/* handle button releases on piers */
void pier_click(pier_t *pier, XButtonEvent *e) {
	int idx;

	for (idx = 0; idx < pier->item_count; idx++)
		if (pier->items[idx]->frame == e->window)
			goto found;
	return;
found:
	switch (pier->items[idx]->type) {
	case PI_NOTHING:
		break;
	case PI_LAUNCH:
		action_exec(pier->screen, pier->items[idx]->cmd);
		break;
	}
}

/* we got a window made by our command for swallowed/docked apps */
void pier_gotcom(comtab_t *comtab, XMapRequestEvent *e) {
	XWMHints *wmhints;
	pier_t *pier;
	pieritem_t *item;
	Window dumwin;
	int wid, hei, dumint, border;

	pier = comtab->pier;
	item = pier->items[comtab->idx];

	switch (item->type) {
	case PI_SWALLOWED:
		item->window = e->window;
		break;
	case PI_WMDOCK:
		wmhints = XGetWMHints(display, e->window);
		if (wmhints && (wmhints->flags & IconWindowHint)) {
			item->window = wmhints->icon_window;
		} else {
			PWARN("couldn't use wmaker style docking for %s.%s",
				comtab->classhint.res_name, comtab->classhint.res_class);
			if (kill(item->pid, SIGTERM) == -1)
				PWARN("couldn't kill dock/swallow pid");
			item->window = None;
			item->pid = 0;
		}
		XFree(wmhints);
		break;
	}

	/* reparent and map the item's window */
	if (item->window) {
		XSetWindowBorder(display, item->window, 0);
		XGetGeometry(display, item->window, &dumwin, &dumint, &dumint,
			&wid, &hei, &border, &dumint);
		XReparentWindow(display, item->window, item->frame,
			(pier_size - wid) / 2 - border,
			(pier_size - hei) / 2 - border);
		XMapWindow(display, item->window);
	}

	LIST_REMOVE(comtab, ct_list);
	free(comtab);
}
