/* 
 * Copyright 2004-2005 Timo Hirvonen
 * 
 * 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.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <pager.h>
#include <x.h>
#include <xmalloc.h>
#include <debug.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xft/Xft.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define WM_WAIT 15

#define TITLE_HPAD	3
#define TITLE_VPAD	1
#define POPUP_PAD	5

/* minimum draw size of window. makes moving small windows possible
 * these must be >= 2 (window borders take 2 pixels)
 */
#define WINDOW_MIN_W	3
#define WINDOW_MIN_H	3

#define WINDOW_SHADED_H	12

#define DRAG_THRESHOLD	2

/* ---------------------------------------------------------------------------
 * PRIVATE
 */

struct client_window {
	Window window;
	int x, y, w, h;

	/* -1 = sticky */
	int page;

	enum window_type type;
	char *name;

	/* WINDOW_STATE_* */
	unsigned int states;

	int icon_w, icon_h;
	char *icon_data;
};

struct pager {
	Window window;
	Window popup_window;
	Pixmap pixmap;
	GC active_win_gc;
	GC inactive_win_gc;
	GC active_page_gc;
	GC inactive_page_gc;
	GC win_border_gc;
	GC pager_border_gc;
	GC grid_gc;
	GC desk_grid_gc;

	struct client_window *windows;
	int nr_windows;

	Window active_win;
	int active_page;
        int border_width;

	/* cols, rows, desk_cols, desk_rows, page_cols, page_rows */
	int size[6];

	int x, y;
	int w, h;
	double zoom;

	/* user set geometry, these are constants
	 *
	 * if gh is 0 then height (h) is calculated and aspect ratio set
	 *
	 * pager geometry is calculated from these when window size changes
	 * or desktop layout changes
	 */
	int gx, gy, gw, gh;
	unsigned int gx_negative : 1;
	unsigned int gy_negative : 1;

	int root_w;
	int root_h;
	int page_w;
	int page_h;

	int w_extra;
	int h_extra;

	/* index to windows[] or -1. used to get the title of the window */
	int popup_idx;
	XGlyphInfo popup_extents;

	unsigned int popup_visible : 1;

	unsigned int needs_configure : 1;
	unsigned int needs_update : 1;
	unsigned int needs_update_properties : 1;
	unsigned int needs_update_popup : 1;

	unsigned int show_sticky : 1;
	unsigned int show_window_titles : 1;
	unsigned int show_window_icons : 1;
	unsigned int show_popups : 1;
	unsigned int allow_cover : 1;

	double opacity;

	enum pager_layer layer;

	struct {
		/* index to windows[] or -1. the window we are moving */
		int window_idx;

		/* mouse coordinates relative to the pager window */
		int window_x;
		int window_y;

		/* mouse button or -1 */
		int button;

		/* click coordinates relative to pager */
		int click_x;
		int click_y;

		unsigned int dragging : 1;
	} mouse;

	XftDraw *xft_draw;
	XftColor active_win_font_color;
	XftColor inactive_win_font_color;
	XftColor popup_font_color;
	XftFont *window_font;
	XftFont *popup_font;
};

static int netwm_compatibility = 0;

static GC make_gc(Window window, const char *color)
{
	GC gc;
	XGCValues values;
	unsigned long fg;
/* 	unsigned long bg; */
	
	x_parse_color(color, &fg);
	values.foreground = fg;
/* 	x_parse_color("rgb:ff/00/00", &bg); */
/* 	values.background = bg; */
	values.line_width = 1;
	values.line_style = LineSolid;
	gc = XCreateGC(display, window, GCForeground, &values);
/* 	gc = XCreateGC(display, window, GCForeground | GCBackground, &values); */
/* 	gc = XCreateGC(display, window, GCForeground | GCBackground | GCLineStyle | GCLineWidth, &values); */
	return gc;
}

static void pager_free_windows(struct pager *pager)
{
	int i;

	for (i = 0; i < pager->nr_windows; i++) {
		free(pager->windows[i].name);
		free(pager->windows[i].icon_data);
	}
	free(pager->windows);
	pager->windows = NULL;
	pager->nr_windows = 0;
}

static void pager_calc_x_y(struct pager *pager)
{
	if (pager->gx_negative) {
		pager->x = pager->root_w - pager->w - pager->gx;
	} else {
		pager->x = pager->gx;
	}
	if (pager->gy_negative) {
		pager->y = pager->root_h - pager->h - pager->gy;
	} else {
		pager->y = pager->gy;
	}
}

static void pager_update_strut(struct pager *pager)
{
	if (pager->allow_cover)
		return;

	if (pager->x >= pager->root_w || pager->y >= pager->root_h) {
		d_print("pager is outside of screen (%d,%d %dx%d)\n", pager->x, pager->y, pager->w, pager->h);
		return;
	}

	if (pager->w > pager->h) {
		if (pager->y == 0) {
			x_window_set_strut_partial(pager->window, STRUT_TYPE_TOP, pager->h, pager->x, pager->x + pager->w);
		} else if (pager->y + pager->h == pager->root_h) {
			x_window_set_strut_partial(pager->window, STRUT_TYPE_BOTTOM, pager->h, pager->x, pager->x + pager->w);
		} else {
			d_print("pager not at edge of screen (%d,%d %dx%d)\n", pager->x, pager->y, pager->w, pager->h);
		}
	} else {
		if (pager->x == 0) {
			x_window_set_strut_partial(pager->window, STRUT_TYPE_LEFT, pager->w, pager->y, pager->y + pager->h);
		} else if (pager->x + pager->w == pager->root_w) {
			x_window_set_strut_partial(pager->window, STRUT_TYPE_RIGHT, pager->w, pager->y, pager->y + pager->h);
		} else {
			d_print("pager not at edge of screen (%d,%d %dx%d)\n", pager->x, pager->y, pager->w, pager->h);
		}
	}
}

static void pager_configure(struct pager *pager)
{
	int x, y, w, h;

	if (x_window_get_geometry(pager->window, &x, &y, &pager->w, &pager->h)) {
		d_print("x_window_get_geometry failed\n");
		return;
	}

	pager_calc_x_y(pager);
	if (x != pager->x || y != pager->y) {
		d_print("forcing position\n");
		x_window_set_geometry(pager->window, XValue | YValue, pager->x, pager->y, 0, 0);
	}

	pager->needs_configure = 0;
	pager->needs_update = 1;

        w = pager->w - 2 * pager->border_width - pager->size[COLS] + 1;
        h = pager->h - 2 * pager->border_width - pager->size[ROWS] + 1;

	pager->page_w = w / pager->size[COLS];
	pager->page_h = h / pager->size[ROWS];

	pager->w_extra = w - pager->size[COLS] * pager->page_w;
	pager->h_extra = h - pager->size[ROWS] * pager->page_h;
	XFreePixmap(display, pager->pixmap);
	pager->pixmap = XCreatePixmap(display,
			pager->window,
			pager->w, pager->h,
			DefaultDepth(display, DefaultScreen(display)));
	XSetWindowBackgroundPixmap(display, pager->window, pager->pixmap);
	pager_update_strut(pager);
}

static void pager_calc_w_h(struct pager *pager)
{
        pager->w = (int) (pager->gw * pager->zoom + 0.5);
	if (!pager->gh)
		pager->h = pager->w * (pager->root_h * pager->size[ROWS]) / (pager->root_w * pager->size[COLS]);
	else
		pager->h = (int) (pager->gh * pager->zoom + 0.5);

}

static void pager_update_aspect(struct pager *pager)
{
	if (!pager->gh)
		x_window_set_aspect(pager->window,
				pager->root_w * pager->size[COLS],
				pager->root_h * pager->size[ROWS]);
}

static void update_desktop_count(struct pager *pager)
{
	int rows = pager->size[ROWS];
	int cols = pager->size[COLS];
	int width = -1;
	int height = -1;

	if (netwm_compatibility &&
	    x_get_desktop_layout(&cols, &rows, &width, &height)) {
		d_print("x_get_desktop_layout failed\n");
	}
	if (rows != pager->size[ROWS] || cols != pager->size[COLS]) {
		pager->size[ROWS] = rows;
		pager->size[COLS] = cols;
		pager_update_aspect(pager);

		pager_calc_w_h(pager);
		/* pager_configure sets right values for these */
		pager->w_extra = 0;
		pager->h_extra = 0;

		if (x_window_set_geometry(pager->window, WidthValue | HeightValue, 0, 0, pager->w, pager->h)) {
		}
		XFreePixmap(display, pager->pixmap);
		pager->pixmap = XCreatePixmap(display,
				pager->window,
				pager->w, pager->h,
				DefaultDepth(display, DefaultScreen(display)));

		pager->needs_configure = 1;
	}
}

static int get_window_index(struct pager *pager, Window window)
{
	int i;

	for (i = 0; i < pager->nr_windows; i++) {
		struct client_window *win = &pager->windows[i];

		if (win->window == window)
			return i;
	}
	return -1;
}

extern int ignore_bad_window;

static void pager_update_properties(struct pager *pager)
{
	Window move_win = -1, popup_win = -1;
	Window *windows;
	int nr_windows;
	int i, j, active_col, active_row;

	update_desktop_count(pager);

	/* stacking order */
	if (x_get_client_list(1, &windows, &nr_windows) == -1) {
		fprintf(stderr, "x_get_client_list (stacking order) failed\n");
		return;
	}

	pager->needs_update_properties = 0;
	pager->needs_update = 1;

	active_col = pager->active_page % pager->size[COLS];
	active_row = pager->active_page / pager->size[COLS];
	active_col = active_col % pager->size[PAGE_COLS];
	active_row = active_row % pager->size[PAGE_ROWS];
	d_print("active page=(%d,%d)\n", active_col, active_row);

	if (pager->mouse.window_idx != -1)
		move_win = pager->windows[pager->mouse.window_idx].window;
	if (pager->popup_idx != -1)
		popup_win = pager->windows[pager->popup_idx].window;

	pager_free_windows(pager);

	pager->nr_windows = nr_windows;
	d_print("nr_windows: %d\n", nr_windows);

	pager->windows = xnew(struct client_window, pager->nr_windows);

	j = 0;
	for (i = 0; i < pager->nr_windows; i++) {
		struct client_window *win = &pager->windows[j];

		win->window = windows[i];
		ignore_bad_window = 1;

		win->type = WINDOW_TYPE_NORMAL;
		if (x_window_get_type(win->window, &win->type)) {
/* 			fprintf(stderr, "could not get window type of window 0x%x\n", (int)windows[i]); */
/* 			continue; */
		}

		win->states = 0;
		if (x_window_get_states(win->window, &win->states)) {
 			fprintf(stderr, "could not get states of window 0x%x, assuming 'MODAL'\n", (int)windows[i]); 
			win->states = WINDOW_STATE_MODAL;
		}

		ignore_bad_window = 0;

		if (win->states & WINDOW_STATE_SKIP_PAGER) {
/* 			d_print("skip pager 0x%x\n", (int)windows[i]); */
			continue;
		}
		win->page = -1;

		if (x_window_get_desktop(win->window, active_col, active_row, &win->page, &win->x, &win->y, &win->w, &win->h, pager->size)) {
			fprintf(stderr, "could not get desktop of window 0x%x\n", (int)win->window);
			continue;
		}

		if (x_window_get_title(win->window, &win->name)) {
			fprintf(stderr, "could not get name of window 0x%x\n", (int)win->window);
			/* continue; */
			win->name = xstrdup("?");
		}
		win->icon_w = -1;
		win->icon_h = -1;
		win->icon_data = NULL;
		/*
 		d_print("new window 0x%x '%s' page %d\n", (int)win->window, win->name, win->page); 
		*/
		/* FIXME: breaks sometimes */
/* 		XSelectInput(display, win->window, PropertyChangeMask); */
		j++;
	}
	ignore_bad_window = 0;
	pager->nr_windows = j;
	free(windows);

	if (move_win != -1)
		pager->mouse.window_idx = get_window_index(pager, move_win);
	if (popup_win != -1)
		pager->popup_idx = get_window_index(pager, popup_win);

	x_get_current_desktop(&pager->active_page, pager->size);
	x_get_active_window(&pager->active_win);
}

static void do_draw_window(struct pager *pager, struct client_window *window, int px, int py, int pw, int ph)
{
	GC gc;
	XftColor *color;
	XGlyphInfo extents;
	XRectangle ra;
	int len = strlen(window->name);
	int x, y, w, h;

	if (pager->active_win == window->window) {
		gc = pager->active_win_gc;
	} else {
		gc = pager->inactive_win_gc;
	}

	/* NOTE: XDrawRectangle draws rectangle one pixel larger than XFillRectangle */
   
	XFillRectangle(display, pager->pixmap, gc, px, py, pw, ph);
	XDrawRectangle(display, pager->pixmap, pager->win_border_gc, px, py, pw, ph);

	if (! pager->show_window_titles)
		return;

	if (!opt_transient && pager->zoom * pager->zoom < zoom)
		return;

	ra.x = px + TITLE_HPAD;
	ra.y = py + TITLE_VPAD;
	w = pw - 2 * TITLE_HPAD;
	h = ph - 2 * TITLE_VPAD;

	if (w < 1 || h < 1)
		return;

	ra.width = w;
	ra.height = h;

	XftTextExtentsUtf8(display, pager->window_font, (FcChar8 *)window->name, len, &extents);
/* 		d_print("w = %d, h = %d, x = %d, y = %d, xoff = %d, yoff = %d\n", extents.width, extents.height, extents.x, extents.y, extents.xOff, extents.yOff); */

	x = ra.x + extents.x;
	y = ra.y + extents.y;
	if (extents.width < ra.width)
		x += (ra.width - extents.width) / 2;
	y += (ra.height - extents.height) / 2;

	if (pager->active_win == window->window) {
		color = &pager->active_win_font_color;
	} else {
		color = &pager->inactive_win_font_color;
	}
	XftDrawSetClipRectangles(pager->xft_draw, 0, 0, &ra, 1);
	XftDrawStringUtf8(pager->xft_draw, color,
			  pager->window_font, x, y,
			  (FcChar8 *)window->name, len);
}

static void draw_window(struct pager *pager, struct client_window *window)
{
	int col, row;
	int x_off, y_off;
	double x_scale, y_scale;
	int px, py, pw, ph;

	d_print(" | %d: %s %d %d", window->page, window->name, window->x, window->y);

	x_scale = ((double)pager->root_w - win_shift_x) / ((double)(pager->page_w + 1));
	y_scale = ((double)pager->root_h - win_shift_y) / ((double)(pager->page_h + 1));

	px = (int)((double)(window->x - win_shift_x) / x_scale) + pager->border_width;
	py = (int)((double)(window->y - win_shift_y) / y_scale) + pager->border_width;
        
	pw = (int)((double)window->w / x_scale);
	if (window->states & WINDOW_STATE_SHADED) {
		ph = (int)((double)WINDOW_SHADED_H / y_scale);
	} else {
		ph = (int)((double)window->h / y_scale);
	}

	if (pw < WINDOW_MIN_W)
		pw = WINDOW_MIN_W;
	if (ph < WINDOW_MIN_H)
		ph = WINDOW_MIN_H;

	XftDrawChange(pager->xft_draw, pager->pixmap);

	if (window->page == -1) {
		if (!pager->show_sticky)
			return;

		d_print(" | %d: %d %d", window->page, window->x, window->y);
		for (row = 0; row < pager->size[ROWS]; row++) {
			for (col = 0; col < pager->size[COLS]; col++) {
				x_off = col * (pager->page_w + 1);
				y_off = row * (pager->page_h + 1);

				do_draw_window(pager, window, px + x_off, py + y_off, pw, ph);
			}
		}
	} else {
		col = window->page % pager->size[COLS];
		row = window->page / pager->size[COLS];
		col = (col / pager->size[PAGE_COLS]) * pager->size[PAGE_COLS];
		row = (row / pager->size[PAGE_ROWS]) * pager->size[PAGE_ROWS];
		x_off = col * (pager->page_w + 1) + col;
		y_off = row * (pager->page_h + 1) + row;
		do_draw_window(pager, window, px + x_off, py + y_off, pw, ph);
	}
}

/* convert mouse coordinates (@x, @y) (which are relative to the pager window)
 * to root window coordinates (@rx, @ry) and desktop value (@desk)
 */
static void pager_coords_to_root(struct pager *pager, int x, int y, int *rx, int *ry, int *page)
{
	int col, row, w, h;

	x -= pager->border_width;
        if (x < 0) x = 0;
        y -= pager->border_width;
        if (y < 0) y = 0;

	w = pager->page_w + 1;
	h = pager->page_h + 1;

	col = x / w;
	x -= col * w;
	row = y / h;
	y -= row * h;

	*page =  row * pager->size[COLS] + col;

	col = col % pager->size[PAGE_COLS];
	row = row % pager->size[PAGE_ROWS];

	*rx = (x * pager->root_w) / pager->page_w + col * pager->root_w;
	*ry = (y * pager->root_h) / pager->page_h + row * pager->root_h;
}

static int get_window_idx(struct pager *pager, int rx, int ry, int page)
{
	double scale = (double)pager->root_w / (double)pager->page_w;
	int i;

	for (i = pager->nr_windows - 1; i >= 0; i--) {
		struct client_window *window = &pager->windows[i];
		int w, h, sw, sh;

		if (!(window->page == page || (window->page == -1 && pager->show_sticky)))
			continue;

		if (window->type == WINDOW_TYPE_DESKTOP)
			continue;

		if (window->states & WINDOW_STATE_HIDDEN)
			continue;

		h = window->h;
		w = window->w;
		if (window->states & WINDOW_STATE_SHADED)
			h = WINDOW_SHADED_H;

		sw = (double)w / scale;
		sh = (double)h / scale;
		if (sw < WINDOW_MIN_W)
			w = (double)WINDOW_MIN_W * scale;
		if (sh < WINDOW_MIN_H)
			h = (double)WINDOW_MIN_H * scale;

		if (rx >= window->x && rx < window->x + w &&
		    ry >= window->y && ry < window->y + h)
			return i;
	}
	return -1;
}

static void pager_update(struct pager *pager)
{
	int row, col, x, y, i;
	int showing_desktop = 0;

	pager->needs_update = 0;

	/* desktops */
	for (row = 0; row < pager->size[ROWS]; row++) {
		y = row * (pager->page_h + 1) + pager->border_width;
		for (col = 0; col < pager->size[COLS]; col++) {
			x = col * (pager->page_w + 1) + pager->border_width;
			if (row * pager->size[COLS] + col == pager->active_page) {
				XFillRectangle(display, pager->pixmap,
						pager->active_page_gc,
						x, y, pager->page_w+1, pager->page_h+1);
			} else {
				XFillRectangle(display, pager->pixmap,
						pager->inactive_page_gc,
						x, y, pager->page_w+1, pager->page_h+1);
			}
		}
	}

	if (pager->w_extra) {
		XFillRectangle(display, pager->pixmap,
				pager->inactive_page_gc,
				pager->w - pager->w_extra + pager->border_width,
				pager->border_width,
				pager->w_extra,
				pager->h);
	}

	if (pager->h_extra) {
		XFillRectangle(display, pager->pixmap,
				pager->inactive_page_gc,
				pager->border_width,
				pager->h - pager->h_extra + pager->border_width,
				pager->w - pager->w_extra,
				pager->h_extra);
	}

	/* border of pager */
        for (i = 0; i<pager->border_width; i++)
		XDrawRectangle(display, pager->pixmap, pager->pager_border_gc, i, i, pager->w -2*i-1, pager->h - 2*i-1);


	/* grid */
	x = pager->size[ROWS] / pager->size[DESK_ROWS];
	for (row = 1; row < pager->size[ROWS]; row++) {
		y = row * (pager->page_h + 1) - 1;
		i = row % x;
		XDrawLine(display, pager->pixmap, (i)? pager->grid_gc:pager->desk_grid_gc, pager->border_width, y+ pager->border_width, pager->w - pager->border_width, y + pager->border_width);
	}

	y = pager->size[COLS] / pager->size[DESK_COLS];
	for (col = 1; col < pager->size[COLS]; col++) {
		x = col * (pager->page_w + 1) - 1;
		i = col % y;
		XDrawLine(display, pager->pixmap, (i)? pager->grid_gc:pager->desk_grid_gc, x + pager->border_width, pager->border_width, x + pager->border_width, pager->h - pager->border_width);
	}

	if (x_get_showing_desktop(&showing_desktop)) {
	}
	if (!showing_desktop) {
		/* windows */
		if (pager->nr_windows)
			d_print("windows:");
		for (i = 0; i < pager->nr_windows; i++) {
			switch (pager->windows[i].type) {
			case WINDOW_TYPE_DESKTOP:
			case WINDOW_TYPE_MENU:
				break;
			case WINDOW_TYPE_DOCK:
			case WINDOW_TYPE_TOOLBAR:
			case WINDOW_TYPE_UTILITY:
			case WINDOW_TYPE_SPLASH:
			case WINDOW_TYPE_DIALOG:
			case WINDOW_TYPE_NORMAL:
				if (!(pager->windows[i].states & WINDOW_STATE_HIDDEN))
					draw_window(pager, &pager->windows[i]);
				break;
			}
		}
		d_print("\n");
	}

	XClearWindow(display, pager->window);

/* 	XFlush(display); */
}

static void pager_update_popup(struct pager *pager)
{
	int len;
	int x, y;
	const char *text;
	XRectangle ra;

	pager->needs_update_popup = 0;
	if (pager->popup_idx == -1) {
		return;
	}

	ra.x = 0;
	ra.y = 0;
	ra.width = pager->popup_extents.width + 2 * POPUP_PAD;
	ra.height = pager->popup_extents.height + 2 * POPUP_PAD;

	text = pager->windows[pager->popup_idx].name;
	len = strlen(text);
	XClearWindow(display, pager->popup_window);
	x = POPUP_PAD + pager->popup_extents.x;
	y = POPUP_PAD + pager->popup_extents.y;
	XftDrawChange(pager->xft_draw, pager->popup_window);
	XftDrawSetClipRectangles(pager->xft_draw, 0, 0, &ra, 1);
	XftDrawStringUtf8(pager->xft_draw, &pager->popup_font_color, pager->popup_font,
			x, y, (FcChar8 *)text, len);
}

static int cursor_to_desk(struct pager *pager, int x, int y)
{
	int row, col;

	col = (x - pager->x) / (pager->page_w + 1);
	row = (y - pager->y) / (pager->page_h + 1);
	return row * pager->size[COLS] + col;
}

/* show popup window below (or above) the window in the pager */
static void popup_show(struct pager *pager, int cx, int cy)
{
	struct client_window *win;
	int x, y, w, h, len, win_row, bw;
	int x_min = 2;
	int y_min = 2;
	int x_max = pager->root_w - 2;
	int y_max = pager->root_h - 2;

	if (!pager->show_popups)
		return;
	if (!opt_transient && pager->zoom * pager->zoom < zoom)
		return;
	
	win = &pager->windows[pager->popup_idx];
	win_row = cursor_to_desk(pager, cx, cy) / pager->size[COLS];
	len = strlen(win->name);

	bw = 1;

	XftTextExtentsUtf8(display, pager->popup_font, (FcChar8 *)win->name, len, &pager->popup_extents);

	w = pager->popup_extents.width + 2 * POPUP_PAD;
	h = pager->popup_extents.y + 2 * POPUP_PAD;

	x_max -= w + bw * 2;
	y_max -= h + bw * 2;

	x = cx - w / 2;
	if (x < x_min)
		x = x_min;
	if (x > x_max)
		x = x_max;

	/* y = 4px below (or above) the window in the pager */
	y = pager->y + win_row * pager->page_h + win_row +
		(win->y + win->h) * pager->page_h / pager->root_h + 4;
	if (y + h > y_max)
		y = pager->y + win_row * pager->page_h + win_row +
			win->y * pager->page_h / pager->root_h - h - 4;
	if (y < y_min)
		y = y_min;
	XMoveResizeWindow(display, pager->popup_window, x, y, w, h);

	pager_update_popup(pager);
	
	XMapRaised(display, pager->popup_window);
	pager->popup_visible = 1;
}

static void popup_hide(struct pager *pager)
{
	if (pager->popup_visible) {
		XUnmapWindow(display, pager->popup_window);
		pager->popup_visible = 0;
	}
}

/* ---------------------------------------------------------------------------
 * PUBLIC
 */

char *active_win_color = "rgb:99/a6/bf";
char *inactive_win_color = "rgb:f6/f6/f6";
char *active_page_color = "rgb:65/72/8c";
char *inactive_page_color = "rgb:ac/ac/ac";
char *win_border_color = "rgb:00/00/00";
char *pager_border_color = "rgb:5f/5f/5f";
char *grid_color = "rgb:ff/ff/ff";
char *desk_grid_color = "rgb:00/ff/ff";

char *active_win_font_color = "rgb:00/00/00";
char *inactive_win_font_color = "rgb:00/00/00";

char *popup_color = "rgb:e6/e6/e6";
char *popup_font_color = "rgb:00/00/00";

int border_width = 0;
int win_shift_x = 0;
int win_shift_y = 0;
int win_drift_x = 0;
int win_drift_y = 0;
int switch_delay = 0;
double zoom = 1.0;
double zoom_factor = 1.04663513939;  /* 1.2^0.25 */

static int set_pager_grid(struct pager *pager,
			   int cols, int rows, int desk_cols, int desk_rows)
{
	int wm_c = 1, wm_r = 1, width = 0, height = 0;
	int x, y, failed = 0;

        netwm_compatibility = x_is_netwm_compatible_wm_running();
	d_print("NetWM compatibility: %d%s\n", 
		netwm_compatibility,
		netwm_compatibility? "": " (?)");

	/* NetWM compatible window manager must be running */
	while (1) {
		static int count = 0;
		if (x_get_desktop_layout(&wm_c, &wm_r, &width, &height) == 0) {
			d_print("Got desktop layout (%dx%d) width=%d height=%d [%dx%d]\n"
			"NetWM compatible wm should be running\n",
			wm_c, wm_r, width, height,
			width/DisplayWidth(display, DefaultScreen(display)),
			height/DisplayHeight(display, DefaultScreen(display)));
			break;
		}
		if (count == 0)
			fprintf(stderr, "NetWM compatible window manager not running. Waiting at most %d seconds.\n", WM_WAIT);
		if (count == WM_WAIT) {
			failed = 1;
			fprintf(stderr, "Couldn't get desktop layout.\n");
			fprintf(stderr, "Exiting.\n");
			break;
		}
		sleep(1);
		count++;
	}

        if (cols == -1)
		cols = width / DisplayWidth(display, DefaultScreen(display));
        if (rows == -1)
		rows = height / DisplayHeight(display, DefaultScreen(display));
        if (cols < 1)
		cols = 1;
        if (rows < 1)
		rows = 1;
        if (cols > 32)
		cols = 32;
        if (rows > 32)
		rows = 32;

	if (desk_cols == -1)
		desk_cols = wm_c;
	if (desk_rows == -1)
		desk_rows = wm_r;
	if (desk_cols < 1)
		desk_cols = 1;
	if (desk_rows < 1)
		desk_rows = 1;
	if (desk_cols > 32)
		desk_cols = 32;
	if (desk_rows > 32)
		desk_rows = 32;

	if (failed) {
		/* set desktop layout */
		width = cols * DisplayWidth(display, DefaultScreen(display));
		height = cols * DisplayHeight(display, DefaultScreen(display));
		if (x_set_desktop_layout(desk_cols, desk_rows, width, height)) {
		  fprintf(stderr, "Couldn't set desktop layout (%d x %d) width=%d height=%d.\n", desk_cols, desk_rows, width, height);
			return 1;
		}
	}

	if (x_window_get_geometry(DefaultRootWindow(display), &x, &y, &pager->root_w, &pager->root_h)) {
		d_print("x_window_get_geometry for root window failed\n");
		free(pager);
		return 1;
	}

	pager->size[PAGE_COLS] = cols;
	pager->size[PAGE_ROWS] = rows;
	pager->size[DESK_COLS] = desk_cols;
	pager->size[DESK_ROWS] = desk_rows;
	pager->size[COLS] = cols * desk_cols;
	pager->size[ROWS] = rows * desk_rows;

	return 0;
}

struct pager *pager_new(const char *geometry,
			int cols, int rows, int desk_cols, int desk_rows)
{
	unsigned long popup_bg;

	struct pager *pager;
	XSetWindowAttributes attrib;
	unsigned long attrib_mask;
	Visual *visual;
	Colormap cm;
	int gflags, gx, gy;
	unsigned int gw, gh;

	pager = xnew(struct pager, 1);

	x_parse_geometry(geometry, &gflags, &gx, &gy, &gw, &gh);
	if (!(gflags & XValue))
		gx = 0;
	if (!(gflags & YValue))
		gy = 0;
	if (!(gflags & WidthValue))
		gw = 100;
	if (!(gflags & HeightValue))
		gh = 0;
	if (gx < 0)
		gx *= -1;
	if (gy < 0)
		gy *= -1;

	if (set_pager_grid(pager, cols, rows, desk_cols, desk_rows)) {
		free(pager);
		return NULL;
	}

	pager->gx = gx;
	pager->gy = gy;
	pager->gx_negative = (gflags & XNegative) != 0;
	pager->gy_negative = (gflags & YNegative) != 0;
	pager->zoom = (opt_zoom)? zoom : 1.0;
	pager->gw = gw;
	pager->gh = gh;
	pager_calc_w_h(pager);
	pager_calc_x_y(pager);

	d_print("geometry: %s -> %dx%d+%d+%d flags: %d %d pages: %dx%d desks: %dx%d\n", 
		geometry, pager->w, pager->h, pager->x, pager->y,
		gflags & XNegative, gflags & YNegative,
		pager->size[PAGE_COLS], pager->size[PAGE_ROWS],
		pager->size[DESK_COLS], pager->size[DESK_ROWS]);

	pager->windows = NULL;
	pager->nr_windows = 0;
	pager->active_win = 0;
	pager->active_page = 0;
	pager->border_width = border_width;

	pager->popup_idx = -1;
	pager->popup_visible = 0;

	pager->needs_configure = 1;
	pager->needs_update = 1;
	pager->needs_update_properties = 1;
	pager->needs_update_popup = 0;

	pager->show_sticky = 1;
	pager->show_window_titles = 1;
	pager->show_window_icons = 1;
	pager->show_popups = 1;
	pager->allow_cover = 1;

	pager->opacity = 1.0;
	pager->layer = LAYER_COMMUTE;

	pager->mouse.window_idx = -1;
	pager->mouse.button = -1;
	pager->mouse.dragging = 0;
	pager->mouse.click_x = -1;
	pager->mouse.click_y = -1;

	/* main window */
	attrib_mask = CWBackPixmap | CWBorderPixel | CWEventMask;
	attrib.background_pixmap = ParentRelative;
	attrib.border_pixel = 0;
	attrib.event_mask = ButtonPressMask | ButtonReleaseMask |
		PointerMotionMask | EnterWindowMask | LeaveWindowMask |
		ExposureMask;
	pager->window = XCreateWindow(display, DefaultRootWindow(display),
			pager->x, pager->y,
                        pager->w, pager->h,
			0, // border
			CopyFromParent,
			InputOutput,
			CopyFromParent,
			attrib_mask, &attrib);

	/* popup window */
	attrib_mask = CWOverrideRedirect | CWEventMask;
	attrib.override_redirect = True;
	attrib.event_mask = ExposureMask;
	pager->popup_window = XCreateWindow(display, DefaultRootWindow(display),
			0, 0,
			1, 1,
			1, /* border */
			CopyFromParent,
			InputOutput,
			CopyFromParent,
			attrib_mask, &attrib);

	/* this is freed / recreated soon */
	pager->pixmap = XCreatePixmap(display,
			pager->window,
			8,
			8,
			DefaultDepth(display, DefaultScreen(display)));

	x_window_set_title(pager->window, "netwmpager");
	pager_update_aspect(pager);

	x_set_property(pager->window, XA_STRING,
			XInternAtom(display, "WM_CLASS", False),
			8, "netwmpager\0netwmpager", 22);

	pager->active_win_gc    = make_gc(pager->window, active_win_color);
	pager->inactive_win_gc  = make_gc(pager->window, inactive_win_color);
	pager->active_page_gc   = make_gc(pager->window, active_page_color);
	pager->inactive_page_gc = make_gc(pager->window, inactive_page_color);
	pager->win_border_gc    = make_gc(pager->window, win_border_color);
	pager->pager_border_gc  = make_gc(pager->window, pager_border_color);
	pager->grid_gc          = make_gc(pager->window, grid_color);
	pager->desk_grid_gc     = make_gc(pager->window, desk_grid_color);

	visual = DefaultVisual(display, DefaultScreen(display));
	cm = DefaultColormap(display, DefaultScreen(display));
	pager->xft_draw = XftDrawCreate(display, pager->pixmap, visual, cm);
	XftColorAllocName(display, visual, cm, active_win_font_color, &pager->active_win_font_color);
	XftColorAllocName(display, visual, cm, inactive_win_font_color, &pager->inactive_win_font_color);
	XftColorAllocName(display, visual, cm, popup_font_color, &pager->popup_font_color);

	x_parse_color(popup_color, &popup_bg);
	XSetWindowBackground(display, pager->popup_window, popup_bg);

	pager->window_font = NULL;
	pager->popup_font = NULL;
	pager_set_window_font(pager, "fixed");
	pager_set_popup_font(pager, "fixed");

	XSelectInput(display, DefaultRootWindow(display), PropertyChangeMask | SubstructureNotifyMask);
	return pager;
}

void pager_delete(struct pager *pager)
{
	XFreePixmap(display, pager->pixmap);

	XFreeGC(display, pager->active_win_gc);
	XFreeGC(display, pager->inactive_win_gc);
	XFreeGC(display, pager->active_page_gc);
	XFreeGC(display, pager->inactive_page_gc);
	XFreeGC(display, pager->win_border_gc);
	XFreeGC(display, pager->grid_gc);

	XftFontClose(display, pager->window_font);
	XftFontClose(display, pager->popup_font);
	XftColorFree(display, XftDrawVisual(pager->xft_draw), XftDrawColormap(pager->xft_draw), &pager->active_win_font_color);
	XftColorFree(display, XftDrawVisual(pager->xft_draw), XftDrawColormap(pager->xft_draw), &pager->inactive_win_font_color);
	XftColorFree(display, XftDrawVisual(pager->xft_draw), XftDrawColormap(pager->xft_draw), &pager->popup_font_color);
	XftDrawDestroy(pager->xft_draw);

	XDestroyWindow(display, pager->window);

	pager_free_windows(pager);
	free(pager);
}

void pager_expose_event(struct pager *pager, XEvent *event)
{
	if (event->xexpose.window == pager->window) {
		pager->needs_update = 1;
	} else {
		pager->needs_update_popup = 1;
	}
}

void pager_configure_notify(struct pager *pager)
{
	pager->needs_configure = 1;
	pager->needs_update_properties = 1;
}

void pager_property_notify(struct pager *pager)
{
	pager->needs_update_properties = 1;
}

void pager_handle_events(struct pager *pager)
{
	if (pager->needs_configure)
		pager_configure(pager);
	if (pager->needs_update_properties)
		pager_update_properties(pager);
	if (pager->needs_update)
		pager_update(pager);
	if (pager->needs_update_popup)
		pager_update_popup(pager);
}

void pager_set_opacity(struct pager *pager, double opacity)
{
	pager->opacity = opacity;
	x_window_set_opacity(pager->window, pager->opacity);
}

void pager_set_layer(struct pager *pager, enum pager_layer layer)
{
	pager->layer = layer;
}

static int set_font(const char *name, XftFont **font)
{
	XftFont *f;

	f = XftFontOpenName(display, DefaultScreen(display), name);
	if (f == NULL)
		return -1;
	if (*font)
		XftFontClose(display, *font);
	*font = f;
	return 0;
}

int pager_set_window_font(struct pager *pager, const char *name)
{
	if (set_font(name, &pager->window_font))
		return -1;
	return 0;
}

int pager_set_popup_font(struct pager *pager, const char *name)
{
	if (set_font(name, &pager->popup_font))
		return -1;
	return 0;
}

void pager_set_show_sticky(struct pager *pager, int on)
{
	pager->show_sticky = on != 0;
}

void pager_set_show_window_titles(struct pager *pager, int on)
{
	pager->show_window_titles = on != 0;
}

void pager_set_show_window_icons(struct pager *pager, int on)
{
	pager->show_window_icons = on != 0;
}

void pager_set_show_popups(struct pager *pager, int on)
{
	pager->show_popups = on != 0;
}

void pager_set_allow_cover(struct pager *pager, int on)
{
	pager->allow_cover = on != 0;
}

void pager_raise(struct pager *pager)
{
	x_window_set_below(pager->window, _NET_WM_STATE_REMOVE);
	x_window_set_above(pager->window, _NET_WM_STATE_ADD);
  	if (auto_hide) {
	  	XMapWindow(display, pager->window);
		XLowerWindow(display, pager->window);
	}
	XFlush(display);
}

void pager_lower(struct pager *pager)
{
	x_window_set_above(pager->window, _NET_WM_STATE_REMOVE);
	x_window_set_below(pager->window, _NET_WM_STATE_ADD);
	XFlush(display);
}

void pager_unmap(struct pager *pager)
{
	XUnmapWindow(display, pager->window);
}

void pager_show(struct pager *pager)
{
	pager_configure(pager);
	pager_update(pager);

	x_window_set_type(pager->window, WINDOW_TYPE_DOCK);
	XMapWindow(display, pager->window);

	/* pager->window must be mapped and WM must be running */

	x_window_set_skip_pager(pager->window, _NET_WM_STATE_ADD);
	x_window_set_skip_taskbar(pager->window, _NET_WM_STATE_ADD);
	if (opt_tray)
		x_request_systray(pager->window);

	switch (pager->layer) {
	case LAYER_NORMAL:
		break;
	case LAYER_COMMUTE:
	case LAYER_BELOW:
		pager_lower(pager);
		break;
	case LAYER_ABOVE:
		pager_raise(pager);
		break;
	}
	x_window_set_desktop(pager->window, -1, pager->size);
}

void pager_button_press(struct pager *pager, int x, int y, int button)
{
	int rx, ry, active;

	if (pager->popup_visible)
		popup_hide(pager);
	if (button < 1 || button > 5)
		return;

	if (pager->mouse.button != -1)
		return;

	pager_coords_to_root(pager, x, y, &rx, &ry, &active);
        d_print("button press page=%d (%d %d) (%d %d)\n", active, x, y, rx, ry);

	pager->mouse.button = button;
	pager->mouse.dragging = 0;
	pager->mouse.click_x = x;
	pager->mouse.click_y = y;

	pager->mouse.window_idx = get_window_idx(pager, rx, ry, active);
	if (pager->mouse.window_idx != -1) {
		pager->mouse.window_x = rx - pager->windows[pager->mouse.window_idx].x;
		pager->mouse.window_y = ry - pager->windows[pager->mouse.window_idx].y;
	}
}

void pager_button_release(struct pager *pager, int x, int y, int button)
{
#if DEBUG > 0
	int desk;
#endif
	int rx, ry, col, row, active, bool;

	if (pager->mouse.button != button)
		return;

	pager_coords_to_root(pager, x, y, &rx, &ry, &active);
	col = active % pager->size[COLS];
	row = active / pager->size[COLS];
	col = col / pager->size[PAGE_COLS];
	row = row / pager->size[PAGE_ROWS];
#if DEBUG > 0
	desk = row * pager->size[DESK_COLS] + col;
#endif
        bool = (active != pager->active_page) && switch_delay;

        d_print("button release page=%d desk=%d pager_xy (%d %d) root_xy (%d %d)\n", 
		active, desk, x, y, rx, ry);

	if (button == action_button[1]) {
		if (pager->active_page != active) {
			pager->active_page = active;
			x_set_current_desktop(pager->active_page, pager->size);
		}
		if (pager->mouse.window_idx != -1 && !pager->mouse.dragging) {
			if (bool) {
				usleep(500*switch_delay);
				XSync(display, True);
			}
			x_set_current_desktop(pager->active_page, pager->size);
                        x_set_active_window(pager->windows[pager->mouse.window_idx].window, SOURCE_INDICATION_PAGER);
			XSetInputFocus(display, pager->windows[pager->mouse.window_idx].window, RevertToParent, CurrentTime);
			if (bool) {
				XSync(display, True);
				usleep(500*switch_delay);
				XSync(display, True);
			}
		}
		pager->needs_update = 1;
	} else if (button == action_button[2] || button >= 4) {
	  	if (opt_transient && button == action_button[2]) {
			exit(0);
		}
	  	if (opt_tray && button == action_button[2]) {
			if (system("netwmpager -transient &"));
			/* hack to silence warning: ignoring return value */
			goto terminate;
		} else
	  	if (button == 4)
			pager->zoom *= zoom_factor;
		else
		if (button == 5) {
			pager->zoom /= zoom_factor;
			if (pager->zoom < 0.5)
				pager->zoom = 0.5;
		} else
	  	if (pager->zoom > 1.0)
			pager->zoom = 1.0;
		else
			pager->zoom = zoom;
		d_print("zoom: %g\n", pager->zoom);
		pager_calc_w_h(pager);
		XResizeWindow(display, pager->window, pager->w, pager->h);
	terminate:
		(void) set_pager_grid(pager, cols_init, rows_init, desk_cols_init, desk_rows_init);
		pager->needs_configure = 1;
	} else if (button == action_button[3]) {
		if (pager->mouse.window_idx != -1)
			pager->needs_update = 1;
	} 
	pager->mouse.window_idx = -1;
	pager->mouse.button = -1;
	pager->mouse.dragging = 0;
}

void pager_motion(struct pager *pager, int x, int y)
{
	int wx, wy, rx, ry, active_col, active_row, page;
        static int init = 1;
	struct client_window *window;

	active_col = pager->active_page % pager->size[COLS];
	active_row = pager->active_page / pager->size[COLS];
	active_col = active_col % pager->size[PAGE_COLS];
	active_row = active_row % pager->size[PAGE_COLS];

	if (pager->mouse.button == -1) {
		/* show / hide popup */
		int idx;
		int cx, cy;
		Window child_ret;

		if (!XTranslateCoordinates(display, pager->window,
					DefaultRootWindow(display),
					x, y, &cx, &cy, &child_ret)) {
			fprintf(stderr, "XTranslateCoordinates failed\n");
			return;
		}
		pager_coords_to_root(pager, x, y, &rx, &ry, &page);
		idx = get_window_idx(pager, rx, ry, page);

		if (pager->popup_visible) {
			if (idx == -1) {
				popup_hide(pager);
			} else if (idx != pager->popup_idx) {
				popup_hide(pager);
				pager->popup_idx = idx;
				popup_show(pager, cx, cy);
			}
		} else if (idx != -1) {
			pager->popup_idx = idx;
			popup_show(pager, cx, cy);
		}
	} else if (pager->mouse.window_idx != -1) {
		if (!pager->mouse.dragging &&
				abs(pager->mouse.click_x - x) < DRAG_THRESHOLD &&
				abs(pager->mouse.click_y - y) < DRAG_THRESHOLD)
			return;

		/* move window */
		pager->mouse.dragging = 1;

		pager_coords_to_root(pager, x, y, &rx, &ry, &page);

		window = &pager->windows[pager->mouse.window_idx];
		if (pager->mouse.button == action_button[1]) {
			/* move to other page */
			/* can't set geometry of a shaded window */
			if (window->states & WINDOW_STATE_SHADED)
				x_window_set_shaded(window->window, _NET_WM_STATE_REMOVE);
			repeat:
			window->x += win_drift_x;
			window->y += win_drift_y;
			wx = window->x / pager->root_w;
			rx = rx / pager->root_w - active_col;
			wy = window->y / pager->root_h;
			ry = ry / pager->root_h - active_row;
                        while (window->x < (pager->size[PAGE_COLS]-active_col-1) * pager->root_w - window->w && rx > wx) {
				window->x += pager->root_w;
				++wx;
			}
                        while (window->x >= (1 - active_col) * pager->root_w && rx < wx) {
				window->x -= pager->root_w;
				--wx;
			}
                        while (window->y < (pager->size[PAGE_ROWS]-active_row-1) * pager->root_h - window->h && ry > wy) {
				window->y += pager->root_h;
				++wy;
			}
                        while (window->y >= (1 - active_row) * pager->root_h && ry < wy) {
				window->y -= pager->root_h;
				--wy;
			}
			if (page != window->page && window->page != -1) {
				x_window_set_desktop(window->window, page, pager->size);
				window->page = page;
			}
			x_window_set_geometry(window->window, XValue | YValue, window->x, window->y, 0, 0);
			wx = window->x;
			wy = window->y;
			x_window_get_desktop(window->window, active_col, active_row, &window->page, &window->x, &window->y, &rx, &ry, pager->size);
			/* accommodate strange behaviour of some WMs
			   like compiz, fluxbox ... */
			win_drift_x = wx - window->x;
			win_drift_y = wy - window->y; 
			if (init && (win_drift_x!=0 || win_drift_y !=0)) {
				init = 0;
				window->x += win_drift_x;
				window->y += win_drift_y;
				goto repeat;
			}

			d_print("moving window '%s' to (%d,%d) page: %d\n",
				window->name, window->x, window->y,
				window->page);
			pager_update(pager);
		} else if (pager->mouse.button == action_button[3]) {
			/* exact placement */
			/* can't set geometry of a shaded window */
			if (window->states & WINDOW_STATE_SHADED)
				x_window_set_shaded(window->window, _NET_WM_STATE_REMOVE);
			wx = rx - pager->mouse.window_x - active_col * pager->root_w;
			wy = ry - pager->mouse.window_y - active_row * pager->root_h;
			x_window_set_geometry(window->window, XValue | YValue, wx, wy, 0, 0);
			window->x = wx;
			window->y = wy;
			if (page != window->page && window->page != -1) {
				x_window_set_desktop(window->window, page, pager->size);
				window->page = page;
			}
			x_window_get_desktop(window->window, active_col, active_row, &window->page, &window->x, &window->y, &wx, &wy, pager->size);

			d_print("moving window '%s' to (%d,%d) page: %d\n",
				window->name, window->x, window->y,
				window->page);
			pager_update(pager);
		}
	}
}

void pager_enter(struct pager *pager, int x, int y)
{
	if (pager->layer == LAYER_COMMUTE) {
		x_window_set_above(pager->window, _NET_WM_STATE_ADD);
		x_window_set_below(pager->window, _NET_WM_STATE_REMOVE);
	}
	pager_motion(pager, x, y);
}

void pager_leave(struct pager *pager)
{
	/* popup_hide(pager); */
	if (pager->layer == LAYER_COMMUTE) {
		x_window_set_above(pager->window, _NET_WM_STATE_REMOVE);
		x_window_set_below(pager->window, _NET_WM_STATE_ADD);
	}
}
