/*
 * x11.c
 * kirk johnson
 * october 1990
 */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include "mosaic.h"
#include "x11.h"
#include "patchlevel.h"

#define BufSize       (256)

#define BoundVarVal(var, min, max) \
{                                  \
  if ((var) < (min))               \
    (var) = (min);                 \
  else if ((var) > (max))          \
    (var) = (max);                 \
}

typedef unsigned long Pixel;
typedef XFontStruct   XFS;

static void   GetDisplayName();
static void   GetResourceOptions();
static int    IsTrue();
static char  *strdup();
static void   GetCmdLineOptions();
static void   Help();
static void   doKey();
static void   doMotion();
static void   doBoardExpose();
static void   doHelpExpose();
static void   doNextExpose();
static void   doMainExpose();
static void   doHighExpose();
static void   doButtonPress();
static Pixel  GetColor();
static Window GetWindow();
static Pixmap MakePixmap();
static void   SetCursor();
static void   TopWindowStuff();
static void   AddEventHandler();
static void (*GetEventHandler())();

static char  buf[BufSize];
static char *TileBits[4];

static char *dpyname;		/* display name */
static int   mono;		/* mono mode flag */
static int   solid;		/* solid mode flag */
static int   expert;		/* expert mode flag */
static int   gwidth;		/* gutter width */
static int   bwidth;		/* border width */
static char *fgname;		/* fg color name */
static char *bgname;		/* bg color name */
static char *bdname;		/* border color name */
static char *cname[4];		/* tile color names */
static char *fntname;		/* font name */

static Display *dpy;		/* display connection */
static int      scrn;		/* screen number */
static Window   root;		/* root window */
static Visual  *visl;		/* visual */
static Colormap cmap;		/* colormap */
static int      dpth;		/* display depth */
static GC       gc;		/* graphics context */

static Pixel  fore;		/* foreground color */
static Pixel  back;		/* background color */
static Pixel  brdr;		/* border color */
static Pixel  tilec[4];		/* tile colors */
static Pixmap tilep[4];		/* tile pixmaps */
static XFS   *font;		/* text font */

static Window mainw;		/* main window */
static Window boardw;		/* board window */
static Window helpw;		/* help window */
static Window nextw;		/* next piece window */
static Window scoresw;		/* high scores window */

static int crow = 0;		/* controller position */
static int ccol = 0;

static int scoresvis;		/* high scores visible? */
static int helpvis;		/* help window visible? */

static int swidth;		/* width of score text area */
static int hswidth;		/* width of high score window */


void InitDisplay(argc, argv)
     int    argc;
     char **argv;
{
  int    i;
  int    w, h;
  int    x, y;

  /* set standard defaults */
  dpyname  = NULL;		/* default to DISPLAY */
  expert   = False;		/* default to novice mode */
  mono     = False;		/* default to color (if possible) */
  solid    = False;		/* default to patterned tiles */
  gwidth   = DfltGutterWidth;	/* gutter width */
  bwidth   = DfltBorderWidth;	/* border width */
  fgname   = DfltForeName;	/* foreground color name */
  bgname   = DfltBackName;	/* background color name */
  bdname   = DfltBrdrName;	/* border color name */
  cname[0] = DfltC0Name;	/* color 0 name */
  cname[1] = DfltC1Name;	/* color 1 name */
  cname[2] = DfltC2Name;	/* color 2 name */
  cname[3] = DfltC3Name;	/* color 3 name */
  fntname  = DfltFontName;	/* font name */

  GetDisplayName(argc, argv);

  /* general X11 setup */
  dpy = XOpenDisplay(dpyname);
  if (dpy == NULL)
  {
    sprintf(buf, "unable to open display \"%s\"", dpyname);
    fatal(buf);
  }
  scrn = DefaultScreen(dpy);
  root = RootWindow(dpy, scrn);
  visl = DefaultVisual(dpy, scrn);
  cmap = DefaultColormap(dpy, scrn);
  dpth = DefaultDepth(dpy, scrn);

  GetResourceOptions();

  /*
   * if running with a StaticGray or GrayScale
   * display class, default to mono mode
   */
  switch (visl->class)
  {
  case StaticGray:
  case GrayScale:
    mono = True;
    break;
  }

  GetCmdLineOptions(argc, argv);

  /* check for mono mode */
  if (mono)
  {
    bdname = fgname;
    for (i=0; i<4; i++)
      cname[i] = fgname;
  }

#ifdef DEBUG
  /* debug: color names */
  fflush(stdout);
  fprintf(stderr, "%s: color names\n", AppName);
  fprintf(stderr, "  fgname %s\n", fgname);
  fprintf(stderr, "  bgname %s\n", bgname);
  fprintf(stderr, "  bdname %s\n", bdname);
  fprintf(stderr, "  c0name %s\n", cname[0]);
  fprintf(stderr, "  c1name %s\n", cname[1]);
  fprintf(stderr, "  c2name %s\n", cname[2]);
  fprintf(stderr, "  c3name %s\n", cname[3]);
  fprintf(stderr, "\n");
  fflush(stderr);
#endif /* DEBUG */

  /* color allocation */
  fore = GetColor(fgname);
  back = GetColor(bgname);
  brdr = GetColor(bdname);
  for (i=0; i<4; i++)
    tilec[i] = GetColor(cname[i]);

  /* graphics context */
  gc = XCreateGC(dpy, root, 0, NULL);
  XSetState(dpy, gc, fore, back, GXcopy, AllPlanes);

  /* select the tile patterns */
  if (solid)
  {
    TileBits[0] = zerobits;
    TileBits[1] = solidbits;
    TileBits[2] = solidbits;
    TileBits[3] = solidbits;
  }
  else
  {
    TileBits[0] = zerobits;
    TileBits[1] = onebits;
    TileBits[2] = twobits;
    TileBits[3] = threebits;
  }

  /* build the tiles */
  for (i=0; i<4; i++)
    tilep[i] = MakePixmap(TileBits[i], TileSize, TileSize, tilec[i], back);

  /* fonts */
  font = XLoadQueryFont(dpy, fntname);
  if (font == NULL)
  {
    sprintf(buf, "unable to load font \"%s\"", fntname);
    fatal(buf);
  }

  swidth  = XTextWidth(font, MaxPossibleScore, strlen(MaxPossibleScore));
  swidth += TileSize + (gwidth*4);

  /* main window */
  w  = (gwidth*4) + (bwidth*2) + (TileSize*BoardSize);
  h  = (gwidth*4) + (bwidth*2) + (TileSize*BoardSize);
  w += gwidth + swidth;
  h += gwidth + font->ascent + font->descent;
  mainw = GetWindow(root, 0, 0, w, h, bwidth);
  SetCursor(mainw, MainCurs);
  XSelectInput(dpy, mainw, PointerMotionMask|KeyPressMask|ExposureMask);
  AddEventHandler(mainw, MotionNotify, doMotion);
  AddEventHandler(mainw, KeyPress, doKey);
  AddEventHandler(mainw, Expose, doMainExpose);

  /* playing board window */
  x = gwidth;
  y = gwidth;
  w = (gwidth*2) + (TileSize*BoardSize);
  h = (gwidth*2) + (TileSize*BoardSize);
  boardw = GetWindow(mainw, x, y, w, h, bwidth);
  SetCursor(boardw, BoardCurs);
  XSelectInput(dpy, boardw, ExposureMask|ButtonPressMask);
  AddEventHandler(boardw, Expose, doBoardExpose);
  AddEventHandler(boardw, ButtonPress, doButtonPress);
  XMapWindow(dpy, boardw);

  /* help window */
  w = (TileSize*(BoardSize-6)) + (gwidth*2);
  h = NHelpLines*(font->ascent + font->descent) + (gwidth*2);
  x = ((TileSize*BoardSize + gwidth*2) - (w + bwidth*2)) / 2;
  y = ((TileSize*BoardSize + gwidth*2) - (h + bwidth*2)) / 2;
  helpw = GetWindow(boardw, x, y, w, h, bwidth);
  XSelectInput(dpy, helpw, ExposureMask);
  AddEventHandler(helpw, Expose, doHelpExpose);

  /* if we're in "expert" mode, don't map the silly help window initially */
  if (expert)
  {
    helpvis = False;
  }
  else
  {
    XMapWindow(dpy, helpw);
    helpvis = True;
  }

  /* next piece window */
  x = (gwidth*4) + (bwidth*2) + (TileSize*BoardSize);
  y = gwidth;
  w = (gwidth*2) + (TileSize*2);
  h = (gwidth*2) + (TileSize*2);
  nextw = GetWindow(mainw, x, y, w, h, bwidth);
  XSelectInput(dpy, nextw, ExposureMask);
  AddEventHandler(nextw, Expose, doNextExpose);
  XMapWindow(dpy, nextw);

  /* scores window */
  hswidth  = XTextWidth(font, HighScoreTitle, strlen(HighScoreTitle));
  hswidth += 2*gwidth;
  w  = font->max_bounds.width * MaxUnameLen;
  w += XTextWidth(font, MaxPossibleScore, strlen(MaxPossibleScore));
  w += 3*gwidth;
  if (hswidth > w)
    w = hswidth;
  else
    hswidth = w;
  h = (NHighScores+2)*(font->ascent + font->descent) + 2*gwidth;
  scoresw = GetWindow(root, 0, 0, w, h, bwidth);
  SetCursor(scoresw, MainCurs);
  XSelectInput(dpy, scoresw, KeyPressMask|ExposureMask);
  AddEventHandler(scoresw, KeyPress, doKey);
  AddEventHandler(scoresw, Expose, doHighExpose);
  scoresvis = False;

  /* various magic incantations (window manager) */
  TopWindowStuff(mainw, MainWindowName);
  TopWindowStuff(scoresw, ScoresWindowName);

  /* map the main window */
  XMapWindow(dpy, mainw);
}


static void GetDisplayName(argc, argv)
     int    argc;
     char **argv;
{
  int i;

  /* get display name from command line */ 
  for (i=1; i<argc; i++)
    if (strcmp(argv[i], "-display") == 0)
    {
      i += 1;
      if (i < argc)
	dpyname = argv[i];
      else
	Help("no display arg provided");
    }
}


static void GetResourceOptions()
{
  char *tmp;

  tmp = XGetDefault(dpy, AppName, "Mono");
  if (tmp != NULL)
    mono = IsTrue(tmp);

  tmp = XGetDefault(dpy, AppName, "Solid");
  if (tmp != NULL)
    solid = IsTrue(tmp);

  tmp = XGetDefault(dpy, AppName, "Expert");
  if (tmp != NULL)
    expert = IsTrue(tmp);

  tmp = XGetDefault(dpy, AppName, "GutterWidth");
  if (tmp != NULL)
  {
    sscanf(tmp, "%d", &gwidth);
    if (gwidth < 0)
      fatal("gutterwidth must be non-negative");
  }
  
  tmp = XGetDefault(dpy, AppName, "BorderWidth");
  if (tmp != NULL)
  {
    sscanf(tmp, "%d", &bwidth);
    if (bwidth < 0)
      fatal("borderwidth must be non-negative");
  }
  
  tmp = XGetDefault(dpy, AppName, "Foreground");
  if (tmp != NULL)
    fgname = strdup(tmp);

  tmp = XGetDefault(dpy, AppName, "Background");
  if (tmp != NULL)
    bgname = strdup(tmp);

  tmp = XGetDefault(dpy, AppName, "BorderColor");
  if (tmp != NULL)
    bdname = strdup(tmp);

  tmp = XGetDefault(dpy, AppName, "Color0");
  if (tmp != NULL)
    cname[0] = strdup(tmp);

  tmp = XGetDefault(dpy, AppName, "Color1");
  if (tmp != NULL)
    cname[1] = strdup(tmp);

  tmp = XGetDefault(dpy, AppName, "Color2");
  if (tmp != NULL)
    cname[2] = strdup(tmp);

  tmp = XGetDefault(dpy, AppName, "Color3");
  if (tmp != NULL)
    cname[3] = strdup(tmp);

  tmp = XGetDefault(dpy, AppName, "Font");
  if (tmp != NULL)
    fntname = strdup(tmp);
}


static int IsTrue(s)
     char *s;
{
  return ((strcasecmp(s, "true") == 0) ||
	  (strcasecmp(s, "on") == 0) ||
	  (strcasecmp(s, "yes") == 0));
}


static char *strdup(s)
     char *s;
{
  char *rslt;

  rslt = (char *) malloc(sizeof(char) * (strlen(s) + 1));
  if (rslt == NULL)
    fatal("unable to malloc");

  strcpy(rslt, s);

  return rslt;
}


static void GetCmdLineOptions(argc, argv)
     int    argc;
     char **argv;
{
  int i;

  /* get options from command line */
  for (i=1; i<argc; i++)
    if (strcmp(argv[i], "-display") == 0)
    {
      /* already got the display name */
      i += 1;
    }
    else if (strcmp(argv[i], "-expert") == 0)
    {
      /* run in "expert" mode */
      expert = True;
    }
    else if (strcmp(argv[i], "-color") == 0)
    {
      /* try to use colors */
      mono = False;
    }
    else if (strcmp(argv[i], "-mono") == 0)
    {
      /* skip the colors */
      mono = True;
    }
    else if (strcmp(argv[i], "-patterns") == 0)
    {
      /* use patterned tiles */
      solid = False;
    }
    else if (strcmp(argv[i], "-solid") == 0)
    {
      /* use solid tiles */
      solid = True;
    }
    else if (strcmp(argv[i], "-gw") == 0)
    {
      /* get gutter width from next arg */
      i += 1;
      if (i < argc)
	sscanf(argv[i], "%d", &gwidth);
      else
	Help("no gutterwidth arg provided");

      /* ensure the gutter width is non-negative */
      if (gwidth < 0)
	fatal("gutterwidth must be non-negative");
    }
    else if (strcmp(argv[i], "-bw") == 0)
    {
      /* get border width from next arg */
      i += 1;
      if (i < argc)
	sscanf(argv[i], "%d", &bwidth);
      else
	Help("no borderwidth arg provided");

      /* ensure the border width is non-negative */
      if (bwidth < 0)
	fatal("borderwidth must be non-negative");
    }
    else if (strcmp(argv[i], "-fg") == 0)
    {
      /* get foreground color name from next arg */
      i += 1;
      if (i < argc)
	fgname = argv[i];
      else
	Help("no foreground arg provided");
    }
    else if (strcmp(argv[i], "-bg") == 0)
    {
      /* get background color name from next arg */
      i += 1;
      if (i < argc)
	bgname = argv[i];
      else
	Help("no background arg provided");
    }
    else if (strcmp(argv[i], "-bd") == 0)
    {
      /* get border color name from next arg */
      i += 1;
      if (i < argc)
	bdname = argv[i];
      else
	Help("no bordercolor arg provided");
    }
    else if (strcmp(argv[i], "-c0") == 0)
    {
      /* get color 0 name from next arg */
      i += 1;
      if (i < argc)
	cname[0] = argv[i];
      else
	Help("no color0 arg provided");
    }
    else if (strcmp(argv[i], "-c1") == 0)
    {
      /* get color 1 name from next arg */
      i += 1;
      if (i < argc)
	cname[1] = argv[i];
      else
	Help("no color1 arg provided");
    }
    else if (strcmp(argv[i], "-c2") == 0)
    {
      /* get color 2 name from next arg */
      i += 1;
      if (i < argc)
	cname[2] = argv[i];
      else
	Help("no color2 arg provided");
    }
    else if (strcmp(argv[i], "-c3") == 0)
    {
      /* get color 3 name from next arg */
      i += 1;
      if (i < argc)
	cname[3] = argv[i];
      else
	Help("no color3 arg provided");
    }
    else if (strcmp(argv[i], "-fn") == 0)
    {
      /* get font name from next arg */
      i += 1;
      if (i < argc)
	fntname = argv[i];
      else
	Help("no font arg provided");
    }
    else if (strcmp(argv[i], "-help") == 0)
    {
      Help(NULL);
    }
    else
    {
      sprintf(buf, "unknown option: %s", argv[i]);
      Help(buf);
    }
}


static void Help(msg)
     char *msg;
{
  if (msg != NULL)
    fprintf(stderr, "\n%s\n", msg);
  else
    fprintf(stderr, "\n%s (v %d.%d)\n", AppName, MajorVersion, MinorVersion);

  fprintf(stderr, "\ncommand line options:\n");
  fprintf(stderr, "  -help           print this message\n");
  fprintf(stderr, "  -display <d>    use display <d>\n");
  fprintf(stderr, "  -expert         don't show quick help initially\n");
  fprintf(stderr, "  -color          run in color mode\n");
  fprintf(stderr, "  -mono           run in mono mode\n");
  fprintf(stderr, "  -patterns       use patterned tiles\n");
  fprintf(stderr, "  -solid          use solid tiles\n");
  fprintf(stderr, "  -gw <n>         use gutter width of <n> pixels\n");
  fprintf(stderr, "  -bw <n>         use border width of <n> pixels\n");
  fprintf(stderr, "  -fg <c>         use <c> as forground color\n");
  fprintf(stderr, "  -bg <c>         use <c> as background color\n");
  fprintf(stderr, "  -bd <c>         use <c> as border color\n");
  fprintf(stderr, "  -c0 <c>         use <c> as board color 0\n");
  fprintf(stderr, "  -c1 <c>         use <c> as board color 1\n");
  fprintf(stderr, "  -c2 <c>         use <c> as board color 2\n");
  fprintf(stderr, "  -c3 <c>         use <c> as board color 3\n");
  fprintf(stderr, "  -fn <f>         use font <f> for drawing text\n");

  fprintf(stderr, "\nSee the man page for more info.\n\n");

  exit(1);
}


void MainLoop()
{
  XEvent xev;
  void (*handler)();

  while (1)
  {
    XNextEvent(dpy, &xev);
    handler = GetEventHandler(&xev);
    if (handler != NULL) handler(&xev);
  }
}


void drawBoard()
{
  int r, c;

  /* redraw the tiles */
  for (c=0; c<BoardSize; c++)
    for (r=0; r<BoardSize; r++)
      drawTile(r, c);
  
  /* redraw controller */
  drawController();
}


void drawHelp()
{
  /*
   * mosaic quick help
   * 
   * q  quit
   * s  toggle high scores window visibility
   * h  toggle help window visibility
   * n  new game (only enabled when current game is completed)
   * a  automatic -- randomly place any remaining tiles
   * 
   * try `man mosaic' for further info
   * press `h' to hide this window
   */

  int   fontsize;
  int   x, y;
  int   len;
  char *txt;
  
  fontsize = font->ascent + font->descent;

  x   = 10;
  y   = font->ascent + gwidth;

  y  += fontsize/2;
  txt = "mosaic quick help";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x, y, txt, len);
  y += fontsize/2;
  y += fontsize;

  txt = "q";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x, y, txt, len);

  txt = "quit";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x+2*font->max_bounds.width, y, txt, len);
  y += fontsize;

  txt = "s";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x, y, txt, len);
  
  txt = "toggle high scores window";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x+2*font->max_bounds.width, y, txt, len);
  y += fontsize;

  txt = "h";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x, y, txt, len);

  txt = "toggle help window";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x+2*font->max_bounds.width, y, txt, len);
  y += fontsize;

  txt = "n";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x, y, txt, len);

  txt = "new game";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x+2*font->max_bounds.width, y, txt, len);
  y += fontsize;

  txt = "a";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x, y, txt, len);

  txt = "automatic";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x+2*font->max_bounds.width, y, txt, len);
  y += fontsize;

  y += fontsize/2;
  txt = "try `man mosaic' for further info";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x, y, txt, len);
  y += fontsize;

  txt = "(press `h' to hide this window)";
  len = strlen(txt);
  XDrawString(dpy, helpw, gc, x, y, txt, len);
  y += fontsize;
  y += fontsize/2;
}


void drawTile(r, c)
     int r, c;			/* tile position */
{
  int x, y;

  x = c * TileSize + gwidth;
  y = r * TileSize + gwidth;
  
  XCopyArea(dpy, tilep[tile[r*BoardSize+c]], boardw, gc,
	    0, 0, TileSize, TileSize, x, y);
}


void drawController()
{
  int x, y;
  
  x = ccol * TileSize + gwidth;
  y = crow * TileSize + gwidth;
  
  XDrawRectangle(dpy, boardw, gc, x, y,
		 TileSize*2-1, TileSize*2-1);
  XDrawRectangle(dpy, boardw, gc, x+1, y+1,
		 TileSize*2-3, TileSize*2-3);
}


void drawNext()
{
  int p;
  
  p = (nextpiece < NPieces) ? piece[nextpiece] : 0;
  
  XCopyArea(dpy, tilep[p&0x03], nextw, gc, 0, 0, TileSize, TileSize,
	    gwidth, gwidth);
  p >>= 2;
  XCopyArea(dpy, tilep[p&0x03], nextw, gc, 0, 0, TileSize, TileSize,
	    gwidth+TileSize, gwidth);
  p >>= 2;
  XCopyArea(dpy, tilep[p&0x03], nextw, gc, 0, 0, TileSize, TileSize,
	    gwidth, gwidth+TileSize);
  p >>= 2;
  XCopyArea(dpy, tilep[p&0x03], nextw, gc, 0, 0, TileSize, TileSize,
	    gwidth+TileSize, gwidth+TileSize);
}


void drawTitle()
{
  int   x, y;
  int   len;
  char *txt;

  x   = (gwidth*2) + (bwidth) + (TileSize*BoardSize/2);
  y   = (gwidth*4) + (bwidth*2) + (TileSize*BoardSize);
  txt = AppName;

  len = strlen(txt);
  x  -= XTextWidth(font, txt, len) / 2;
  y  += font->ascent;

  XSetFont(dpy, gc, font->fid);
  XDrawString(dpy, mainw, gc, x, y, txt, len);
}


void drawScore()
{
  int   i, total;
  int   top, left;
  int   xtra, len;
  int   x, y;
  char *txt;
  char  buf[16];

  XSetFont(dpy, gc, font->fid);

  left = (gwidth*5) + (bwidth*2) + (TileSize*BoardSize);
  top  = (gwidth*4) + (TileSize*4);
  xtra = (TileSize - (font->ascent + font->descent)) / 2;

  txt = "to play";
  len = strlen(txt);
  x   = left + (swidth - XTextWidth(font, txt, len)) / 2;
  y   = top + xtra + font->ascent;
  XDrawString(dpy, mainw, gc, x, y, txt, len);
  top += TileSize + gwidth;

  for (i=0; i<3; i++)
  {
    XClearArea(dpy, mainw, left, top, swidth, TileSize, False);
    XCopyArea(dpy, tilep[i+1], mainw, gc, 0, 0,
	      TileSize, TileSize, left, top);
    sprintf(buf, "%d", remain[i]);
    len = strlen(buf);
    x   = left + swidth - gwidth - XTextWidth(font, buf, len);
    y   = top + xtra + font->ascent;
    XDrawString(dpy, mainw, gc, x, y, buf, len);
    top += TileSize + gwidth;
  }
  top += TileSize + gwidth;

  txt = "total";
  len = strlen(txt);
  x   = left + (swidth - XTextWidth(font, txt, len)) / 2;
  y   = top + xtra + font->ascent;
  XDrawString(dpy, mainw, gc, x, y, txt, len);
  top += TileSize + gwidth;

  total = 0;
  for (i=0; i<3; i++)
  {
    total += tscore[i];
    XClearArea(dpy, mainw, left, top, swidth, TileSize, False);
    XCopyArea(dpy, tilep[i+1], mainw, gc, 0, 0,
	      TileSize, TileSize, left, top);
    sprintf(buf, "%d", tscore[i]);
    len = strlen(buf);
    x   = left + swidth - gwidth - XTextWidth(font, buf, len);
    y   = top + xtra + font->ascent;
    XDrawString(dpy, mainw, gc, x, y, buf, len);
    top += TileSize + gwidth;
  }

  XClearArea(dpy, mainw, left, top, swidth, TileSize, False);
  sprintf(buf, "%d", total);
  len = strlen(buf);
  x   = left + swidth - gwidth - XTextWidth(font, buf, len);
  y   = top + xtra + font->ascent;
  XDrawString(dpy, mainw, gc, x, y, buf, len);
  top += TileSize + gwidth;
  top += TileSize + gwidth;

  txt = "piece";
  len = strlen(txt);
  x   = left + (swidth - XTextWidth(font, txt, len)) / 2;
  y   = top + xtra + font->ascent;
  XDrawString(dpy, mainw, gc, x, y, txt, len);
  top += TileSize + gwidth;

  total = 0;
  for (i=0; i<3; i++)
  {
    total += pscore[i];
    XClearArea(dpy, mainw, left, top, swidth, TileSize, False);
    XCopyArea(dpy, tilep[i+1], mainw, gc, 0, 0,
	      TileSize, TileSize, left, top);
    sprintf(buf, "%d", pscore[i]);
    len = strlen(buf);
    x   = left + swidth - gwidth - XTextWidth(font, buf, len);
    y   = top + xtra + font->ascent;
    XDrawString(dpy, mainw, gc, x, y, buf, len);
    top += TileSize + gwidth;
  }

  XClearArea(dpy, mainw, left, top, swidth, TileSize, False);
  sprintf(buf, "%d", total);
  len = strlen(buf);
  x   = left + swidth - gwidth - XTextWidth(font, buf, len);
  y   = top + xtra + font->ascent;
  XDrawString(dpy, mainw, gc, x, y, buf, len);
  top += TileSize + gwidth;
}


void drawHighScores()
{
  int   i;
  int   fontsize;
  int   x, y;
  int   len;
  char *txt;

  fontsize = font->ascent + font->descent;

  txt = HighScoreTitle;
  len = strlen(txt);
  x   = (hswidth - XTextWidth(font, txt, len)) / 2;
  y   = fontsize/2 + font->ascent + gwidth;
  XDrawString(dpy, scoresw, gc, x, y, txt, len);

  y = fontsize*2 + gwidth;
  for (i=0; i<NHighScores; i++)
    if (highscore[i].score > 0)
    {
      XClearArea(dpy, scoresw, 0, y, hswidth, fontsize, False);
      y += font->ascent;
      
      txt = highscore[i].uname;
      len = strlen(txt);
      XDrawString(dpy, scoresw, gc, gwidth*2, y, txt, len);
      
      sprintf(buf, "%d", highscore[i].score);
      len = strlen(buf);
      x   = hswidth - gwidth*2 - XTextWidth(font, buf, len);
      XDrawString(dpy, scoresw, gc, x, y, buf, len);
      
      y += font->descent;
    }
}


void drawAll()
{
  drawBoard();
  drawHelp();
  drawNext();
  drawTitle();
  drawScore();
  drawHighScores();
}


static void doKey(xev)
     XEvent *xev;
{
  if (XLookupString((XKeyEvent *) xev, buf, BufSize, NULL, NULL) > 0)
    switch (buf[0])
    {
    case 'q':
    case 'Q':
      QuitGame();
      break;

    case 'n':
    case 'N':
    case '\n':
    case '\r':
      if (nextpiece == NPieces)
      {
	InitGame();
	drawAll();
      }
      break;

    case 's':
    case 'S':
      if (scoresvis)
      {
	XUnmapWindow(dpy, scoresw);
	scoresvis = False;
      }
      else
      {
	XMapWindow(dpy, scoresw);
	XRaiseWindow(dpy, scoresw);
	scoresvis = True;
      }
      break;

    case 'h':
    case 'H':
      if (helpvis)
      {
	XUnmapWindow(dpy, helpw);
	helpvis = False;
      }
      else
      {
	XMapWindow(dpy, helpw);
	helpvis = True;
      }
      break;

    case 'a':
    case 'A':
      AutoPlay();
      break;

    case '\f':
      drawAll();
      break;
    }
}


static void doMotion(xev)
     XEvent *xev;
{
  int    x, y;
  Window dummyw;
  int    row, col;
  int    oldcr, oldcc;
  int    redraw;
  
  /* motion event compression */
  while (XCheckTypedEvent(dpy, MotionNotify, xev));

  /* convert to boardw coordinates */
  XTranslateCoordinates(dpy, xev->xmotion.window, boardw,
			xev->xmotion.x, xev->xmotion.y,
			&x, &y, &dummyw);

  /* convert (x, y) to (row, col) */
  row = (y - gwidth) / TileSize;
  BoundVarVal(row, 0, (BoardSize-1));
  col = (x - gwidth) / TileSize;
  BoundVarVal(col, 0, (BoardSize-1));

  /* check if redraw is necessary */
  oldcr  = crow;
  oldcc  = ccol;
  redraw = False;
  if (row < crow)
  {
    crow   = row;
    redraw = True;
  }
  else if ((row - 1) > crow)
  {
    crow   = row - 1;
    redraw = True;
  }
  if (col < ccol)
  {
    ccol   = col;
    redraw = True;
  }
  else if ((col - 1) > ccol)
  {
    ccol   = col - 1;
    redraw = True;
  }

  /* if necessary, redraw */
  if (redraw)
  {
    /* redraw tiles */
    drawTile(oldcr++, oldcc);
    drawTile(oldcr, oldcc++);
    drawTile(oldcr--, oldcc);
    drawTile(oldcr, oldcc--);

    /* draw controller at (crow, ccol) */
    drawController();
  }
}


static void doBoardExpose(xev)
     XEvent *xev;
{
  /* exposure event compression */
  while (XCheckTypedWindowEvent(dpy, boardw, Expose, xev));

  drawBoard();
}


static void doHelpExpose(xev)
     XEvent *xev;
{
  while (XCheckTypedWindowEvent(dpy, helpw, Expose, xev));

  drawHelp();
}


static void doNextExpose(xev)
     XEvent *xev;
{
  /* exposure event compression */
  while (XCheckTypedWindowEvent(dpy, nextw, Expose, xev));

  drawNext();
}


static void doMainExpose(xev)
     XEvent *xev;
{
  /* exposure event compression */
  while (XCheckTypedWindowEvent(dpy, mainw, Expose, xev));

  drawTitle();
  drawScore();
}


static void doHighExpose(xev)
     XEvent *xev;
{
  /* exposure event compression */
  while (XCheckTypedWindowEvent(dpy, scoresw, Expose, xev));

  drawHighScores();
}


static void doButtonPress(xev)
     XEvent *xev;
{
  if (helpvis)
  {
    XBell(dpy, 0);
  }
  else if ((nextpiece < NPieces) &&
	   (DropPiece(crow, ccol, piece[nextpiece])))
  {
    nextpiece += 1;
    drawNext();

    if (nextpiece == NPieces)
      CheckHighScore();
  }
}


static Pixel GetColor(name)
     char *name;
{
  XColor xc;
  
  if (XParseColor(dpy, cmap, name, &xc) == 0)
  {
    sprintf(buf, "unable to parse color \"%s\"", name);
    fatal(buf);
  }

  if (XAllocColor(dpy, cmap, &xc) == 0)
    fatal("unable to alloc color");

  return xc.pixel;
}


static Window GetWindow(parent, x, y, w, h, b)
     Window parent;
     int    x, y;		/* position */
     int    w, h;		/* dimensions */
     int    b;			/* border width */
{
  return XCreateSimpleWindow(dpy, parent, x, y, w, h, b, brdr, back);
}


static Pixmap MakePixmap(bits, w, h, fg, bg)
     char *bits;
     int   w, h;
     Pixel fg, bg;
{
  return XCreatePixmapFromBitmapData(dpy, root, bits, w, h, fg, bg, dpth);
}


static void SetCursor(w, idx)
     Window w;
     int    idx;
{
  Cursor curs;
  XColor fg, bg;

  curs = XCreateFontCursor(dpy, idx);

  if (XParseColor(dpy, cmap, fgname, &fg) == 0)
  {
    sprintf(buf, "unable to parse color \"%s\"", fgname);
    fatal(buf);
  }
  if (XParseColor(dpy, cmap, bgname, &bg) == 0)
  {
    sprintf(buf, "unable to parse color \"%s\"", bgname);
    fatal(buf);
  }

  XRecolorCursor(dpy, curs, &fg, &bg);
  XDefineCursor(dpy, w, curs);
}


static void TopWindowStuff(w, name)
     Window w;
     char  *name;
{
  XWindowAttributes xwa;
  XSizeHints       *xsh;

  XGetWindowAttributes(dpy, w, &xwa);
  
  xsh = XAllocSizeHints();
  xsh->width       = xwa.width;
  xsh->height      = xwa.height;
  xsh->min_width   = xwa.width;
  xsh->min_height  = xwa.height;
  xsh->max_width   = xwa.width;
  xsh->max_height  = xwa.height;
  xsh->base_width  = xwa.width;
  xsh->base_height = xwa.height;
  xsh->flags       = (PSize|PMinSize|PMaxSize|PBaseSize);

  XSetWMNormalHints(dpy, w, xsh);
  XStoreName(dpy, w, name);
  XSetIconName(dpy, w, name);

  XFree((char *) xsh);
}


static void AddEventHandler(w, type, proc)
     Window w;
     int    type;
     void (*proc)();
{
  if (XSaveContext(dpy, w,
		   (XContext) type,
		   (caddr_t) proc) != 0)
    fatal("unable to XSaveContext");
}


static void (*GetEventHandler(xev))()
     XEvent *xev;
{
  void (*rslt)();
  
  if (XFindContext(dpy, xev->xany.window,
		   (XContext) xev->xany.type,
		   (caddr_t *) &rslt) != 0)
    rslt = NULL;

  return rslt;
}
