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

/* temporary */
#define ICONSIZE	64

/* per screen structure */
static struct iconscr {
	Pixmap		pixmap;		/* pixmap used as window bg for icons */
} *iconscr;

/* structure describing an icon */
typedef struct icon {
	Window		icon;		/* the icon window we created */
	client_t	*client;	/* the client it represents */

	TAILQ_ENTRY(icon) i_list;
} icon_t;

/* linked list of icons */
static TAILQ_HEAD(iconlist, icon) icon_list;

/* context for icon windows */
static XContext	icon_context;

/* information for dragging of icons */
static icon_t	*dragged_icon	= NULL;
static int	drag_x, drag_y;

/* add an icon */
static icon_t *icon_add(client_t *client) {
	XSetWindowAttributes attr;
	icon_t *icon;
	Window dumwin;
	int x, y, width, height;
	int dumint;

	icon = calloc(1, sizeof(icon_t));
	if (!icon)
		return NULL;

	icon->client = client;
	if (client->wmhints && client->wmhints->flags & IconPositionHint) {
		x = client->wmhints->icon_x;
		y = client->wmhints->icon_y;
	} else {
		x = client->x + ((client->width + client->dgroup->left_space
			+ client->dgroup->right_space) / 2) - ICONSIZE / 2;
		y = client->y + ((client->height + client->dgroup->top_space
			+ client->dgroup->bottom_space) / 2) - ICONSIZE / 2;
	}

	/* create the window */
	attr.override_redirect = 1;
	attr.background_pixmap = iconscr[client->screen->num].pixmap;
	icon->icon = XCreateWindow(display, RootWindow(display, client->screen->num), x, y,
		ICONSIZE, ICONSIZE, 0, CopyFromParent, CopyFromParent, CopyFromParent,
		CWOverrideRedirect | CWBackPixmap, &attr);
	plugin_setcontext(plugin_this, icon->icon);
	XSaveContext(display, icon->icon, icon_context, (XPointer) icon);
	XSaveContext(display, icon->client->frame, icon_context, (XPointer) icon);

	/* use it's icon window if it has one */
	if (client->wmhints && client->wmhints->flags & IconWindowHint) {
		XGetGeometry(display, client->wmhints->icon_window, &dumwin,
			&dumint, &dumint, &width, &height, &dumint, &dumint);
		XSetWindowBorder(display, client->wmhints->icon_window, 0);
		XReparentWindow(display, client->wmhints->icon_window, icon->icon,
			ICONSIZE / 2 - width / 2, ICONSIZE / 2 - height / 2);
		XMapWindow(display, client->wmhints->icon_window);
	}

	TAILQ_INSERT_HEAD(&icon_list, icon, i_list);

	XSelectInput(display, icon->icon, ButtonPressMask | ButtonReleaseMask | Button1MotionMask);
	XMapRaised(display, icon->icon);

	return icon;
}

/* get rid of an icon */
static void icon_rm(icon_t *icon) {
	plugin_rmcontext(icon->icon);
	XDeleteContext(display, icon->icon, icon_context);
	XDeleteContext(display, icon->client->frame, icon_context);
	XDestroyWindow(display, icon->icon);

	TAILQ_REMOVE(&icon_list, icon, i_list);
	free(icon);
}

/* client window gets unmmapped, and client_t subsequently becomes invalid */
static int window_death(int pcall, client_t *client) {
	icon_t *icon;

	if (XFindContext(display, client->frame, icon_context, (XPointer *) &icon))
		return PLUGIN_OK;
	icon_rm(icon);
	
	return PLUGIN_OK;
}

/* client iconification */
static int iconify_notify(int pcall, client_t *client) {
	icon_t *icon;

	if (XFindContext(display, client->frame, icon_context, (XPointer *) &icon)) {
		if (!icon_add(client))
			return PLUGIN_UNLOAD;
	} else
		XMapRaised(display, icon->icon);

	return PLUGIN_OK;
}

/* client deiconification */
static int restore_notify(int pcall, client_t *client) {
	icon_t *icon;

	if (XFindContext(display, client->frame, icon_context, (XPointer *) &icon))
		return PLUGIN_OK;
	XUnmapWindow(display, icon->icon);
	
	return PLUGIN_OK;
}

/* x event handler for button presses */
static void button_press(XButtonEvent *e) {
	icon_t *icon;

	if (XFindContext(display, e->window, icon_context, (XPointer *) &icon))
		return;

	drag_x = e->x;
	drag_y = e->y;
	XRaiseWindow(display, icon->icon);
}

/* x event handler for presses on the icons */
static void button_release(XButtonEvent *e) {
	icon_t *icon;

	if (XFindContext(display, e->window, icon_context, (XPointer *) &icon))
		return;

	/* they were dragging, not clicking */
	if (dragged_icon) {
		dragged_icon = NULL;
		return;
	}

	/* restore it if it's still over the icon */
	if (e->x < ICONSIZE && e->y < ICONSIZE && e->x > 0 && e->y > 0)	
		action_restore(icon->client);
}

/* x event handler to allow the icons to be dragged */
static void pointer_motion(XMotionEvent *e) {
	if (!dragged_icon) {
		if (XFindContext(display, e->window, icon_context, (XPointer *) &dragged_icon))
			return;
	} else {
		/* this shouldn't happen */
		if (e->window != dragged_icon->icon) {
			dragged_icon = NULL;
			return;
		}
	}

	XMoveWindow(display, dragged_icon->icon, e->x_root - drag_x,
		e->y_root - drag_y);
}

/* x event processor */
int xevent_handler(XEvent *e) {
	switch (e->type) {
	case ButtonPress:
		button_press(&e->xbutton);
		break;
	case ButtonRelease:
		button_release(&e->xbutton);
		break;
	case MotionNotify:
		pointer_motion(&e->xmotion);
		break;
	}

	return PLUGIN_OK;
}

/* read parameters */
int init() {
	char *pmfn;
	int i, cnt;

	TAILQ_INIT(&icon_list);
	REQUIRED_PARAM(&plugin_this->params, "pixmap", string, pmfn);

	cnt = ScreenCount(display);
	iconscr = calloc(cnt, sizeof(struct iconscr));
	if (!iconscr)
		goto free;

	for (i = 0; i < cnt; i++)
		XpmReadFileToPixmap(display, RootWindow(display, i), pmfn, &iconscr[i].pixmap,
			NULL, NULL);

	free(pmfn);
	return PLUGIN_OK;

free:	
	free(pmfn);
	return PLUGIN_UNLOAD;
}

/* setup for the plugin */
int start() {
	icon_context = XUniqueContext();

	/* register plugin callbacks */
	plugin_callback_add(plugin_this, PCALL_WINDOW_DEATH, window_death);
	plugin_callback_add(plugin_this, PCALL_ICONIFY_NOTIFY, iconify_notify);
	plugin_callback_add(plugin_this, PCALL_RESTORE_NOTIFY, restore_notify);

	return PLUGIN_OK;
}

/* plugin shutdown */
void shutdown() {
	icon_t *icon;
	int i;

	while (!TAILQ_EMPTY(&icon_list)) {
		icon = TAILQ_FIRST(&icon_list);
		icon_rm(icon);
	}

	if (iconscr) {
		for (i = 0; i < screen_count; i++)
			XFreePixmap(display, iconscr[i].pixmap);
		free(iconscr);
	}
}
