/* 
 *     XaoS, a fast portable realtime fractal zoomer 
 *                  Copyright (C) 1996,1997 by
 *
 *      Jan Hubicka          (hubicka@paru.cas.cz)
 *      Thomas Marsh         (tmarsh@austin.ibm.com)
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include "aconfig.h"
#ifdef X11_DRIVER
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include "xlib.h"
#ifdef MITSHM
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif
#include <fconfig.h>
#include <ui.h>
xlibparam xparams = { 0, 0, 0, 0, NULL, -1 };

static int allocated;
Cursor normal, xwait, replay;

struct ui_driver x11_driver;
static xdisplay *d;
static char *size;
static int Xsync;
static int busy;
static int sharedcolormap;
#if 0
static char *selection;
#endif
#ifdef MITSHM
static int Completion;
#endif
#ifdef AMIGA
#define XFlush(x) while(0)
#endif

static void x11_setpaletterange(ui_palette p, int s, int e)
{
    xsetpaletterange(d, p, s, e);
}

static int x11_set_color(int r, int g, int b, int init)
{
    if (init)
	xfree_colors(d);
    return (xalloc_color(d, r * 256, g * 256, b * 256, init));
}

static void x11_print(int x, int y, CONST char *text)
{
    xmoveto(d, x, y + x11_driver.textheight - 2);
    xouttext(d, text);

}

static void x11_flush(void)
{
    XFlush(d->display);
}

static int display;
static int flipped = 0;
static void x11_display(void)
{
    XFlush(d->display);
#ifdef MITSHM
    if (d->SharedMemFlag) {
	if (busy) {
	    display = 1;
	    return;
	}
	busy++;
	display = 0;
    }
#endif
    if (Xsync)
	XSync(d->display, 0);
    if (flipped)
	xflip_buffers(d), flipped = 0;
    draw_screen(d);
#ifdef MITSHM
    if (d->SharedMemFlag) {
	XSync(d->display, 0);
    }
#endif
}

static void x11_flip_buffers(void)
{
    flipped ^= 1;
}

static void x11_free_buffers(char *b1, char *b2)
{
    if (allocated) {
	XSync(d->display, 0);
	allocated = 0;
	free_image(d);
    }
}

static int x11_alloc_buffers(char **b1, char **b2)
{
    if (!allocated) {
	XSync(d->display, 0);
	allocated = 1;
	if (!alloc_image(d)) {
	    return (0);
	}
	xflip_buffers(d);
    }
    *b1 = d->vbuff;
    *b2 = d->back;
    flipped = 0;
#if 0
    if (d->SharedMemFlag) {
	x11_driver.flags |= UI_KEEP_BUFFER;
    }
#endif
    return (d->linewidth);
}

static void x11_getsize(int *w, int *h)
{
    XSync(d->display, 0);
    xupdate_size(d);
    *w = d->width;
    *h = d->height;
}

static void x11_processevents(int wait, int *mx, int *my, int *mb, int *k)
{
    static int mousex = 100, mousey = 0;
    static int iflag = 0;
    static unsigned int mousebuttons = 0;
    static int resized;
    XEvent ev;


    if (XPending(d->display) || busy >= 2 || wait) {
	do {
	    XNextEvent(d->display, &ev);
	    switch (ev.type) {
	    case ClientMessage:
		if ((int) ev.xclient.format == 32
		    && ev.xclient.data.l[0] == wmDeleteWindow)
		    ui_quit();
		break;
	    case ButtonRelease:
		mousex = ev.xbutton.x;
		mousey = ev.xbutton.y;
		switch (ev.xbutton.button) {
		case 1:
		    mousebuttons &= ~BUTTON1;
		    break;
		case 2:
		    mousebuttons &= ~BUTTON2;
		    break;
		case 3:
		    mousebuttons &= ~BUTTON3;
		    break;
		}
		break;
	    case ButtonPress:
		mousex = ev.xbutton.x;
		mousey = ev.xbutton.y;
		if (!
		    (mousex < 0 || mousey < 0 || mousex > (int) d->width
		     || mousey > (int) d->height)) {
		    switch (ev.xbutton.button) {
		    case 1:
			mousebuttons |= BUTTON1;
			break;
		    case 2:
			mousebuttons |= BUTTON2;
			break;
		    case 3:
			mousebuttons |= BUTTON3;
			break;
		    }
		}
		break;
	    case MotionNotify:
		mousex = ev.xmotion.x;
		mousey = ev.xmotion.y;
		mousebuttons =
		    ev.xmotion.state & (BUTTON1 | BUTTON2 | BUTTON3);
		break;
	    case Expose:
		if (resized)
		    break;
		x11_display();
		break;
	    case ResizeRequest:
		XResizeWindow(d->display, d->window,
			      ev.xresizerequest.width,
			      ev.xresizerequest.height);
		XResizeWindow(d->display, d->parent_window,
			      ev.xresizerequest.width,
			      ev.xresizerequest.height);
		XSync(d->display, 0);
		resized = 2;
		ui_resize();
		resized = 0;
		break;

	    case ConfigureNotify:
		{
		    int oldw = d->width, oldh = d->height;
		    XSync(d->display, 0);
		    xupdate_size(d);
		    if ((int) d->width != oldw || (int) d->height != oldh) {
			resized = 2;
			ui_resize();
			resized = 0;
		    }
		}
		break;
	    case KeyRelease:
		{
		    switch (XLookupKeysym(&ev.xkey, 0)) {
		    case XK_Left:
			iflag &= ~1;
			break;
		    case XK_Right:
			iflag &= ~2;
			break;
		    case XK_Up:
			iflag &= ~4;
			break;
		    case XK_Down:
			iflag &= ~8;
			break;
		    }
		}
		break;
	    case KeyPress:
		{
		    KeySym ksym;
		    switch (ksym = XLookupKeysym(&ev.xkey, 0)) {
		    case XK_Left:
			iflag |= 1;
			ui_key(UIKEY_LEFT);
			break;
		    case XK_Right:
			iflag |= 2;
			ui_key(UIKEY_RIGHT);
			break;
		    case XK_Up:
			iflag |= 4;
			ui_key(UIKEY_UP);
			break;
		    case XK_Down:
			iflag |= 8;
			ui_key(UIKEY_DOWN);
			break;
#ifdef XK_Page_Up
		    case XK_Page_Up:
			iflag |= 4;
			ui_key(UIKEY_PGUP);
			break;
		    case XK_Page_Down:
			iflag |= 8;
			ui_key(UIKEY_PGDOWN);
			break;
#endif
		    case XK_Escape:
			ui_key(UIKEY_ESC);
		    case XK_BackSpace:
			ui_key(UIKEY_BACKSPACE);
			break;	/* This statement was missing.
				   I'm not sure if this is needed
				   because new X drivers handle
				   UIKEY_BACKSPACE better or
				   double backspaces were problems
				   in earlier versions of XaoS, too.
				   -- Zoltan, 2004-10-30 */
		    default:
			{
			    CONST char *name;
			    char buff[256];
			    if (ksym == XK_F1)
				name = "h";
			    else {
				name = buff;
				buff[XLookupString
				     (&ev.xkey, buff, 256, &ksym, NULL)] =
				    0;
			    }
			    if (strlen(name) == 1) {
				if (ui_key(*name) == 2) {
				    return;
				}
			    }
			}
		    }
		}
		break;
	    default:
#ifdef MITSHM
		if (ev.xany.type == Completion) {
		    busy--;
		    if (display)
			x11_display();
		}
#endif
		break;
	    }
	}
	while (busy >= 2 ||	/*XEventsQueued (d->display, QueuedAlready) */
	       XPending(d->display));
    }
    *mx = mousex;
    *my = mousey;
    *mb = mousebuttons;
    *k = iflag;
}

/*static int defined; */
static void x11_cursor(int mode)
{
    /*if(defined)
       XUndefineCursor(d->display,d->window),defined=0; */
    switch (mode) {
    case NORMALMOUSE:
	XDefineCursor(d->display, d->window, normal);
	/*defined=1; */
	break;
    case WAITMOUSE:
	XDefineCursor(d->display, d->window, xwait);
	/*defined=1; */
	break;
    case REPLAYMOUSE:
	XDefineCursor(d->display, d->window, replay);
	/*defined=1; */
	break;
    }
    XFlush(d->display);
}

#if 0
static Atom atom;
void x11_copy()
{
    if (slection)
	free(selection), selection = NULL;
    selection = ui_getpos();
    atom = XInternAtom(d->display, "image/x-xaos.position", False);
    printf("%i\n", atom);
    XSetSelectionOwner(d->display, atom, d->window, CurrentTime);
}
#endif
static int x11_init(void)
{
    if (xparams.windowid != -1)
	xparams.rootwindow = xparams.fullscreen = 0;
    if (xparams.fullscreen || xparams.rootwindow)
	sharedcolormap = 1;	/*private colormap is broken in fullscreen
				   mode (at least at my X) */
    xparams.privatecolormap = !sharedcolormap;
    if (xparams.display == NULL) {	/*solaris stuff */
	xparams.display = getenv("DISPLAY");
    }
    if (size != NULL) {
	int x, y;
	sscanf(size, "%ix%i", &x, &y);
	if (x < 0)
	    x = XSIZE;
	if (y < 0)
	    y = YSIZE;
	d = xalloc_display("XaoS", x, y, &xparams);
    } else
	d = xalloc_display("XaoS", XSIZE, YSIZE, &xparams);
    if (d == NULL)
	return 0;
    /*normal=XCreateFontCursor(d->display,XC_arrow); */
    normal = XCreateFontCursor(d->display, XC_left_ptr);
    xwait = XCreateFontCursor(d->display, XC_watch);
    replay = XCreateFontCursor(d->display, XC_dot);
    if (d->truecolor || d->privatecolormap)
	x11_driver.flags &= ~RANDOM_PALETTE_SIZE;
    if (!alloc_image(d)) {
	xfree_display(d);
	return (0);
    }
    allocated = 1;
    switch (d->visual->class) {
    case StaticGray:
	if (d->depth == 1) {
	    if (BitmapBitOrder(d->display) == LSBFirst)
		if (WhitePixel(d->display, d->screen))
		    x11_driver.imagetype = UI_LBITMAP;
		else
		    x11_driver.imagetype = UI_LIBITMAP;
	    else if (WhitePixel(d->display, d->screen))
		x11_driver.imagetype = UI_MBITMAP;
	    else
		x11_driver.imagetype = UI_MIBITMAP;
	} else {
	    /*Warning! this is untested. I believe it works */
	    /*x11_driver.set_color = x11_set_color; */
	    x11_driver.palettestart = 0;
	    x11_driver.paletteend = 256;
	    x11_driver.maxentries = 256;
	    x11_driver.imagetype = UI_GRAYSCALE;
	}
	break;
    case StaticColor:
      smallcolor:
	{
	    int end = 256;
	    int start = 0;
	    int entries = d->visual->map_entries;
	    if (d->visual->class == TrueColor) {
		entries = (int) (d->image[0]->red_mask |
				 d->image[0]->green_mask |
				 d->image[0]->blue_mask);
	    }
	    x11_driver.imagetype = UI_FIXEDCOLOR;
	    if (end > entries)
		end = entries;
	    if (end < 64)
		start = 0;
	    x11_driver.set_range = x11_setpaletterange;
	    x11_driver.palettestart = start;
	    x11_driver.paletteend = end;
	    x11_driver.maxentries = end - start;
	}
	break;
    case PseudoColor:
    case GrayScale:
	if (d->privatecolormap) {
	    int end = 256;
	    int start = 16;
	    if (end > d->visual->map_entries)
		end = d->visual->map_entries;
	    if (end < 64)
		start = 0;
	    x11_driver.set_range = x11_setpaletterange;
	    x11_driver.palettestart = start;
	    x11_driver.paletteend = end;
	    x11_driver.maxentries = end - start;
	} else {
	    int end = 256;
	    if (end > d->visual->map_entries)
		end = d->visual->map_entries;
	    x11_driver.set_color = x11_set_color, x11_driver.flags |=
		RANDOM_PALETTE_SIZE;
	    x11_driver.palettestart = 0;
	    x11_driver.paletteend = end;
	    x11_driver.maxentries = end;
	}
	break;
    case TrueColor:
	x11_driver.rmask = d->image[0]->red_mask;
	x11_driver.gmask = d->image[0]->green_mask;
	x11_driver.bmask = d->image[0]->blue_mask;
	{
	    unsigned char c[4];
	    int order = MSBFirst;
	    *(unsigned short *) c = 0xff;
	    if (c[0] == (unsigned char) 0xff)
		order = LSBFirst;
	    if (order != d->image[0]->byte_order) {
		int shift = 32 - d->image[0]->bits_per_pixel;
#define SWAPE(c)  (((c&0xffU)<<24)|((c&0xff00U)<<8)|((c&0xff0000U)>>8)|((c&0xff000000U)>>24))
		x11_driver.rmask = SWAPE(x11_driver.rmask) >> shift;
		x11_driver.gmask = SWAPE(x11_driver.gmask) >> shift;
		x11_driver.bmask = SWAPE(x11_driver.bmask) >> shift;
	    }
	}
	switch (d->image[0]->bits_per_pixel) {
	case 8:
	    goto smallcolor;
	case 16:
	    x11_driver.imagetype = UI_TRUECOLOR16;
	    break;
	case 24:
	    x11_driver.imagetype = UI_TRUECOLOR24;
	    break;
	case 32:
	    x11_driver.imagetype = UI_TRUECOLOR;
	    break;
	default:
	    printf("Fatal error:unsupported bits per pixel!\n");
	}
    }
    x11_driver.maxwidth = XDisplayWidth(d->display, d->screen);
    x11_driver.maxheight = XDisplayHeight(d->display, d->screen);
    x11_driver.width =
	((double) ((unsigned int) XDisplayWidthMM(d->display, d->screen)))
	/ x11_driver.maxwidth / 10.0;
    x11_driver.height =
	((double) ((unsigned int) XDisplayHeightMM(d->display, d->screen)))
	/ x11_driver.maxheight / 10.0;
    x11_driver.textheight = xsetfont(d, "fixed");
    x11_driver.textwidth =
	d->font_struct->max_bounds.rbearing -
	d->font_struct->min_bounds.lbearing;
#ifdef MITSHM
    Completion = XShmGetEventBase(d->display) + ShmCompletion;
#endif
    if (d->privatecolormap) {
	x11_driver.flags |= PALETTE_ROTATION | ROTATE_INSIDE_CALCULATION;
    }
    return (1);
}

static void x11_uninitialise(void)
{
#if 0
    if (selection)
	free(selection), selection = NULL;
#endif
    xfree_colors(d);
    xfree_display(d);
}

static void x11_getmouse(int *x, int *y, int *b)
{
    int rootx, rooty;
    Window rootreturn, childreturn;
    XQueryPointer(d->display, d->window,
		  &rootreturn, &childreturn,
		  &rootx, &rooty, x, y, (unsigned int *) b);
}

static CONST struct params params[] = {
    {"", P_HELP, NULL, "X11 driver options:"},
    {"-display", P_STRING, &xparams.display, "Select display"},
    {"-size", P_STRING, &size, "Select size of window (WIDTHxHEIGHT)."},
    {"-sync", P_SWITCH, &Xsync,
     "Generate sync signals before looking for events. This\n\t\t\thelps on old and buggy HP-UX X servers."},
    {"-shared", P_SWITCH, &sharedcolormap,
     "Use shared colormap on pseudocolor display."},
    {"-usedefault", P_SWITCH, &xparams.usedefault,
     "Use default visual if autodetection causes troubles."},
    {"-nomitshm", P_SWITCH, &xparams.nomitshm,
     "Disable MITSHM extension."},
    {"-fullscreen", P_SWITCH, &xparams.fullscreen,
     "Enable fullscreen mode."},
    {"-windowid", P_NUMBER, &xparams.windowid, "Use selected window."},
    {"-window-id", P_NUMBER, &xparams.windowid, "Use selected window."},
    {"-root", P_SWITCH, &xparams.rootwindow, "Use root window."},
    {NULL, 0, NULL, NULL}
};

struct ui_driver x11_driver = {
    "X11",
    x11_init,
    x11_getsize,
    x11_processevents,
    x11_getmouse,
    x11_uninitialise,
    NULL,
    NULL,
    x11_print,
    x11_display,
    x11_alloc_buffers,
    x11_free_buffers,
    x11_flip_buffers,
    x11_cursor,
    x11_flush,
    8,
    8,
    params,
    RESOLUTION | PIXELSIZE | NOFLUSHDISPLAY /*| UPDATE_AFTER_RESIZE */ ,
    0.0, 0.0,
    0, 0,
    UI_C256,
    16, 254, 254 - 16
};

#endif
