/*
 *    Ygl: Run 2d-GL programs with standard X11 routines.
 *    (C) Fred Hucht 1993-95
 *    EMail: fred@thp.Uni-Duisburg.DE
 */

#include "header.h"

#define VERSION "3.0.0"

static const char *WhatString[2]= {
  "@(#)Ygl V" VERSION " by Fred Hucht (C) 1993-95",
  "@(#)EMail: fred@thp.Uni-Duisburg.DE"
};

static void change_gc(Int32, Ulong, XGCValues *);

static void change_gc(Int32 id, Ulong mask, XGCValues *values) {
  int i;
  YglWindow *w = Ygl.Windows + id;
  if(w->rgb || Ygl.GC) { /* RGB windows have only one GC */
    XChangeGC(D, w->gc, mask, values);
  } else {
    for(i = 0; i < CMapSize; i++) {
      XChangeGC(D, w->gclist[i], mask, values);
    }
  }
}

void singlebuffer(void) {
  I("singlebuffer");
  W->dbuf = False;	/* Window is not in dbuf mode */
  W->draw = W->win;
}

#define YglUpdateAction MultibufferUpdateActionUndefined
#define YglUpdateHint   MultibufferUpdateHintFrequent

#ifdef MULTIBUFLIB

static int HasMBuf = True;

void doublebuffer(void) {
  I("doublebuffer");
  if(HasMBuf && W->dispbuf == -1) { /* If not inited */
    if(2 != XmbufCreateBuffers(D, W->win, 2, YglUpdateAction, YglUpdateHint, W->mbuf)) {
      fprintf(stderr, "Ygl: doublebuffer: unable to create 2 buffers.\n");
      HasMBuf = False;
    } else {
      W->dbuf = True;			/* Window is in dbuf mode */
      W->dispbuf = 0;			/* Displayed buffer */
      W->draw = W->mbuf[1 - W->dispbuf];/* draw to backbuffer */
    }
  }
}

void swapbuffers(void) {
  I("swapbuffers");
  if(HasMBuf) { /* MULTI-BUFFER extension in X-Server? */
    
    if(!W->dbuf) {
      fprintf(stderr, "Ygl: swapbuffers: window %d is not in doublebuffer mode.\n", winget());
      return;
    }
    
    W->draw = W->mbuf[W->dispbuf];
    W->dispbuf = 1 - W->dispbuf;
    XmbufDisplayBuffers(D, 1, W->mbuf + W->dispbuf, 0, 0);
  }
  XFlush(D); /* XmbufDisplayBuffers() seems not to flush */
}

void frontbuffer(Int32 bool) {
  I("frontbuffer");
  if(HasMBuf) { /* MULTI-BUFFER extension in X-Server? */
    
    if(!W->dbuf) {
      fprintf(stderr, "Ygl: frontbuffer: window %d is not in doublebuffer mode.\n", winget());
      return;
    }
    
    if(bool) W->draw = W->mbuf[    W->dispbuf];
    else     W->draw = W->mbuf[1 - W->dispbuf];
  }
  XFlush(D);
}

void backbuffer(Int32 bool) {
  I("backbuffer");
  frontbuffer(!bool);
}

#else /* MULTIBUFLIB */

void doublebuffer(void) {
  I("doublebuffer");
  fprintf(stderr, "Ygl: doublebuffer: Ygl is not configured to use doublebuffering.\n");
}

void swapbuffers(void) {
  I("swapbuffers");
  XFlush(D);
}

void frontbuffer(Int32 bool) {
  I("frontbuffer");
}

void backbuffer(Int32 bool) {
  I("backbuffer");
}

#endif /* MULTIBUFLIB */

void gflush(void) {
  I("gflush");
  XFlush(D);
}

Display *getXdpy(void) {
  I("getXdpy");
  return(D);
}

Window getXwid(void) {
  /* Return the main window. Usable for move/resize, map/unmap,
   * event stuff */
  I("getXwid");
  return(W->main);
}

Window getXdid(void) {
  /* Return the drawable. Usable for drawing. */
  I("getXdid");
  return(W->draw);
}

GC getXgc(void) {
  I("getXgc");
  return(W->gc);
}

void wintitle(Char8 *Title) {
  I("wintitle");
  XStoreName(D, W->main, Title);
}

void winset(Int32 wid) {
  I("winset");
  if(wid > 0 && wid <= Ygl.NextWindow && Ygl.Windows[wid].main != 0) {
    W = Ygl.Windows + (Ygl.ActiveWindow = wid);
  } else {
    fprintf(stderr, "Ygl: winset: invalid window id: %d\n", wid);
  }
}

Int32 winget(void) {
  I("winget");
  return(Ygl.ActiveWindow);
}

Int32 getplanes(void) {
  I("getplanes");
  return(W->rgb ? Ygl.RV.depth : Ygl.EmulateCmap ? EMULATE_CMAP_DEPTH : Ygl.CV.depth);
}

Int32 getvaluator(Device dev) {
  Window junkwin;
  int rx, ry, cx, cy;
  Uint mask;
  Int32 r = -1;
  I("getvaluator");
  XQueryPointer(D, W->main, &junkwin, &junkwin, &rx, &ry, &cx, &cy, &mask);
#ifdef DEBUG
  fprintf(stderr, "getvaluator: root = (%d,%d), child = (%d,%d) mask=0x%x\n", rx, ry, cx, cy, mask);
#endif
  switch(dev) {
  case MOUSEX: r = rx; break;
  case MOUSEY: r = YglScreenHeight - ry - 1; break;
  default:     fprintf(stderr, "Ygl: getvaluator: unknown device: %d.\n", dev); break;
  }
  return(r);
}

Int32 getbutton(Device dev) {
  Window junkwin;
  int junk;
  Uint mask;
  Int32 r = -1;
  I("getbutton");
  XQueryPointer(D, W->main, &junkwin, &junkwin, &junk, &junk, &junk, &junk, &mask);
#ifdef DEBUG
  fprintf(stderr, "getbutton: mask = 0x%x\n", mask);
#endif
  switch(dev) {
  case   LEFTMOUSE: r = 0 != (mask & Button1Mask); break;
  case MIDDLEMOUSE: r = 0 != (mask & Button2Mask); break;
  case  RIGHTMOUSE: r = 0 != (mask & Button3Mask); break;
  default:          fprintf(stderr, "Ygl: getbutton: unknown device: %d.\n", dev); break;
  }
  return(r);
}

Int32 gversion(Char8 *v) { 
  static const char *version = "Ygl:X11-" VERSION;
  int i = 0, r = 0;
  
  while('\0' != (*(v+i) = *(version+i))) i++;
  
  if(D == NULL) {
    Display *dpy = XOpenDisplay(NULL);
    if(dpy == NULL) r = -1;
    else XCloseDisplay(dpy);
  }
  return(r);
}

void ortho2(Coord x1, Coord x2, Coord y1, Coord y2) {
  I("ortho2");
  if(x1 == x2 || y1 == y2) {
    fprintf(stderr, "Ygl: ortho2: X-range or Y-range is empty.\n");
    return;
  }
  W->xo = x1;
  W->yo = y1;
  W->xd = x2 - x1;
  W->yd = y2 - y1;
  W->xf = (double)W->xm / W->xd;
  W->yf = (double)W->ym / W->yd;
}

void reshapeviewport(void) {
  Int32 x, y;
  I("reshapeviewport");
  getsize(&x, &y);
  W->xm = x; W->ym = y;
  W->xf = (double)W->xm / W->xd;  /* Used in scaled lib */
  W->yf = (double)W->ym / W->yd;  /* Used in scaled lib */
  if(W->rgb) XResizeWindow(D, W->win, x, y);
}

void winpop(void) {
  I("winpop");
  XRaiseWindow(D, W->main);
}

void winpush(void) {
  I("winpush");
  XLowerWindow(D, W->main);
}

void linewidth(Int16 w) {
  Ulong      mask;
  XGCValues  values;
  I("linewidth");
  if(w == W->linewidth) return; /* already set */
  W->linewidth = values.line_width = w;
  mask = GCLineWidth;
  change_gc(Ygl.ActiveWindow, mask, &values);
}

Int32 getlwidth(void) {
  I("getlwidth");
  return(W->linewidth);
}

Int32 getdisplaymode(void) {
  I("getdisplaymode");
  if(W->rgb)
    if(W->dbuf) return(DMRGBDOUBLE);
    else        return(DMRGB);
  else
    if(W->dbuf) return(DMDOUBLE);
    else        return(DMSINGLE);
}

void setbell(Char8 t) {
  XKeyboardControl xkbc;
  I("setbell");
  switch(t) {
  case 0: xkbc.bell_duration = 0;   break;
  case 1: xkbc.bell_duration = 100; break;
  case 2: xkbc.bell_duration = 400; break;
  default:
    fprintf(stderr, "Ygl: setbell: invalid value: %d.\n", t);
    return; break;
  }
  XChangeKeyboardControl(D, KBBellDuration, &xkbc);
  F;
}

void ringbell(void) {
  I("ringbell");
  XBell(D, 0);
  F;
}

Int32 getgdesc(Int32 what) {
  Int32 r = -1;
  Display *dpy;
  
  if(D == NULL) {
    if ((dpy = XOpenDisplay(NULL)) == NULL) {
      fprintf(stderr, "Ygl: getgdesc: can\'t open display \"%s\".\n", XDisplayName(NULL));
      exit(1);
    }
  } else {
    dpy = D;
  }
  
  switch(what) {
  case GD_XPMAX: r = DisplayWidth (dpy, DefaultScreen(dpy)); break;
  case GD_YPMAX: r = DisplayHeight(dpy, DefaultScreen(dpy)); break;
  default:
    fprintf(stderr, "Ygl: getgdesc: unsupported or unknown argument: %d.\n", what);
    break;
  }

  if(D == NULL) {
    XCloseDisplay(dpy);
  }

  return(r);  
}

void foreground(void) {
  /* Do nothing */
}


#ifdef COVERSLEEP

#if defined (__linux) /* Any others? */
# define USLEEP_RETURNS_VOID
#endif

#ifdef USLEEP_RETURNS_VOID
void
#else
int
#endif

usleep(Ulong Useconds) {
  struct timeval tmout;
  if(D != NULL) XFlush(D);
  tmout.tv_usec = Useconds % 1000000;
  tmout.tv_sec  = Useconds / 1000000;
  (void) select(0, NULL, NULL, NULL, &tmout);
#ifdef USLEEP_RETURNS_VOID
  return;
#else
  return (0);
#endif
}

Uint sleep(Uint seconds) {
  struct timeval tmout;
  if(D != NULL) XFlush(D);
  tmout.tv_usec = 0;
  tmout.tv_sec  = seconds;
  (void) select(0, NULL, NULL, NULL, &tmout);
  return (0);
}
#endif
