/* xscreensaver, Copyright (c) 1997, 1998 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.
 */

#include "screenhack.h"
#include <X11/Xutil.h>

#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
# include "xdbe.h"
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */

static int ncolors;
static XColor *colors = 0;
static int fg_pixel, bg_pixel;
static Pixmap p0 = 0, p1 = 0, p2 = 0, p3 = 0;
static GC copy_gc = 0, erase_gc = 0, window_gc = 0;
static int width, height, size;
static int x1, x2, y1, y2, x3, y3;
static int dx1, dx2, dx3, dy1, dy2, dy3;
static int othickness, thickness;
static Bool do_three;
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
XdbeBackBuffer back_buf = 0;
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */

static void
init_moire2 (Display *dpy, Window window)
{
  XWindowAttributes xgwa;
  XGetWindowAttributes (dpy, window, &xgwa);

  othickness = get_integer_resource("thickness", "Thickness");

  if (mono_p)
    ncolors = 2;
  else
    ncolors = get_integer_resource ("colors", "Colors");
  if (ncolors < 2) ncolors = 2;
  if (ncolors <= 2) mono_p = True;

  if (mono_p)
    colors = 0;
  else
    colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));

  if (mono_p)
    ;
  else
    make_smooth_colormap (dpy, xgwa.visual, xgwa.colormap, colors, &ncolors,
			  True, 0, True);

  bg_pixel = get_pixel_resource("background", "Background", dpy,
				xgwa.colormap);
  fg_pixel = get_pixel_resource("foreground", "Foreground", dpy,
				xgwa.colormap);

#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  back_buf = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
}


static void
reset_moire2 (Display *dpy, Window window)
{
  GC gc;
  XWindowAttributes xgwa;
  XGCValues gcv;
  Bool xor;
  XGetWindowAttributes (dpy, window, &xgwa);

  do_three = (0 == (random() % 3));

  width = xgwa.width;
  height = xgwa.height;
  size = width > height ? width : height;

  if (p0) XFreePixmap(dpy, p0);
  if (p1) XFreePixmap(dpy, p1);
  if (p2) XFreePixmap(dpy, p2);
  if (p3) XFreePixmap(dpy, p3);

  p0 = XCreatePixmap(dpy, window, width, height, 1);
  p1 = XCreatePixmap(dpy, window, width*2, height*2, 1);
  p2 = XCreatePixmap(dpy, window, width*2, height*2, 1);
  if (do_three)
    p3 = XCreatePixmap(dpy, window, width*2, height*2, 1);
  else
    p3 = 0;

  thickness = (othickness > 0 ? othickness : (1 + (random() % 4)));

  gcv.foreground = 0;
  gcv.line_width = (thickness == 1 ? 0 : thickness);
  gc = XCreateGC (dpy, p1, GCForeground|GCLineWidth, &gcv);

  XFillRectangle(dpy, p1, gc, 0, 0, width*2, height*2);
  XFillRectangle(dpy, p2, gc, 0, 0, width*2, height*2);
  if (do_three)
    XFillRectangle(dpy, p3, gc, 0, 0, width*2, height*2);

  XSetForeground(dpy, gc, 1);

  xor = (do_three || (thickness == 1) || (random() & 1));

  {
    int i, ii, maxx, maxy;

#define FROB(P) do { \
    maxx = (size*4); \
    maxy = (size*4); \
    if (0 == (random() % 5)) { \
	float f = 1.0 + frand(0.05); \
	if (random() & 1) maxx *= f; \
	else maxy *= f; \
      } \
    ii = (thickness + 1 + (xor ? 0 : 1) + (random() % (4 * thickness)));  \
    for (i = 0; i < (size*2); i += ii)  \
      XDrawArc(dpy, (P), gc, i-size, i-size, maxx-i-i, maxy-i-i, 0, 360*64); \
    if (0 == (random() % 5)) \
      { \
	XSetFunction(dpy, gc, GXxor); \
	XFillRectangle(dpy, (P), gc, 0, 0, width*2, height*2); \
	XSetFunction(dpy, gc, GXcopy); \
      } \
    } while(0)

    FROB(p1);
    FROB(p2);
    if (do_three)
      FROB(p3);
#undef FROB
  }

  XFreeGC(dpy, gc);

  if (copy_gc) XFreeGC(dpy, copy_gc);
  gcv.function = (xor ? GXxor : GXor);
  gcv.foreground = 1;
  gcv.background = 0;

  copy_gc = XCreateGC (dpy, p0, GCFunction|GCForeground|GCBackground, &gcv);

  gcv.foreground = 0;
  if (erase_gc) XFreeGC(dpy, erase_gc);
  erase_gc = XCreateGC (dpy, p0, GCForeground, &gcv);

  gcv.foreground = fg_pixel;
  gcv.background = bg_pixel;
  if (window_gc) XFreeGC(dpy, window_gc);
  window_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);

#define FROB(N,DN,MAX) \
  N = (MAX/2) + (random() % MAX); \
  DN = ((1 + (random() % (7*thickness))) * ((random() & 1) ? 1 : -1))

  FROB(x1,dx1,width);
  FROB(x2,dx2,width);
  FROB(x3,dx3,width);
  FROB(y1,dy1,height);
  FROB(y2,dy2,height);
  FROB(y3,dy3,height);
#undef FROB
}



static void
moire2 (Display *dpy, Window window)
{
#define FROB(N,DN,MAX) \
  N += DN; \
  if (N <= 0) N = 0, DN = -DN; \
  else if (N >= MAX) N = MAX, DN = -DN; \
  else if (0 == (random() % 100)) DN = -DN; \
  else if (0 == (random() % 50)) \
    DN += (DN <= -20 ? 1 : (DN >= 20 ? -1 : ((random() & 1) ? 1 : -1)))

  FROB(x1,dx1,width);
  FROB(x2,dx2,width);
  FROB(x3,dx3,width);
  FROB(y1,dy1,height);
  FROB(y2,dy2,height);
  FROB(y3,dy3,height);
#undef FROB

  XFillRectangle(dpy, p0, erase_gc, 0, 0, width, height);
  XCopyArea(dpy, p1, p0, copy_gc, x1, y1, width, height, 0, 0);
  XCopyArea(dpy, p2, p0, copy_gc, x2, y2, width, height, 0, 0);
  if (do_three)
    XCopyArea(dpy, p3, p0, copy_gc, x3, y3, width, height, 0, 0);

#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  if (back_buf)
    {
      XdbeSwapInfo info[1];
      info[0].swap_window = window;
      info[0].swap_action = XdbeUndefined;
      XCopyPlane (dpy, p0, back_buf, window_gc, 0, 0, width, height, 0, 0, 1L);
      XdbeSwapBuffers (dpy, info, 1);
    }
  else
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
    XCopyPlane (dpy, p0, window, window_gc, 0, 0, width, height, 0, 0, 1L);

  XSync(dpy, False);

#if 0
  XCopyPlane(dpy, p1, window, window_gc, (width*2)/3, (height*2)/3,
	     width/2, height/2,
	     0, height/2, 1L);
  XCopyPlane(dpy, p2, window, window_gc, (width*2)/3, (height*2)/3,
	     width/2, height/2,
	     width/2, height/2, 1L);
#endif
}




char *progclass = "Moire2";

char *defaults [] = {
  ".background:		black",
  ".foreground:		white",
  "*delay:		50000",
  "*thickness:		0",
  "*colors:		150",
  "*colorShift:		5",

#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  /* Off by default, since it slows it down a lot, and the flicker isn't really
     all that bad without it... Or rather, it flickers just as badly with it.
     The XFree86 implementation of the XDBE extension totally blows!  There is
     just *no* excuse for the "swap buffers" operation to flicker like it does.
   */
  "*useDBE:      	False",
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */

  0
};

XrmOptionDescRec options [] = {
  { "-delay",		".delay",	XrmoptionSepArg, 0 },
  { "-ncolors",		".colors",	XrmoptionSepArg, 0 },
  { "-thickness",	".thickness",	XrmoptionSepArg, 0 },
  { 0, 0, 0, 0 }
};

void
screenhack (Display *dpy, Window window)
{
  int delay = get_integer_resource ("delay", "Integer");
  int color_shift = get_integer_resource ("colorShift", "Integer");
  int pix = 0;
  Bool flip_a, flip_b;

  if (color_shift <= 0) color_shift = 1;
  init_moire2 (dpy, window);
  while (1)
    {
      int iterations = 30 + (random() % 70) + (random() % 70);
      reset_moire2 (dpy, window);

      flip_a = mono_p ? False : (random() & 1);
      flip_b = mono_p ? False : (random() & 1);

      if (flip_b)
	{
	  XSetForeground(dpy, window_gc, bg_pixel);
	  XSetBackground(dpy, window_gc, fg_pixel);
	}
      else
	{
	  XSetForeground(dpy, window_gc, fg_pixel);
	  XSetBackground(dpy, window_gc, bg_pixel);
	}

      while (--iterations > 0)
	{
	  int i;

	  if (!mono_p)
	    {
	      pix++;
	      pix = pix % ncolors;

	      if (flip_a)
		XSetBackground(dpy, window_gc, colors[pix].pixel);
	      else
		XSetForeground(dpy, window_gc, colors[pix].pixel);
	    }

	  for (i = 0; i < color_shift; i++)
	    {
	      moire2 (dpy, window);
              screenhack_handle_events (dpy);
	      if (delay)
		usleep(delay);
	    }
	}
    }
}
