#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <setjmp.h>
#include "types.h"
#include "icon.h"
#include "gray.h"

#define DEFAULT_FONT    "6x13"
#define MAXPOINTS 1000
#define MAXNUMLEN 5

#define SCALEX(X) (int) (Xscale * X)
#define SCALEY(Y) (int) (xsh.height - Y * Xscale)

char        *geom;          /* string which describes geometry of the window */
Display     *dpy;           /* X server connection */
int         scr_id;         /* screen ID */
Screen      *screen;         /* screen struct */
int         depth;          /* depth of pixmap */
Window      win;            /* Window ID */
Pixmap      pix;            /* Pixmap */
GC          gc;             /* GC to draw with */
GC          bgc;            /* GC to fill background with */
GC          ggc[gray_values];            /* reverse GC to fill gray with */
char        *fontName;      /* Name of the font to use */
XFontStruct *fontstruct;    /* Font descriptor */
int         fth, ftw;       /* Font size parameters */
unsigned long fg, bg, bd;   /* Pixel values */
unsigned long bw;           /* Border width */
XGCValues   gcv, bgcv;      /* Structs for creating GC */
XEvent      event;          /* Event received */
XSizeHints  xsh;            /* Size hints for window manager */
XSetWindowAttributes xswa;  /* Temporary Set Window Attribute struct */

int         Npoints;        /* number of points in the cur. polygon or line */
XPoint      Pts[MAXPOINTS]; /* array with points of the cur. poly or line */
int         Xscale;         /* scale factor from graph3d coords to window */
char        Modeline[256];  /* contains the mode line information */

extern STYLE     Style;    /* we have to write fontsizes into this struct */
extern TRANSFORM Transform;
extern jmp_buf   Redraw_env, Newview_env;

/* stuff for icon */
Pixmap icon_pix, gray_pix[gray_values];
	       
int xopenpl(STYLE *s )   
{
  int i;

  /*
   * Open the display using the $DISPLAY environment variable to locate
   * the X server.
   */
  if ((dpy = XOpenDisplay(NULL)) == NULL) {
    fprintf(stderr, "Can't open display %s\n", XDisplayName(NULL));
    exit(1);
  }

  /*
   *  uncomment this for synchronous behaviour, needed for debugging
   */
/*  XSynchronize(dpy, 1); */

  if(!fontName) fontName = DEFAULT_FONT;
  if ((fontstruct = XLoadQueryFont(dpy, fontName)) == NULL) {
    fprintf(stderr, "display %s doesn't know font %s\n",
	    DisplayString(dpy), fontName);
    exit(1);
  }
  fth = fontstruct->max_bounds.ascent + fontstruct->max_bounds.descent;
  ftw = fontstruct->max_bounds.width;

  /*
   * Select colors for the border,  the window background,  and the
   * foreground.
   */
  scr_id = DefaultScreen(dpy);
  screen = ScreenOfDisplay(dpy, scr_id);

  bd = BlackPixel(dpy, scr_id);
  bg = WhitePixel(dpy, scr_id);
  fg = BlackPixel(dpy, scr_id); 
  
  /*
   * Set the border width of the window
   */
  bw = 3;
  
  /*
   * Create the Window with the information in the XSizeHints, the
   * border width,  and the border & background pixels.
   */

  depth = DefaultDepth(dpy, scr_id); 
  xsh.width = 500;
  xsh.height = 500;
  xsh.x = 0; 
  xsh.y = 0;
  xsh.flags = (USPosition | USSize); 
  
  if (geom) {
    XParseGeometry(geom, &xsh.x, &xsh.y,
		   (unsigned int *)&xsh.width, (unsigned int *)&xsh.height);
  }

  Xscale = (xsh.width > xsh.height) ? xsh.height : xsh.width;
  s->x_char_size = ftw / (float)Xscale;
  s->y_char_size = fth / (float)Xscale;

  win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
			    xsh.x, xsh.y, xsh.width, xsh.height,
			    bw, bd, bg);

  if(!DoesBackingStore(screen))
    pix = XCreatePixmap(dpy, DefaultRootWindow(dpy),
			xsh.width, xsh.height, depth);
  else
    fprintf(stderr, "Using Backing store\n");

  icon_pix = XCreatePixmapFromBitmapData(dpy, DefaultRootWindow(dpy),
					 icon_bits, icon_width, icon_height,
					 fg, bg, depth);

  if(s->remove_hidden) /* install pixmaps for gray poly's */
    for(i = 0; i < gray_values; i++)
      gray_pix[i] = XCreatePixmapFromBitmapData(dpy, DefaultRootWindow(dpy),
						gray_bits[i], gray_width,
						gray_height, fg, bg, 1);

  /* 
   * Set the standard properties for the window managers.
   */
  XSetStandardProperties(dpy, win, "", "GRAPH3D", icon_pix, NULL, 0, &xsh);
  
  /*
   * Ensure that the window's colormap field points to the default
   * colormap,  so that the window manager knows the correct colormap to
   * use for the window.  See Section 3.2.9. Also,  set the window's Bit
   * Gravity to reduce Expose events.
   */

  xswa.save_under    = True;
  xswa.backing_store = Always;
  xswa.colormap      = DefaultColormap(dpy, scr_id);
  xswa.bit_gravity   = CenterGravity;
  XChangeWindowAttributes(dpy, win, (CWSaveUnder | CWBackingStore | CWColormap | CWBitGravity), &xswa);

  /*
   * Create gc and bgc for plotting
   */
  
  gcv.font       = fontstruct->fid;
  gcv.foreground = fg;
  gcv.background = bg;
  gc  = XCreateGC(dpy, win, (GCFont | GCForeground | GCBackground), &gcv);

  bgcv.foreground = bg;
  bgcv.background = fg;
  bgc = XCreateGC(dpy, win, (GCForeground | GCBackground), &bgcv);

  if(s->remove_hidden) /* install GC's for gray poly's */
    {
      gcv.fill_style = FillOpaqueStippled;
      for(i = 0; i < gray_values; i++)
	{
	  gcv.stipple = gray_pix[i];
	  ggc[i] = XCreateGC(dpy, win,
			     (GCFillStyle | GCStipple | 
			      GCForeground | GCBackground),
			     &gcv);
	}
    }


  /* ensure white background in window */
  if(!DoesBackingStore(screen))
    XFillRectangle(dpy, pix, bgc, 0, 0, xsh.width, xsh.height);

  /*
   * Specify the event types we're interested in.
   */
  XSelectInput(dpy, win, ExposureMask | KeyPressMask | StructureNotifyMask);
  
  /*
   * Map the window to make it visible.
   */
  XMapWindow(dpy, win);     

  /* now wait untill we see the first expose event
   * With release 4 servers and/or mwm we never see it, therefore also look
   * for MapNotify
   */
  while(1)
    {
      XNextEvent(dpy, &event);
      if(event.type == Expose || event.type == MapNotify)
	return(0);
    }
}


int check_Npoints(void) 
{
  if(Npoints >= MAXPOINTS) {
    fprintf(stderr, "Too many points in polygon or line (> %d)\n",
	    MAXPOINTS);
    exit(1);
  }
}

int xstartpoly(void) 
{
  Npoints = 0;  
}

int xstoppoly(int  hide   ,
               float brightness )   
{
  int gray_index;
  /* close polygon */
  check_Npoints();
  Pts[Npoints].x   = Pts[0].x;
  Pts[Npoints++].y = Pts[0].y;
  if (hide) {
    gray_index = (gray_values - 1) - (int)((gray_values - 1) * brightness);
    XFillPolygon(dpy, win, ggc[gray_index], Pts, Npoints, Nonconvex,
		 CoordModeOrigin);
      if(!DoesBackingStore(screen))
	XFillPolygon(dpy, pix, ggc[gray_index], Pts, Npoints, Nonconvex,
		     CoordModeOrigin);
  }
  XDrawLines(dpy, win, gc, Pts, Npoints, CoordModeOrigin);
  event_handler(1);
}

int xcontpoly(float x1 ,
               float y1 )   
{
  check_Npoints();
  Pts[Npoints].x   = SCALEX(x1);
  Pts[Npoints++].y = SCALEY(y1);
}

int xstartline(void) 
{
  Npoints = 0;  
}

int xstopline(void) 
{
  XDrawLines(dpy, win, gc, Pts, Npoints, CoordModeOrigin);
  event_handler(1);
}

int xcontline(float x1 ,
               float y1 )   
{
  check_Npoints();
  Pts[Npoints].x   = SCALEX(x1);
  Pts[Npoints++].y = SCALEY(y1);
}

int xlabel(char *s   ,
            float xi ,
            float yi )   
{
  int x, y;
  
  x = SCALEX(xi);
  y = SCALEY(yi);
  XDrawString(dpy, win, gc, x, y, s, strlen(s));
  event_handler(1);
}

int xclosepl(void)

{
  sprintf(Modeline, "xrot: %d yrot: %d zrot: %d focus: %.3g plane: %.3g",
	  (int)Transform.x_rot, (int)Transform.y_rot, (int)Transform.z_rot,
	  Transform.proj_focus, Transform.proj_plane);
  XDrawString(dpy, win, gc, ftw / 2, fth, Modeline, strlen(Modeline));
  if(!DoesBackingStore(screen)) /* save window contents in pixmap */
    XCopyArea (dpy, win, pix, gc, 0, 0, xsh.width, xsh.height, 0, 0);

  while (1) {
    sleep((unsigned) 1);
    event_handler(0);    
  }
}

int event_handler(int still_drawing )   
{
  static char  numb_buf[MAXNUMLEN];
  static int   i = 0;
  static int   skip_expose = 0;

  while (XEventsQueued (dpy, QueuedAfterReading) > 0) {
    XNextEvent (dpy, &event);
    switch (event.type) {
    case Expose:
      if(event.xexpose.count == 0) { /* merge multiple expose events */
	if((!still_drawing) && (!DoesBackingStore(screen)))
	  {
	    XClearWindow(dpy, win);
	    XCopyArea (dpy, pix, win, gc, 0, 0, xsh.width, xsh.height, 0, 0);
	  }
/* does not seem to be necessary, you never get a meaningful expose event
 * when having backing_store. But, when your server runs out of memory ...
*/
	else
	  if(skip_expose)
	    {
	      skip_expose = 0;
	    }
	  else
	    longjmp(Redraw_env, 1);
      }
      break;
      
    case MappingNotify:
      XRefreshKeyboardMapping ((XMappingEvent *)&event);
      break;
      
    case KeyPress:
      {
	KeySym keysym;
	XComposeStatus compose;
	int count, rotation;
	char char_buf[5], c;
	
	count = XLookupString ((XKeyEvent *)&event,
			       char_buf, 5, &keysym, &compose);

	if (count == 1) {
	  c = char_buf[0];

	  if(c == 'q')
	    exit(0);

	  if((c >= '0' && c <= '9') || c == '-') {
	    if(i < (MAXNUMLEN - 1)) /* reserve space for \0 */
	      numb_buf[i++] = c;
	    else
	      XBell(dpy, 100); /* beep if too many numbers */
	    break;
	  }

	  if(   c == 'x' || c == 'X'
	     || c == 'y' || c == 'Y'
	     || c == 'z' || c == 'Z'
	     || c == 'f' || c == 'F'
	     || c == 'p' || c == 'P') {
	    numb_buf[i] = '\0';
	    if(i == 0)
	      rotation = 15;
	    else
	      rotation = atoi(numb_buf);
	    i = 0;
	    switch (c) {
	    case 'x':
	      Transform.x_rot += rotation;
	      break;
	    case 'X':
	      Transform.x_rot -= rotation;
	      break;
	    case 'y':
	      Transform.y_rot += rotation;
	      break;
	    case 'Y':
	      Transform.y_rot -= rotation;
	      break;
	    case 'z':
	      Transform.z_rot += rotation;
	      break;
	    case 'Z':
	      Transform.z_rot -= rotation;
	      break;
	    case 'f':
	      Transform.proj_focus *= 1.5;
	      break;
	    case 'F':
	      Transform.proj_focus /= 1.5;
	      break;
	    case 'p':
	      Transform.proj_plane *= 1.5;
	      break;
	    case 'P':
	      Transform.proj_plane /= 1.5;
	      break;
	    }
	    XClearWindow(dpy, win);
	    if(!DoesBackingStore(screen))
	      XFillRectangle(dpy, pix, bgc, 0, 0, xsh.width, xsh.height);
	    longjmp(Newview_env, 1);
	    break;
	  }
	}
	/* ring bell for unrecognised keystroke */
	if(c && (keysym != XK_Shift_L) && (keysym != XK_Shift_R))
	  XBell(dpy, 100);
      }
      break;
      
    case ConfigureNotify:
      /* skip move events here, they are handled as Expose events */
      if(!(xsh.width == event.xconfigure.width &&
	   xsh.height == event.xconfigure.height)) {

	/* when the window grows, we will get an expose event as well */
	if(xsh.width < event.xconfigure.width ||
	   xsh.height < event.xconfigure.height)
	  skip_expose = 1;


	XClearWindow(dpy, win);
	if(!DoesBackingStore(screen))
	  XFreePixmap(dpy, pix);
	xsh.width  = event.xconfigure.width;
	xsh.height = event.xconfigure.height;
	if(!DoesBackingStore(screen))
	  {
	    pix = XCreatePixmap(dpy, win, xsh.width, xsh.height, depth);
	    /* make background white again */
	    XFillRectangle(dpy, pix, bgc, 0, 0, xsh.width, xsh.height);
	  }
	/* now set new scale factor and font size */
	Xscale = (xsh.width > xsh.height) ? xsh.height : xsh.width;
	Style.x_char_size = ftw / (float)Xscale;
	Style.y_char_size = fth / (float)Xscale;
	longjmp(Redraw_env, 1);
      }
      break;
      
    default:
      break;
    }
  }
  XFlush(dpy);
}

