/*
 * Copyright (C) 2000, Matias Atria
 *
 * 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
 */

/* This file contains routines I always use to make X programming easier */

#include "common.h"

#ifndef X_DISPLAY_MISSING

/*#define XSYNCH*/
#define DEFAULT_FONT	"-*-helvetica-medium-r-*-*-*-*-*-*-*-*-*-*"

#include "x11.h"
#include "bitmap.h"
#include "private.h"

#include <X11/Xatom.h>

Display	*dpy      = NULL;
int	scrno     = 0;
GC	normal_gc = NULL;
GC	image_gc  = NULL;
GC	inv_gc    = NULL;
int	depth     = 0;
Visual	*visual   = NULL;
Colormap	colormap  = None;
XFontStruct *default_font = NULL;
int	bell_percent = 100;

static Window	group_leader = None;
static Atom	protocols = None;
static Atom	delete    = None;
static XImage	*image   = NULL;

Uint	root_w    = 0;
Uint	root_h    = 0;
Ulong	black;
Ulong	white;

int	errorhandler(Display *d, XErrorEvent *e)
{
	d=d, e=e;
	abort();
}

#define NORMAL_EVENTS	\
	(ExposureMask|KeyPressMask|StructureNotifyMask|\
	ButtonPressMask|ButtonReleaseMask)

/* functions to query information about a window */

void get_screen_size(int *w, int *h)
{
	*w = root_w;
	*h = root_h;
}

void	beep_sound(void)
{
	XBell(dpy, bell_percent);
}

int get_win_size(Window win, Uint *w, Uint *h)
{
	Window	junk;
	int	junk1;
	
	XGetGeometry(dpy, win, 
		&junk, &junk1, &junk1, 
		w, h, 
		(Uint*)&junk1, (Uint*)&junk1);
	return 0;
}

void	free_propdata(PropData *prop)
{
	if(prop->name) {
		/* this pointer was returned by XGetAtomName */
		XFree(prop->name);
		prop->name = NULL;
	}
	prop->format = 8;
	prop->type = XA_STRING;
	prop->nitems = 0;
	if(prop->data) {
		xfree(prop->data);
		prop->data = NULL;
	}
}

/* 
 * This is a general purpose function to get the data associated to a
 * window's property. It serves as an example of how to use
 * XGetWindowProperty.
 *
 * Returns -1 if there was an error, 0 on success. If all is well, it fills
 * the `ret' PropData structure with all the information reported about the 
 * property. It must be freed with `free_propdata()'.
 */
int	get_window_property(Display *dpy, Window w, Atom property, PropData *ret)
{
	Uchar	*data;
	int	status;
	Atom	prop_type;
	int	prop_format;
	Ulong	prop_nitems;
	Ulong	bytes_left;
	Uchar	*copy, *ptr;
	long	size;
	long	length;
	long	total;

	/* request 1024 32bit words (we'll probably get much less than
	 * that), and get format and type */	 
	status = XGetWindowProperty(dpy, w, property,
			0L, 1024, False,
			AnyPropertyType, &prop_type,
			&prop_format, &prop_nitems,
			&bytes_left, &data);

	if(status != Success || prop_type == None)
		return -1;

	/* this is the number of bytes we just read */
	length = prop_nitems * (prop_format >> 3);

	/* this is the total length of the data for this property */
	total = length + bytes_left;
	
	/* allocate memory to hold all the data for this property.
	 *
	 * Note that we allocate one extra byte and set that to 0 so strings
	 * can be used directly. The docs for XGetWindowProperty say that
	 * this is automatically done, but they don't mention if this extra
	 * byte is included in the # of items returned. I assume it is not.
	 * The worst that can happen is that we're adding a second NUL at
	 * the end, but I better be safe than sorry.
	 */
	ptr = copy = (Uchar *)xmalloc(total + 1);
	size = total;
	if(ptr == NULL) {
		XFree(data);
		return -1;
	}

	/* copy what we have so far */
	memcpy(copy, data, length);
	ptr = copy + length;

	/* we dont need this anymore */
	XFree(data);

	while(bytes_left) {
		int	nbytes;

		/* we could request all the data that is left, but
		 * this will work too */
		XGetWindowProperty(dpy, w, property,
			length / 4, 1024L, False,
			AnyPropertyType, &prop_type,
			&prop_format, &prop_nitems,
			&bytes_left, &data);

		/* # of bytes we read */
		nbytes = prop_nitems * (prop_format >> 3);

		/* # of bytes so far */
		length += nbytes;

		/* check that we don't have more data than we should */
		if(length > total) {
			printf("I expected %ld bytes, but already got %ld, "
				"and there are %ld more to come.\n",
				total, length, bytes_left);
			break;
		}
		/* update our copy */
		memcpy(ptr, data, nbytes);
		XFree(data);
		ptr += nbytes;
	}
	copy[length] = 0;
	
	/* fill the PropData structure */
	ret->name = XGetAtomName(dpy, property);
	ret->format = prop_format;
	ret->type = prop_type;
	ret->nitems = length / (prop_format >> 3);
	ret->data = copy;

	return 0;
}

/* open a connection to a remote display */
int	init_x11(void)
{
	XGCValues xg;
	XKeyboardState kbd;
	
	dpy = XOpenDisplay(NULL);
	if(dpy == NULL) {
		error(_("connection to X server failed\n"));
		return -1;
	}
	scrno = DefaultScreen(dpy);
	get_win_size(RootWindow(dpy, scrno), &root_w, &root_h);
	depth = DefaultDepth(dpy, scrno);
	visual = DefaultVisual(dpy, scrno);
	colormap = DefaultColormap(dpy, scrno);
	black = BlackPixel(dpy, scrno);
	white = WhitePixel(dpy, scrno);

	/* load a default font */
	default_font = XLoadQueryFont(dpy, DEFAULT_FONT);
	if(default_font == NULL)
		default_font = XLoadQueryFont(dpy, "fixed");
	if(default_font == NULL) {
		error(_("%s: could not find display font `%s'\n"),
			program_name, DEFAULT_FONT);
		XCloseDisplay(dpy);
		return -1;
	}
	
	/* create GCs */
	xg.foreground = black;
	xg.background = white;
	xg.font       = default_font->fid;
	xg.graphics_exposures = False;
	normal_gc = XCreateGC(dpy, RootWindow(dpy, scrno),
		GCForeground|GCBackground|GCGraphicsExposures|GCFont, &xg);
	image_gc = XCreateGC(dpy, RootWindow(dpy, scrno),
		GCForeground|GCBackground|GCGraphicsExposures|GCFont, &xg);
#ifdef CHEAP_X_TRICK
	XSetFunction(dpy, image_gc, GXand);
#endif
	xg.foreground = white;
	xg.background = black;
	inv_gc = XCreateGC(dpy, RootWindow(dpy, scrno),
		GCForeground|GCBackground|GCGraphicsExposures|GCFont, &xg);

	image = XCreateImage(dpy, visual, 1, XYBitmap, 0,
		(char *)0, 0, 0, BITMAP_BITS, 0);
#ifdef WORD_BIG_ENDIAN
	image->byte_order = image->bitmap_bit_order = MSBFirst;
#else
	image->byte_order = image->bitmap_bit_order = LSBFirst;
#endif

#if !defined(NODEBUG) && defined(XSYNCH)
	XSynchronize(dpy, True);
	XSetErrorHandler(errorhandler);
#endif

	/* get keyboard state */
	XGetKeyboardControl(dpy, &kbd);
	bell_percent = kbd.bell_percent;
	if(bell_percent < -100)
		bell_percent = -100;
	if(bell_percent > 100)
		bell_percent = 100;
		
	return 0;
}

int	window_closed(XClientMessageEvent *ev, Window w)
{
	if(ev->window != w)
		return 0;
	if(ev->message_type == protocols && ev->data.l[0] == delete)
	   	return 1;
	return 0;
}

int	handle_top_events(TopWindow *tw, XEvent *ev)
{
	if(ev->xany.window != tw->win)
		return 0;
	switch(ev->type) {
		case ClientMessage:
			if(window_closed(&ev->xclient, tw->win)) {
				tw->active = 0;
				return 1;
			}
			break;
		case ConfigureNotify:
			tw->w = ev->xconfigure.width;
			tw->h = ev->xconfigure.height;
			break;
		case KeyPress: {
			if(XLookupKeysym((XKeyEvent *)ev, 0) == XK_Escape) {
				tw->active = 0;
				return 1;
			}
			break;
		}
		default:
			break;
	}
	return 0;
}

void	close_top_window(TopWindow *tw)
{
	XDestroyWindow(dpy, tw->win);
	if(tw->win == group_leader)
		group_leader = None;
	tw->win = None;
	tw->active = 0;
}

void	expose_window(Window w, Bool putback)
{
	XEvent	ev;
	
	XMapWindow(dpy, w);
	while(!XCheckTypedWindowEvent(dpy, w, Expose, &ev))
		;
	if(putback)
		XPutBackEvent(dpy, &ev);
}

void	resize_top_window(TopWindow *tw, Uint w, Uint h)
{
	XSizeHints size_hints;
	
	size_hints.width = w;
	size_hints.height = h;
	size_hints.flags = USSize;
	XSetWMNormalHints(dpy, tw->win, &size_hints);
	XResizeWindow(dpy, tw->win, w, h);
	XFlush(dpy);
	get_win_size(tw->win, &tw->w, &tw->h);
}

int	create_top_window(TopWindow *tw, const char *name, int w, int h)
{
	XSetWindowAttributes xwa;
	
	if(dpy == NULL && init_x11() == -1)
		return -1;

	if(w > root_w)
		w = root_w;
	if(h > root_h)
		h = root_h;
	
	xwa.background_pixel = white;
	xwa.backing_pixel = white;
	xwa.bit_gravity = NorthWestGravity;
	xwa.backing_store = WhenMapped;
	xwa.colormap = colormap;
	
	tw->win = XCreateWindow(dpy, 
		RootWindow(dpy, scrno), 0, 0,
		w, h, 0, depth,
		InputOutput, CopyFromParent, 
		CWBackPixel|CWBackingPixel|
		CWBitGravity|CWBackingStore|
		CWColormap, &xwa);

	/* select `Expose' events */
	XSelectInput(dpy, tw->win, NORMAL_EVENTS);
	XStoreName(dpy, tw->win, name);
	
	protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
	delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	if(delete != None)
		XSetWMProtocols(dpy, tw->win, &delete, 1);
		
	/* set this as group leader, if it's the first */
	if(group_leader == None)
		group_leader = tw->win;
	else {
		XWMHints hints;
		
		hints.window_group = (XID)group_leader;
		hints.flags = WindowGroupHint;
		XSetWMHints(dpy, tw->win, &hints);
	}

	tw->active = 1;
	return 0;
}

void	set_max_winsize(Window win, int w, int h)
{
	XSizeHints size_hints;

	size_hints.flags = PMaxSize;
	size_hints.max_width = w;
	size_hints.max_height = h;
	XSetWMNormalHints(dpy, win, &size_hints);
}

void	add_window_events(Window w, Ulong mask)
{
	XWindowAttributes attr;
	
	XGetWindowAttributes(dpy, w, &attr);
	attr.your_event_mask |= mask;
	XSelectInput(dpy, w, attr.your_event_mask);
	XFlush(dpy);
}

void	clear_window_events(Window w, Ulong mask)
{
	XWindowAttributes attr;
	
	XGetWindowAttributes(dpy, w, &attr);
	attr.your_event_mask &= ~mask;
	XSelectInput(dpy, w, attr.your_event_mask);
	XFlush(dpy);
}

void	set_window_events(Window w, Ulong mask)
{	
	XSelectInput(dpy, w, mask);
}

Window	create_window(Window parent, Ulong c, int w, int h, int b)
{
	XSetWindowAttributes xwa;
	Window	win;
	
	if(parent == None)
		parent = RootWindow(dpy, scrno);
		
	xwa.background_pixel = c;
	xwa.bit_gravity = NorthWestGravity;
#ifndef NODEBUG
	xwa.backing_store = NotUseful;
#else
	xwa.backing_store = WhenMapped;
#endif
	xwa.colormap = colormap;
	xwa.border_pixel = black;
	win = XCreateWindow(dpy, parent, 0, 0,
		w, h, b, depth,
		InputOutput, CopyFromParent, 
		CWBackPixel|CWBitGravity|CWBackingStore|CWColormap, 
		&xwa);

	/* select `Expose' events */
	set_window_events(win, NORMAL_EVENTS);
	
	return win;
}

Window	create_subwindow(Window parent, Ulong c, int x, int y, int w, int h, int b)
{
	XSetWindowAttributes xwa;
	Window	win;
	
	if(parent == None)
		parent = RootWindow(dpy, scrno);
		
	xwa.background_pixel = c;
	xwa.bit_gravity = NorthWestGravity;
#ifndef NODEBUG
	xwa.backing_store = NotUseful;
#else
	xwa.backing_store = WhenMapped;
#endif
	xwa.colormap = colormap;
	xwa.border_pixel = black;
	win = XCreateWindow(dpy, parent, 
		x, y, w, h, b, depth,
		InputOutput, CopyFromParent, 
		CWBackPixel|CWBitGravity|CWBackingStore|
		CWColormap, &xwa);

	/* select `Expose' events */
	set_window_events(win, NORMAL_EVENTS);
	
	return win;
}

Pixmap	create_pixmap(Window parent, int w, int h)
{
	Pixmap	p;
	
	p = XCreatePixmap(dpy, parent, w, h, depth);
	XFillRectangle(dpy, p, inv_gc, 0, 0, w, h);
	return p;
}

int	create_draw_area(DrawArea *area, Window in, int w, int h, int b)
{
	XGCValues xgc;
	
	area->win = create_window(in, white, w, h, b);
	if(area->win == None)
		return -1;
	xgc.foreground = black;
	xgc.background = white;
	xgc.graphics_exposures = False;
	area->gc = XCreateGC(dpy, area->win,
		GCForeground|GCBackground|GCGraphicsExposures, &xgc);
	area->p = create_pixmap(area->win, w, h);
	area->w = w;
	area->h = h;
	area->hidden = 1;
	area->fg = black;
	area->bg = white;
	return 0;
}

void	set_area_foreground(DrawArea *area, Ulong fg)
{
	if(fg != area->fg) {
		XSetForeground(dpy, area->gc, fg);
		area->fg = fg;
	}
}

void	set_area_background(DrawArea *area, Ulong bg)
{
	if(bg != area->bg) {
		XSetBackground(dpy, area->gc, bg);
		area->bg = bg;
	}
}

void	destroy_area(DrawArea *area)
{
	XFreePixmap(dpy, area->p);
	XFreeGC(dpy, area->gc);
	XDestroyWindow(dpy, area->win);
	area->win = None;
	area->p = None;
	area->w = 0;
	area->h = 0;
	area->hidden = 1;
	area->gc = NULL;
}

void	refresh_area(DrawArea *area, int x, int y, int w, int h)
{
	if(!area->hidden)
		XCopyArea(dpy, area->p, area->win, area->gc, x, y, w, h, x, y);
}

void	clear_area(DrawArea *area)
{
	XSetForeground(dpy, area->gc, area->bg);
	XFillRectangle(dpy, area->p, area->gc, 0, 0, area->w, area->h);
	XClearArea(dpy, area->win, 0, 0, area->w, area->h, True);
	XSetForeground(dpy, area->gc, area->fg);
}

void	show_area(DrawArea *area)
{
	XEvent	ev;
	
	if(!area->hidden)
		return;
	add_window_events(area->win, ExposureMask);
	XFlush(dpy);
	XMapWindow(dpy, area->win);
	while(!XCheckTypedWindowEvent(dpy, area->win, Expose, &ev))
		;
	XPutBackEvent(dpy, &ev);
	area->hidden = 0;
}

void	hide_area(DrawArea *area)
{
	XUnmapWindow(dpy, area->win);
	area->hidden = 1;
}

int	resize_area(DrawArea *area, Uint w, Uint h)
{
	Pixmap	p;
	
	p = create_pixmap(area->win, w, h);
	if(p == None)
		return -1;
	XFreePixmap(dpy, area->p);
	area->p = p;
	XResizeWindow(dpy, area->win, w, h);
	area->w = w;
	area->h = h;
	return 0;
}

void	set_clip_area(DrawArea *area, int x, int y, int w, int h)
{
	XRectangle rect;
	
	rect.x = x;
	rect.y = y;
	rect.width = w;
	rect.height = h;
	XSetClipRectangles(dpy, area->gc, 0, 0, &rect, 1, Unsorted);
}

void	draw_line(Drawable d, int x0, int y0, int x1, int y1)
{
	XDrawLine(dpy, d, normal_gc, x0, y0, x1, y1);
}

void	draw_box(Drawable d, int x, int y, int w, int h, int filled)
{
	if(filled)
		XFillRectangle(dpy, d, normal_gc, x, y, w, h);
	else
		XDrawRectangle(dpy, d, normal_gc, x, y, w, h);
}

void	attach_window(Window win, Window to)
{
	XReparentWindow(dpy, win, to, 0, 0);
}

void	detach_window(TopWindow *tw, Window win, int x, int y)
{
	/* set window transient for the top window */
	if(tw && win == tw->win)
		return;
	if(tw != NULL)
		XSetTransientForHint(dpy, win, tw->win);
	/* make window a child of the root */
	XReparentWindow(dpy, win, RootWindow(dpy, scrno), x, y);
	/* and make it exit cleanly */
	if(delete != None)
		XSetWMProtocols(dpy, win, &delete, 1);
}

void	close_x11(void)
{
	XFreeGC(dpy, normal_gc);
	XFreeGC(dpy, inv_gc);
	XFreeGC(dpy, image_gc);
	XFreeFont(dpy, default_font);
	XCloseDisplay(dpy);
	dpy = NULL;
}

void	draw_bitmap(Drawable d, BITMAP *bm, int x, int y)
{
	if(bm->width <= 0 || bm->height <= 0)
		return;
	/* create X image */
	if(bm->data != NULL) {
		image->width = bm->width;
		image->height = bm->height;
		image->data = (char *)bm->data;
		image->bytes_per_line = bm->stride;
		XPutImage(dpy, d, image_gc, image, 
			0, 0, x, y,
			bm->width, bm->height);
	}
}

void	put_image(Drawable d, void *image, int x, int y, int w, int h)
{
	XPutImage(dpy, d, image_gc, (XImage *)image,
		0, 0, x, y, w, h);
}

GC	create_gc(int func, Window p, Ulong fg, Ulong bg)
{
	XGCValues xgc;
	
	xgc.foreground = fg;
	xgc.background = bg;
	xgc.function   = func;
	xgc.font       = default_font->fid;
	xgc.graphics_exposures = False;
	return XCreateGC(dpy, p,
		GCFunction|GCForeground|GCBackground|
		GCGraphicsExposures|GCFont, &xgc);
}

void	draw_text(DrawArea *area, GC gc, int x, int y, const char *text, int justify)
{
	int	n = strlen(text);
	int	w, y0;
	
	w = XTextWidth(default_font, text, n);
	switch(justify & MX11_HJUSTIFY) {
	case MX11_LEFT:
		x = 2; break;
	case MX11_RIGHT:
		x = area->w - w - 2;
		if(x < 2) x = 2;
		break;
	case MX11_HCENTER:
		x = (area->w - w) / 2;
		break;
	}
	switch(justify & MX11_VJUSTIFY) {
	case MX11_ABOVE:
		y -= default_font->max_bounds.descent;
		if(y < 2) y = 2;
		break;
	case MX11_BELOW:
		y += default_font->max_bounds.ascent;
		if(y + default_font->max_bounds.descent + 2 > area->h)
			y = area->h - default_font->max_bounds.descent - 2;
		if(y < 2) y = 2;
		break;
	case MX11_VCENTER:
		y -= FONT_HEIGHT(default_font) / 2 + 
			default_font->max_bounds.ascent;
		if(y < 2) y = 2;
		break;
	}
	if(gc == NULL)
		gc = area->gc;
	y0 = y - default_font->max_bounds.ascent;
	XFillRectangle(dpy, area->p, inv_gc, 
		x, y0, w, FONT_HEIGHT(default_font));
	XDrawString(dpy, area->p, gc, x, y, text, n);
}

Ulong	get_color_byname(const char *name)
{
	XColor	xc;

	if(dpy == NULL)
		return 0UL;	
	if(!XParseColor(dpy, colormap, name, &xc)) {
		warning(_("invalid color name `%s', using black\n"));
		return black;
	}
	if(!XAllocColor(dpy, colormap, &xc)) {
		warning(_("failed to allocate color `%s', using black\n"));
		return black;
	}
	return xc.pixel;
}

#endif /* X_DISPLAY_MISSING */
