/* xscreensaver, Copyright (c) 1997-2006 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 */

/*
  TODO:

    =  Rather than just flickering the pieces before swapping them,
       show them lifting up and moving to their new positions.
       The path on which they move shouldn't be a straight line;
       try to avoid having them cross each other by moving them in
       oppositely-positioned arcs.

    =  Rotate the pieces as well, so that we can swap the corner
       and edge pieces with each other.

    =  Have it drop all pieces to the "floor" then pick them up to
       reassemble the picture.

    =  As a joke, maybe sometimes have one piece that doesn't fit?
       Or lose a piece?
 */

#include "screenhack.h"
#include "spline.h"

#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))

#define CENTER	  0
#define NORTH	  1
#define NORTHEAST 2
#define EAST	  3
#define SOUTHEAST 4
#define SOUTH	  5
#define SOUTHWEST 6
#define WEST	  7
#define NORTHWEST 8

struct piece {
  int width, height;
  int x, y;
  Pixmap pixmap;
};

struct set {
  struct piece pieces[9];
};

#define PIECE_A_HOLLOW 0
#define PIECE_A_FILLED 1
#define PIECE_B_HOLLOW 2
#define PIECE_B_FILLED 3

struct swap_state {
  int flashing;
  int x1, y1, x2, y2;
  Bool draw_p;
};

struct state {
  Display *dpy;
  Window window;

  struct set all_pieces[4];

  int piece_width, piece_height;
  int width, height;
  int x_border, y_border;
  Pixmap source;
  GC gc;
  int fg, bg;
  int border_width;
  XPoint *state;
  int delay, delay2;

  int jigstate;

  struct swap_state swap;
  int clearing;

  async_load_state *img_loader;
};


/* Returns a spline describing one edge of a puzzle piece of the given length.
 */
static spline *
make_puzzle_curve (int pixels)
{
  double x0 = 0.0000, y0 =  0.0000;
  double x1 = 0.3333, y1 =  0.1000;
  double x2 = 0.4333, y2 =  0.0333;
  double x3 = 0.4666, y3 = -0.0666;
  double x4 = 0.3333, y4 = -0.1666;
  double x5 = 0.3666, y5 = -0.2900;
  double x6 = 0.5000, y6 = -0.3333;

  spline *s = make_spline(20);
  s->n_controls = 0;

# define PT(x,y) \
    s->control_x[s->n_controls] = pixels * (x); \
    s->control_y[s->n_controls] = pixels * (y); \
    s->n_controls++
  PT (  x0, y0);
  PT (  x1, y1);
  PT (  x2, y2);
  PT (  x3, y3);
  PT (  x4, y4);
  PT (  x5, y5);
  PT (  x6, y6);
  PT (1-x5, y5);
  PT (1-x4, y4);
  PT (1-x3, y3);
  PT (1-x2, y2);
  PT (1-x1, y1);
  PT (1-x0, y0);
# undef PT

  compute_spline (s);
  return s;
}


/* Draws a puzzle piece.  The top/right/bottom/left_type args
   indicate the direction the tabs point: 1 for out, -1 for in, 0 for flat.
 */
static void
draw_puzzle_shape (Display *dpy, Drawable d, GC gc,
                   int x, int y, int size, int bw,
                   int top_type, int right_type,
                   int bottom_type, int left_type,
                   Bool fill_p)
{
  spline *s = make_puzzle_curve (size);
  XPoint *pts = (XPoint *) malloc (s->n_points * 4 * sizeof(*pts));
  int i, o;

  /* The border is twice as wide for "flat" edges, otherwise it looks funny. */
  if (fill_p) 
    bw = 0;
  else
    bw /= 2;

  o = 0;
  if (top_type == 0) {
    pts[o].x = x;        pts[o].y = y + bw; o++;
    pts[o].x = x + size; pts[o].y = y + bw; o++;
  } else {
    for (i = 0; i < s->n_points; i++) {
      pts[o].x = x + s->points[i].x;
      pts[o].y = y + s->points[i].y * top_type;
      o++;
    }
  }

  if (right_type == 0) {
    pts[o-1].x -= bw;
    pts[o].x = x + size - bw; pts[o].y = y + size; o++;
  } else {
    for (i = 1; i < s->n_points; i++) {
      pts[o].x = x + size + s->points[i].y * (-right_type);
      pts[o].y = y        + s->points[i].x;
      o++;
    }
  }

  if (bottom_type == 0) {
    pts[o-1].y -= bw;
    pts[o].x = x; pts[o].y = y + size - bw; o++;
  } else {
    for (i = 1; i < s->n_points; i++) {
      pts[o].x = x        + s->points[s->n_points-i-1].x;
      pts[o].y = y + size + s->points[s->n_points-i-1].y * (-bottom_type);
      o++;
    }
  }

  if (left_type == 0) {
    pts[o-1].x += bw;
    pts[o].x = x + bw; pts[o].y = y; o++;
  } else {
    for (i = 1; i < s->n_points; i++) {
      pts[o].x = x + s->points[s->n_points-i-1].y * left_type;
      pts[o].y = y + s->points[s->n_points-i-1].x;
      o++;
    }
  }

  free_spline (s);

  if (fill_p)
    XFillPolygon (dpy, d, gc, pts, o, Complex, CoordModeOrigin);
  else
    XDrawLines (dpy, d, gc, pts, o, CoordModeOrigin);

  free (pts);
}


/* Creates two pixmaps for a puzzle piece:
   - The first is a solid bit-mask with 1 for each pixel inside the piece;
   - The second is an outline of the piece, where all drawn pixels are
     contained within the mask.

   The top/right/bottom/left_type args indicate the direction the
   tabs point: 1 for out, -1 for in, 0 for flat.

   Size is how big the piece should be, from origin to origin.

   Returned x/y is the origin within the pixmaps.
 */
static void
make_puzzle_pixmap_pair (Display *dpy, Drawable d, int size, int bw,
                         int top_type, int right_type, 
                         int bottom_type, int left_type,
                         int *x_ret, int *y_ret,
                         Pixmap *mask_ret, Pixmap *outline_ret)
{
  int w = size * 3;
  int h = w;
  int x = size;
  int y = size;
  Pixmap p0 = XCreatePixmap (dpy, d, w, h, 1);
  Pixmap p1 = XCreatePixmap (dpy, d, w, h, 1);
  XGCValues gcv;
  GC gc;
  gcv.foreground = 0;
  gcv.background = 0;
  gc = XCreateGC (dpy, p0, GCForeground|GCBackground, &gcv);
  XFillRectangle (dpy, p0, gc, 0, 0, w, h);
  XFillRectangle (dpy, p1, gc, 0, 0, w, h);
  XSetForeground (dpy, gc, 0);

# ifdef HAVE_COCOA
  jwxyz_XSetAlphaAllowed (dpy, gc, False);
# endif

  /* To ensure that each pixel is drawn only once, we render the piece
     such that it "owns" the left and top edges, but not the right and
     bottom edges.

         - - +      "#" is this piece.
         - # +      It overlaps "-" and is overlapped by "+".
         - + +

     To accomplish this, we clear to black, draw "#" in white,
     then draw "+" in black.
   */

  /* Center square */
  XSetForeground (dpy, gc, 1);
  draw_puzzle_shape (dpy, p0, gc, x, y, size, bw,
                     top_type, right_type, bottom_type, left_type,
                     True);

  /* Top right square */
  XSetForeground (dpy, gc, 0);
  draw_puzzle_shape (dpy, p0, gc, x + size, y - size, size, bw,
                     0, 0, -top_type, -left_type,
                     True);

  /* Center right square */
  draw_puzzle_shape (dpy, p0, gc, x + size, y, size, bw,
                     0, 0, 0, -right_type,
                     True);

  /* Bottom center square */
  draw_puzzle_shape (dpy, p0, gc, x, y + size, size, bw,
                     -bottom_type, 0, 0, 0,
                     True);

  /* And Charles Nelson Reilly in the bottom right square */
  draw_puzzle_shape (dpy, p0, gc, x + size, y + size, size, bw,
                     -bottom_type, -right_type, 0, 0,
                     True);

  /* Done with p0 (the mask).
     To make p1 (the outline) draw an outlined piece through the mask.
   */
  if (bw < 0)
    {
      bw = size / 30;
      if (bw < 1) bw = 1;
    }

  if (bw > 0)
    {
      XSetForeground (dpy, gc, 1);
      XSetClipMask (dpy, gc, p0);
      XSetLineAttributes (dpy, gc, bw, LineSolid, CapButt, JoinRound);
      draw_puzzle_shape (dpy, p1, gc, x, y, size, bw,
                         top_type, right_type, bottom_type, left_type,
                         False);
    }

  XFreeGC (dpy, gc);
  *x_ret = x;
  *y_ret = x;
  *mask_ret = p0;
  *outline_ret = p1;
}


static void
make_puzzle_pixmaps (struct state *st)
{
  int i, j;

  int edges[9][4] = {
    { -1,  1, -1, 1 },	/* CENTER    */
    {  0,  1, -1, 1 },	/* NORTH     */
    {  0,  0, -1, 1 },	/* NORTHEAST */
    { -1,  0, -1, 1 },	/* EAST      */
    { -1,  0,  0, 1 },	/* SOUTHEAST */
    { -1,  1,  0, 1 },	/* SOUTH     */
    { -1,  1,  0, 0 },	/* SOUTHWEST */
    { -1,  1, -1, 0 },	/* WEST      */
    {  0,  1, -1, 0 },	/* NORTHWEST */
  };

  /* sometimes swap direction of horizontal edges */
  if (random() & 1)
    for (j = 0; j < countof(edges); j++) {
      edges[j][0] = -edges[j][0];
      edges[j][2] = -edges[j][2];
    }

  /* sometimes swap direction of vertical edges */
  if (random() & 1)
    for (j = 0; j < countof(edges); j++) {
      edges[j][1] = -edges[j][1];
      edges[j][3] = -edges[j][3];
    }

  for (j = 0; j < 9; j++) {
    for (i = 0; i < 2; i++) {
      int x, y;
      int top, right, bottom, left;
      Pixmap mask, outline;
      top    = edges[j][0];
      right  = edges[j][1];
      bottom = edges[j][2];
      left   = edges[j][3];
      if (i) {
        top    = -top; 
        right  = -right; 
        bottom = -bottom; 
        left   = -left;
      }
      make_puzzle_pixmap_pair (st->dpy, st->window, st->piece_width,
                               st->border_width,
                               top, right, bottom, left,
                               &x, &y, &mask, &outline);

      st->all_pieces[i*2].pieces[j].x = x;
      st->all_pieces[i*2].pieces[j].y = y;
      st->all_pieces[i*2].pieces[j].pixmap = outline;

      st->all_pieces[i*2+1].pieces[j].x = x;
      st->all_pieces[i*2+1].pieces[j].y = y;
      st->all_pieces[i*2+1].pieces[j].pixmap = mask;
    }
  }
}

static void
free_puzzle_pixmaps (struct state *st)
{
  int i, j;
  for (i = 0; i < countof(st->all_pieces); i++)
    for (j = 0; j < countof (st->all_pieces[i].pieces); j++)
      if (st->all_pieces[i].pieces[j].pixmap) {
        XFreePixmap (st->dpy, st->all_pieces[i].pieces[j].pixmap);
        st->all_pieces[i].pieces[j].pixmap = 0;
      }
}


static void
jigsaw_init_1 (struct state *st)
{
  XWindowAttributes xgwa;
  int x, y;
  XGCValues gcv;
  Colormap cmap;

  XGetWindowAttributes (st->dpy, st->window, &xgwa);

  st->piece_width = 40 + (random() % 100);
  if (xgwa.width / st->piece_width < 4)
    st->piece_width = xgwa.width / 4;
  st->piece_height = st->piece_width;

  free_puzzle_pixmaps (st);
  make_puzzle_pixmaps (st);

  cmap = xgwa.colormap;
  st->width  = xgwa.width  / st->piece_width;
  st->height = xgwa.height / st->piece_height;
  st->x_border = (xgwa.width  - (st->width  * st->piece_width)) / 2;
  st->y_border = (xgwa.height - (st->height * st->piece_width)) / 2;

  if (st->width  < 4) st->width  = 4, st->x_border = 0;
  if (st->height < 4) st->height = 4, st->y_border = 0;

  if (st->state) free (st->state);
  st->state = (XPoint *) malloc (st->width * st->height * sizeof(*st->state));

  if (!st->gc)
    {
      XColor fgc, bgc;
      char *fgs = get_string_resource(st->dpy, "foreground", "Foreground");
      char *bgs = get_string_resource(st->dpy, "background", "Background");
      Bool fg_ok, bg_ok;

      st->gc = XCreateGC (st->dpy, st->window, 0, &gcv);

# ifdef HAVE_COCOA
      jwxyz_XSetAlphaAllowed (st->dpy, st->gc, False);
# endif

      if (!XParseColor (st->dpy, cmap, fgs, &fgc))
        XParseColor (st->dpy, cmap, "gray", &fgc);
      if (!XParseColor (st->dpy, cmap, bgs, &bgc))
        XParseColor (st->dpy, cmap, "black", &bgc);

      free (fgs);
      free (bgs);
      fgs = bgs = 0;

      fg_ok = XAllocColor (st->dpy, cmap, &fgc);
      bg_ok = XAllocColor (st->dpy, cmap, &bgc);

      /* If we weren't able to allocate the two colors we want from the
         colormap (which is likely if the screen has been grabbed on an
         8-bit SGI visual -- don't ask) then just go through the map
         and find the closest color to the ones we wanted, and use those
         pixels without actually allocating them.
      */
      if (fg_ok)
        st->fg = fgc.pixel;
      else
        st->fg = 0;

      if (bg_ok)
        st->bg = bgc.pixel;
      else
        st->bg = 1;

#ifndef HAVE_COCOA
      if (!fg_ok || bg_ok)
        {
          int i;
          unsigned long fgd = ~0;
          unsigned long bgd = ~0;
          int max = visual_cells (xgwa.screen, xgwa.visual);
          XColor *all = (XColor *) calloc(sizeof (*all), max);
          for (i = 0; i < max; i++)
            {
              all[i].flags = DoRed|DoGreen|DoBlue;
              all[i].pixel = i;
            }
          XQueryColors (st->dpy, cmap, all, max);
          for(i = 0; i < max; i++)
            {
              long rd, gd, bd;
              unsigned long d;
              if (!fg_ok)
                {
                  rd = (all[i].red   >> 8) - (fgc.red   >> 8);
                  gd = (all[i].green >> 8) - (fgc.green >> 8);
                  bd = (all[i].blue  >> 8) - (fgc.blue  >> 8);
                  if (rd < 0) rd = -rd;
                  if (gd < 0) gd = -gd;
                  if (bd < 0) bd = -bd;
                  d = (rd << 1) + (gd << 2) + bd;
                  if (d < fgd)
                    {
                      fgd = d;
                      st->fg = all[i].pixel;
                      if (d == 0)
                        fg_ok = True;
                    }
                }

              if (!bg_ok)
                {
                  rd = (all[i].red   >> 8) - (bgc.red   >> 8);
                  gd = (all[i].green >> 8) - (bgc.green >> 8);
                  bd = (all[i].blue  >> 8) - (bgc.blue  >> 8);
                  if (rd < 0) rd = -rd;
                  if (gd < 0) gd = -gd;
                  if (bd < 0) bd = -bd;
                  d = (rd << 1) + (gd << 2) + bd;
                  if (d < bgd)
                    {
                      bgd = d;
                      st->bg = all[i].pixel;
                      if (d == 0)
                        bg_ok = True;
                    }
                }

              if (fg_ok && bg_ok)
                break;
            }
          XFree(all);
        }
#endif /* HAVE_COCOA */
    }

  /* Reset the window's background color... */
  XSetWindowBackground (st->dpy, st->window, st->bg);
  XClearWindow(st->dpy, st->window);

  for (y = 0; y < st->height; y++)
    for (x = 0; x < st->width; x++)
      {
	st->state[y * st->width + x].x = x;
	st->state[y * st->width + x].y = y;
      }

  if (st->source)
    XFreePixmap (st->dpy, st->source);
  st->source = XCreatePixmap (st->dpy, st->window, xgwa.width, xgwa.height,
                              xgwa.depth);

  st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
                                            st->source, 0, 0);
}


static void
get_piece (struct state *st, 
           int x, int y, struct piece **hollow, struct piece **filled)
{
  int p;
  Bool which = (x & 1) == (y & 1);

  if      (x == 0           && y == 0)	          p = NORTHWEST;
  else if (x == st->width-1 && y == 0)	          p = NORTHEAST;
  else if (x == st->width-1 && y == st->height-1) p = SOUTHEAST;
  else if (x == 0           && y == st->height-1) p = SOUTHWEST;
  else if (y == 0)			          p = NORTH;
  else if (x == st->width-1)		          p = EAST;
  else if (y == st->height-1)		          p = SOUTH;
  else if (x == 0)			          p = WEST;
  else					          p = CENTER;

  if (hollow)
    *hollow = (which
	       ? &st->all_pieces[PIECE_A_HOLLOW].pieces[p]
	       : &st->all_pieces[PIECE_B_HOLLOW].pieces[p]);
  if (filled)
    *filled = (which
	       ? &st->all_pieces[PIECE_A_FILLED].pieces[p]
	       : &st->all_pieces[PIECE_B_FILLED].pieces[p]);
}


static void
draw_piece (struct state *st, int x, int y, int clear_p)
{
  struct piece *hollow, *filled;
  int from_x = st->state[y * st->width + x].x;
  int from_y = st->state[y * st->width + x].y;

  get_piece(st, x, y, &hollow, &filled);
	  
  XSetClipMask(st->dpy, st->gc, filled->pixmap);
  XSetClipOrigin(st->dpy, st->gc,
		 st->x_border + (x * st->piece_width) - filled->x - 1,
		 st->y_border + (y * st->piece_width) - filled->y - 1);

  if (clear_p)
    {
      XSetForeground(st->dpy, st->gc, st->bg);
      XFillRectangle(st->dpy, st->window, st->gc,
		     st->x_border + (x * st->piece_width)  -st->piece_width/2,
		     st->y_border + (y * st->piece_height) -st->piece_height/2,
		     st->piece_width*2, st->piece_height*2);
    }
  else
    XCopyArea(st->dpy, st->source, st->window, st->gc,
	      st->x_border + (from_x * st->piece_width)  - st->piece_width/2,
	      st->y_border + (from_y * st->piece_height) - st->piece_height/2,
	      st->piece_width*2, st->piece_height*2,
	      st->x_border + (x * st->piece_width)  - st->piece_width/2,
	      st->y_border + (y * st->piece_height) - st->piece_height/2);

  if (clear_p > 1)
    return;

  XSetForeground(st->dpy, st->gc, st->fg);
  XSetClipMask(st->dpy, st->gc, hollow->pixmap);
  XSetClipOrigin(st->dpy, st->gc,
		 st->x_border + (x * st->piece_width) - hollow->x - 1,
		 st->y_border + (y * st->piece_width) - hollow->y - 1);
  XFillRectangle(st->dpy, st->window, st->gc,
		 st->x_border + (x * st->piece_width)  - st->piece_width/2,
		 st->y_border + (y * st->piece_height) - st->piece_height/2,
		 st->piece_width*2, st->piece_height*2);
}


static int
animate_swap (struct state *st, struct swap_state *sw)
{
  XPoint swap;

  if (sw->flashing > 1)
    {
      draw_piece(st, sw->x1, sw->y1, sw->flashing & 1);
      draw_piece(st, sw->x2, sw->y2, sw->flashing & 1);
      sw->flashing--;
      return st->delay;
    }

  swap = st->state[sw->y1 * st->width + sw->x1];
  st->state[sw->y1 * st->width + sw->x1] =
    st->state[sw->y2 * st->width + sw->x2];
  st->state[sw->y2 * st->width + sw->x2] = swap;
  
  if (sw->draw_p)
    {
      draw_piece(st, sw->x1, sw->y1, 0);
      draw_piece(st, sw->x2, sw->y2, 0);
      sw->flashing = 0;
    }

  return 0;
}


static int
swap_pieces (struct state *st,
             int src_x, int src_y, int dst_x, int dst_y,
             Bool draw_p)
{
  struct swap_state *sw = &st->swap;
  
  sw->x1 = src_x;
  sw->y1 = src_y;
  sw->x2 = dst_x;
  sw->y2 = dst_y;
  sw->draw_p = draw_p;

  /* if animating, plan to flash the pieces on and off a few times */
  sw->flashing = sw->draw_p ? 7 : 0;

  return animate_swap(st, sw);
}


static Bool
done (struct state *st)
{
  int x, y;
  for (y = 0; y < st->height; y++)
    for (x = 0; x < st->width; x++)
      {
	int x2 = st->state[y * st->width + x].x;
	int y2 = st->state[y * st->width + x].y;
	if (x != x2 || y != y2)
	  return False;
      }
  return True;
}


static int
shuffle (struct state *st, Bool draw_p)
{
  struct piece *p1, *p2;
  int src_x, src_y, dst_x = -1, dst_y = -1;

 AGAIN:
  p1 = p2 = 0;
  src_x = random() % st->width;
  src_y = random() % st->height;

  get_piece(st, src_x, src_y, &p1, 0);

  /* Pick random coordinates until we find one that has the same kind of
     piece as the first one we picked.  Note that it's possible for there
     to be only one piece of a particular shape on the board (this always
     happens with the four corner pieces.)
   */
  while (p1 != p2)
    {
      dst_x = random() % st->width;
      dst_y = random() % st->height;
      get_piece(st, dst_x, dst_y, &p2, 0);
    }

  if (src_x == dst_x && src_y == dst_y)
    goto AGAIN;

  return swap_pieces(st, src_x, src_y, dst_x, dst_y, draw_p);
}


static void
shuffle_all (struct state *st)
{
  int j;
  for (j = 0; j < 5; j++) {
    /* swap each piece with another 5x */
    int i = (st->width * st->height * 5);
    while (--i > 0)
      shuffle (st, False);

    /* and do that whole process up to 5x if we ended up with a solved
       board (this often happens with 4x4 boards.) */
    if (!done(st)) 
      break;
  }
}


static int
unshuffle (struct state *st)
{
  int i;
  for (i = 0; i < st->width * st->height * 4; i++)
    {
      int x = random() % st->width;
      int y = random() % st->height;
      int x2 = st->state[y * st->width + x].x;
      int y2 = st->state[y * st->width + x].y;
      if (x != x2 || y != y2)
	{
	  return swap_pieces(st, x, y, x2, y2, True);
	}
    }
  return 0;
}


static int
animate_clear (struct state *st)
{
  while (st->clearing > 0)
    {
      int x = random() % st->width;
      int y = random() % st->height;
      XPoint *p = &st->state[y * st->width + x];
      if (p->x == -1)
	continue;
      draw_piece(st, p->x, p->y, 2);
      p->x = p->y = -1;
      st->clearing--;
      return st->delay;
    }
  return 0;
}


static int
clear_all (struct state *st)
{
  st->clearing = st->width * st->height;
  return animate_clear(st);
}


static void *
jigsaw_init (Display *dpy, Window window)
{
  struct state *st = (struct state *) calloc (1, sizeof(*st));
  st->dpy = dpy;
  st->window = window;
  st->delay = get_integer_resource (st->dpy, "delay", "Integer");
  st->delay2 = get_integer_resource (st->dpy, "delay2", "Integer") * 1000000;
  st->border_width = get_integer_resource (st->dpy, "pieceBorderWidth",
                                           "Integer");
  if (st->delay == 0) st->delay = 1; /* kludge */
  return st;
}


static unsigned long
jigsaw_draw (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  int x, y;
  int delay = 0;

  if (st->img_loader)   /* still loading */
    {
      st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
      if (! st->img_loader) {  /* just finished */
	shuffle_all (st);
	for (y = 0; y < st->height; y++)
	  for (x = 0; x < st->width; x++)
	    draw_piece(st, x, y, 0);
      }
      return st->delay;
    }

  if (st->swap.flashing)
    delay = animate_swap (st, &st->swap);
  else if (st->clearing)
    delay = animate_clear (st);

  if (!delay) {
    if (st->jigstate == 0)
      {
	jigsaw_init_1 (st);
	st->jigstate = 1;
      }
    else if (st->jigstate == 1)
      {
	if (done(st))
	  {
	    st->jigstate = 2;
	    delay = st->delay2;
	  }
	else
	  {
	    delay = unshuffle(st);
	  }
      }
    else if (st->jigstate == 2)
      {
	st->jigstate = 0;
	delay = clear_all(st);    
      }
    else
      abort();
  }

  if (delay == 1) delay = 0; /* kludge */
  return (delay ? delay : st->delay * 10);
}

static void
jigsaw_reshape (Display *dpy, Window window, void *closure, 
                 unsigned int w, unsigned int h)
{
  /* window size is checked each time a new puzzle begins */
}

static Bool
jigsaw_event (Display *dpy, Window window, void *closure, XEvent *event)
{
  return False;
}

static void
jigsaw_free (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  free_puzzle_pixmaps (st);
  if (st->state) free (st->state);
  if (st->gc) XFreeGC (dpy, st->gc);
  if (st->source) XFreePixmap (dpy, st->source);
  free (st);
}



static const char *jigsaw_defaults [] = {
  ".background:		Black",
  ".foreground:		#AAAAAA",
  "*delay:		70000",
  "*delay2:		5",
  "*pieceBorderWidth:   -1",
#ifdef __sgi    /* really, HAVE_READ_DISPLAY_EXTENSION */
  "*visualID:		Best",
#endif
  0
};

static XrmOptionDescRec jigsaw_options [] = {
  { "-delay",		".delay",		XrmoptionSepArg, 0 },
  { "-delay2",		".delay2",		XrmoptionSepArg, 0 },
  { "-bw",		".pieceBorderWidth",	XrmoptionSepArg, 0 },
  { "-border-width",	".pieceBorderWidth",	XrmoptionSepArg, 0 },
  { 0, 0, 0, 0 }
};


XSCREENSAVER_MODULE ("Jigsaw", jigsaw)
