/*****************************************************************************
**  This is part of the g2 library
**  Copyright (C) 1998  Ljubomir Milanovic & Horst Wagner
**
**  This program is free software; you can redistribute it and/or modify
**  it under the terms of the GNU General Public License (version 2) as
**  published by the Free Software Foundation.
**
**  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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <limits.h>
#include <math.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "g2_device.h"
#include "g2_util.h"
#include "g2_X11_P.h"
#include "g2_X11.h"
#include "g2_X11_funix.h"
#include "g2_config.h"


static int N_X11=0;
static g2_X11_device *g2_X11_dev=NULL;


/**
 **
 ** Attach (simple) X11 device
 **
 **/
int g2_open_X11(int width, int height)
{
    return g2_open_X11X(width, height,
			10, 10,
			NULL, NULL,
			NULL, -1, -1);
}



/**
 **
 ** Attach (extended) X11 device
 **
 **/
int g2_open_X11X(int width, int height,
		 int x, int y,
		 char *window_name, char *icon_name,
		 char *icon_data, int icon_width, int icon_height)
{
    g2_X11_device *xout=NULL;
    int pid=-1, i;
    char name[32];
    int vid;
    
    if(g2_X11_dev==NULL) {
	g2_X11_dev=g2_malloc(sizeof(g2_X11_device));
	N_X11=1;				  /* first X11 device */
	xout=&g2_X11_dev[N_X11-1];
	pid=0;
    } else {
	for(i=0;i<N_X11;i++)			  /* find free place */
	    if(g2_X11_dev[i].display==NULL) {
		xout=&g2_X11_dev[i];
		pid=i;
		break;
	    }
	if(i==N_X11) {
	    N_X11++;
	    g2_X11_dev=g2_realloc(g2_X11_dev,
			       sizeof(g2_X11_device)*N_X11);
	    xout=&g2_X11_dev[N_X11-1];
	    pid=N_X11-1;
	}
    }
    
    xout->width=width;			  /* set window size */
    xout->height=height;
    
    xout->NofInks=0;			  /* reset inks */
    xout->inks=NULL;
    
    vid = g2_register_physical_device(pid, NULL,
				      g2_IntCoor, g2_X11_funix,
				      1.0, -1.0,
				      0.0, height-1);
    
    sprintf(name, "g2: %d", vid);	   /* set window and icon names */
    if(window_name==NULL)
	window_name=name;
    if(icon_name==NULL)
	icon_name=name;
    
    g2_X11_init_X11X(pid, width, height,
		     x, y,
      		     window_name, icon_name,
		     icon_data, icon_width, icon_height);
    
    
					    /* g2 calls */
    g2_allocate_basic_colors(vid);
    g2_set_background(vid, 0);
    g2_pen(vid, 1);
    
    return vid;
}

     


/*
 *
 * Extended version of the InitX11
 *                      
 */
int g2_X11_init_X11X(int pid, int width, int height,
		     int xposition, int yposition,
      		     char *window_name, char *icon_name,
		     char *icon_data,
		     unsigned int icon_width, unsigned int icon_height)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    Window root;
    XSetWindowAttributes wattr;
    XEvent event;
    Pixmap iconPixmap;
    XSizeHints sizehints;
    int xhot, yhot, rv;
    XColor w_scr, w_exa, r_scr, r_exa;
    XClassHint class_hint;

    if((xout->display=XOpenDisplay(NULL))==NULL) { 
	fputs("g2: can not open display\n", stderr);
	exit(-1);
    }

    xout->root=RootWindow(xout->display, DefaultScreen(xout->display));
    root=xout->root;
    
    wattr.event_mask=ExposureMask;
    xout->window=XCreateWindow(xout->display, root,
			       xposition, yposition,
			       width, height,
			       0,
			       CopyFromParent, InputOutput, CopyFromParent,
			       CWEventMask,
			       &wattr);
    
    xout->colormap=DefaultColormap(xout->display,
				   DefaultScreen(xout->display));

    xout->gc=XCreateGC(xout->display, xout->window,
		       0lu, NULL);
        
    XAllocNamedColor(xout->display, xout->colormap,
		     "red", &r_scr, &r_exa);
    XAllocNamedColor(xout->display, xout->colormap,
		     "white", &w_scr, &w_exa);


    if(icon_data!=NULL) {
	if(icon_width<=0 || icon_height<=0) {	  /* read icon from file */
	    rv=XReadBitmapFile(xout->display, xout->window,
			       icon_data, &icon_width, &icon_height,
			       &iconPixmap, &xhot, &yhot);
	} else {				  /* icon is bitmap */
	    iconPixmap=XCreatePixmapFromBitmapData(xout->display,
						   xout->window,
						   icon_data,
						   icon_width, icon_height,
						   1ul, 0ul, 1);
	    rv=BitmapSuccess;
	}
	switch(rv) {
	  case BitmapOpenFailed:
	    fputs("g2(OpenXX): bitmap file open failed\n",stderr);
	    break;
	  case BitmapFileInvalid:
	    fputs("g2(OpenXX): bitmap file invalid\n",stderr);
	    break;
	  case BitmapNoMemory:
	    fputs("g2(OpenXX): no enough memeory for bitmap\n",stderr);
	    break;
	}
    } else {					  /* no icon data avail. */
	unsigned char bitmapData[] = {
	    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	    0x00,0x00,0x00,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0xe0,
	    0x0d,0x00,0x00,0x00,0x60,0x0c,0x00,0x00,0x00,0x20,0x18,
	    0x00,0x00,0x00,0x00,0x10,0x00,0xf8,0xe3,0x07,0x08,0x00,
	    0xfe,0xfa,0x07,0x0c,0x00,0xbf,0x6e,0x07,0x06,0x80,0x0f,
	    0xf5,0x00,0x01,0x80,0x05,0x34,0x80,0x09,0xc0,0x03,0x78,
	    0xe0,0x18,0x80,0x00,0x70,0xe0,0x1e,0xc0,0x01,0x70,0x70,
	    0x1b,0xc0,0x01,0x50,0x00,0x00,0xc0,0x01,0x70,0x00,0x00,
	    0xc0,0x00,0x70,0x00,0x00,0x40,0x03,0x38,0x00,0x00,0x80,
	    0x05,0x50,0x00,0x00,0x80,0x0a,0x6e,0x00,0x00,0x00,0xfe,
	    0x37,0x00,0x00,0x00,0x6a,0x59,0x00,0x00,0x00,0xbc,0x57,
	    0x00,0x00,0x00,0xe0,0x50,0x00,0x00,0x00,0x00,0x60,0x00,
	    0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x7e,0x00,0x00,
	    0x00,0x7c,0x2b,0x00,0x00,0x00,0xa4,0x1d,0x00,0x00,0x00,
	    0xb8,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	    0x00,0x00};
	iconPixmap=XCreatePixmapFromBitmapData(xout->display, xout->window,
					       (char*)bitmapData,
					       40u, 40u,
					       w_scr.pixel, r_scr.pixel,
					       1ul);
    }
    
    sizehints.min_width = width;
    sizehints.max_width = width;
    sizehints.min_height = height;
    sizehints.max_height = height;
    sizehints.flags = PMinSize | PMaxSize;
    XSetStandardProperties(xout->display, xout->window,
			   window_name, icon_name, iconPixmap,
			   (char **)NULL, 0, &sizehints);
    
    class_hint.res_name =  "g2";
    class_hint.res_class = "G2";
    XSetClassHint(xout->display, xout->window, &class_hint);

 
    XMapRaised(xout->display, xout->window);
    
    XSetWindowBackground(xout->display, xout->window, w_scr.pixel);
    XClearWindow(xout->display,xout->window);
    
    g2_X11_paper(pid, NULL, 0);
    g2_X11_set_font_size(pid, NULL, 12);
						  /* wait expose event */
						  /* (no back. store) */
    while(!XCheckWindowEvent(xout->display,xout->window,
			     ExposureMask,&event))
	;


    wattr.event_mask=NoEventMask;		  /* set NoEventMask */
    wattr.backing_store=Always;			  /* set backing store */
    XChangeWindowAttributes(xout->display, xout->window,
			    CWEventMask|CWBackingStore,
			    &wattr);

    if(XDoesBackingStore(XDefaultScreenOfDisplay(xout->display))!=Always)
	fputs("g2: Warning! Backing store is not available.\n",stderr);

    XFlush(xout->display);
    return 0;
}




int g2_X11_delete(int pid, void *pdp)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XUnmapWindow(xout->display, xout->window);
    XDestroyWindow(xout->display, xout->window);
    XDestroyWindow(xout->display, xout->root);
    XFreeGC(xout->display, xout->gc);
    XFreeColormap(xout->display, xout->colormap);
    XCloseDisplay(xout->display);
    if(xout->inks!=NULL)
	g2_free(xout->inks);
    xout->width=xout->height=0;
    xout->display=NULL;
    return 0;
}



int g2_X11_clear(int pid, void *pdp)
{
    g2_X11_device *xout=&g2_X11_dev[pid];

    XClearWindow(xout->display,xout->window);
    XFlush(xout->display);
    return 0;
}



int g2_X11_flush(int pid, void *pdp)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XFlush(xout->display);
    return 0;
}



int g2_X11_ink(int pid, void *pdp,
	       double red, double green, double blue)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XColor color;

    color.flags=DoRed|DoGreen|DoBlue;
    color.red   = (int)(red   * USHRT_MAX);
    color.green = (int)(green * USHRT_MAX);
    color.blue  = (int)(blue  * USHRT_MAX);
    if(XAllocColor(xout->display,xout->colormap,&color)) {
	xout->NofInks++;
	if(xout->inks==NULL)
	    xout->inks=
	      (unsigned long *)g2_malloc(xout->NofInks*sizeof(unsigned long));
	else
	    xout->inks=
	      (unsigned long *)g2_realloc((void *)xout->inks,
					  xout->NofInks*sizeof(unsigned long));
	if(xout->inks==NULL) {
	    fputs("g2: not enough memory\n",stderr);
	    return -1;
	}
	xout->inks[xout->NofInks-1]=color.pixel;
	return xout->NofInks-1;
    } else {
	fputs("g2: color is not available\n",stderr);
	return -1;
    }
}


int g2_X11_clear_palette(int pid, void *pdp)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XFreeColors(xout->display,xout->colormap,
		xout->inks,xout->NofInks,0x0ul);
    xout->NofInks=0;
    if(xout->inks!=NULL)
        free(xout->inks);
    xout->inks=NULL;
    return 0;
}


int g2_X11_set_background(int pid, void *pdp, int color)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    if(color>=xout->NofInks || color<0)
	return -1;
    XSetWindowBackground(xout->display,xout->window,
			 xout->inks[color]);
    XClearWindow(xout->display,xout->window);
    return 0;
}


int g2_X11_pen(int pid, void *pdp, int color)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    if(color>=xout->NofInks || color<0)
	return -1;
    XSetForeground(xout->display, xout->gc, xout->inks[color]);
    return 0;
}



int g2_X11_paper(int pid, void *pdp, int color)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    if(color>=xout->NofInks || color<0)
	return -1;
    XSetBackground(xout->display, xout->gc, xout->inks[color]);
    return 0;
}


int g2_X11_set_line_width(int pid, void *pdp, int w)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XGCValues val;
    
    val.line_width=w;
    XChangeGC(xout->display, xout->gc, GCLineWidth, &val);
    return 0;
}


int g2_X11_set_dash(int pid, void *pdp, int n, int *data)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XGCValues val;
    int i;
    
    if(n<=0 || data==NULL) {
	val.line_style=LineSolid;
	XChangeGC(xout->display, xout->gc, GCLineStyle,&val);
    } else {
	char *ch_data;
	ch_data=g2_malloc(n*sizeof(char));
	val.line_style=LineOnOffDash;
	for(i=0;i<n;i++)
	    if(data[i]>0)
		ch_data[i]=(char)data[i];
	    else
		ch_data[i]=1;
	XChangeGC(xout->display, xout->gc, GCLineStyle, &val);
	XSetDashes(xout->display, xout->gc, 0, ch_data, n);
	g2_free(ch_data);
    }
    return 0;
}


int g2_X11_set_font_size(int pid, void *pdp, int size)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XFontStruct *fnt_str;
    char font_name[256];
    int sizei, d, n;
    
    sizei=dtoi(size);

    if(sizei<=0)
	sizei=1;				  /* set to smallest size */
    
    for(n=1;n<32;n++) {
	d=((n&0x01)? -1:1)*(n>>1);
	sprintf(font_name, G2_X11_FONT, sizei+d);
	fnt_str=XLoadQueryFont(xout->display, font_name);
	if(fnt_str==NULL) {
	    if(!d)
		fprintf(stderr,"g2: can not load font: '%s'\n",font_name);
	} else {
	    XSetFont(xout->display,xout->gc,fnt_str->fid);
	    if(d)
		fprintf(stderr,"g2: using '%s' instead\n",font_name);
	    return 0;
	}
    }
    fprintf(stderr, "g2: are you sure about %d point size\n", size);
    return -1;
}


int g2_X11_plot(int pid, void *pdp, int x, int y)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XDrawPoint(xout->display, xout->window, xout->gc,
	       x, y);
    return 0;
}


int g2_X11_line(int pid, void *pdp, int x1, int y1, int x2, int y2)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XDrawLine(xout->display,xout->window,xout->gc,
	      x1, y1, x2, y2);
    return 0;
}


int g2_X11_poly_line(int pid, void *pdp, int N, int *p)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XPoint *points;
    int i;
    points=g2_malloc(N*sizeof(XPoint));
    for(i=0;i<N;i++) {
	points[i].x=(short)p[i*2];
	points[i].y=(short)p[i*2+1];
    }
    XDrawLines(xout->display,xout->window,xout->gc,
	       points, N,
	       CoordModeOrigin);
    g2_free(points);
    return 0;
}


int g2_X11_polygon(int pid, void *pdp, int N, int *p)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XPoint *points;
    int i;
    points=g2_malloc((N+1)*sizeof(XPoint));
    for(i=0;i<N;i++) {
	points[i].x=(short)p[i*2];
	points[i].y=(short)p[i*2+1];
    }
    points[N].x=(short)p[0];
    points[N].y=(short)p[1];
    XDrawLines(xout->display,xout->window,xout->gc,
	       points, N+1,
	       CoordModeOrigin);
    g2_free(points);
    return 0;
}


int g2_X11_filled_polygon(int pid, void *pdp, int N, int *p)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XPoint *points;
    int i;
    points=g2_malloc((N+1)*sizeof(XPoint));
    for(i=0;i<N;i++) {
	points[i].x=(short)p[i*2];
	points[i].y=(short)p[i*2+1];
    }
    points[N].x=(short)p[0];
    points[N].y=(short)p[1];
    XFillPolygon(xout->display,xout->window,xout->gc,
		 points, N+1,
		 Complex, CoordModeOrigin);
    g2_free(points);
    return 0;
}


int g2_X11_triangle(int pid, void *pdp,
		    int x1, int y1,
		    int x2, int y2,
		    int x3, int y3)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XPoint points[4];
    points[0].x=x1; points[0].y=y1; 
    points[1].x=x2; points[1].y=y2; 
    points[2].x=x3; points[2].y=y3; 
    points[3].x=x1; points[3].y=y1; 
    XDrawLines(xout->display,xout->window,xout->gc,
	       points, 4, CoordModeOrigin);
    return 0;
}


int g2_X11_filled_triangle(int pid, void *pdp,
			   int x1, int y1,
			   int x2, int y2,
			   int x3, int y3)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XPoint points[4];
    points[0].x=x1; points[0].y=y1; 
    points[1].x=x2; points[1].y=y2; 
    points[2].x=x3; points[2].y=y3; 
    points[3].x=x1; points[3].y=y1; 
    XFillPolygon(xout->display,xout->window,xout->gc,
		 points, 4, Convex, CoordModeOrigin);
    return 0;
}


int g2_X11_rectangle(int pid, void *pdp, int x1, int y1, int x2, int y2)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XDrawRectangle(xout->display,xout->window,xout->gc,
		   x1, y1, x2-x1, y2-y1);
    return 0;
}



int g2_X11_filled_rectangle(int pid, void *pdp, int x1, int y1, int x2, int y2)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XDrawRectangle(xout->display,xout->window,xout->gc,
		   x1, y1, x2-x1, y2-y1);
    XFillRectangle(xout->display,xout->window,xout->gc,
		   x1, y1, x2-x1, y2-y1);
    return 0;
}


int g2_X11_arc(int pid, void *pdp, int x, int y,
	       int r1, int r2, double a1, double a2)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    a1=fmod(a1, 360.);
    a2=fmod(a2, 360.);
    if(a2-a1<0)
	a2+=360.;
    XDrawArc(xout->display,xout->window,xout->gc,
	     x-r1, y-r2,
	     r1*2, r2*2,
	     (int)(a1*64.), (int)((a2-a1)*64.));
    return 0;
}
 

int g2_X11_filled_arc(int pid, void *pdp, int x, int y,
		      int r1, int r2, double a1, double a2)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    a1=fmod(a1, 360.);
    a2=fmod(a2, 360.);
    if(a2-a1<0)
	a2+=360.;
    XDrawArc(xout->display,xout->window,xout->gc,
	     x-r1, y-r2,
	     r1*2, r2*2,
	     a1*64, (a2-a1)*64.0);
    XFillArc(xout->display,xout->window,xout->gc,
	     x-r1, y-r2,
	     r1*2, r2*2,
	     (int)(a1*64.), (int)((a2-a1)*64.));
    return 0;
}
 

int g2_X11_ellipse(int pid, void *pdp, int x, int y, int r1, int r2)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XDrawArc(xout->display,xout->window,xout->gc,
	     x-r1, y-r2,
	     r1*2, r2*2,
	     0,360*64);
    return 0;
}


int g2_X11_filled_ellipse(int pid, void *pdp, int x, int y, int r1, int r2)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XDrawArc(xout->display,xout->window,xout->gc,
	     x-r1, y-r2,
	     r1*2, r2*2,
	     0,360*64);
    XFillArc(xout->display,xout->window,xout->gc,
	     x-r1, y-r2,
	     r1*2, r2*2,
	     0,360*64);
    XFlush(xout->display);
    return 0;
}
 

int g2_X11_draw_string(int pid, void *pdp, int x, int y, char *text)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XDrawString(xout->display,xout->window,xout->gc,
		x, y, text, strlen(text));
    return 0;
}




int g2_X11_image(int pid, int x, int y, int width, int height, int *pen_array)
{
    g2_X11_device *xout=&g2_X11_dev[pid];
    XImage *image=NULL;
    Screen *screen;
    unsigned long *ink_array;
    int i;

    ink_array=malloc(sizeof(unsigned long)*width*height);
    for(i=0;i<width*height;i++)
	ink_array[i]=xout->inks[pen_array[i]];

    screen=DefaultScreenOfDisplay(xout->display);
    printf("BP1\n");
    image=XCreateImage(xout->display,
		       DefaultVisualOfScreen(screen),
		       DefaultDepthOfScreen(screen),
		       ZPixmap,
		       0,			  /* offset */
		       (char *)ink_array,
		       width, height,
		       sizeof(unsigned long)*8,		  /* bitmap pad */
		       0);			  /* bytes per line */
    printf("BP2\n");
    /* XInitImage(image); problems with AIX ?!! */
    printf("BP3 image=%p\n", (void*)image);
    XPutImage(xout->display, xout->window, xout->gc,
	      image,
	      0, 0,
	      x, y, width, height);
    printf("BP4\n");
	
    XDestroyImage(image);
    free(ink_array);
    return 0;
}
