/* xscreensaver, Copyright (c) 1999, 2001, 2002 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 <math.h>
#include "screenhack.h"
#include "alpha.h"

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

#define countof(x) (sizeof(x)/sizeof(*(x)))
#define ABS(x) ((x)<0?-(x):(x))

static Bool transparent_p;
static int nplanes;
static unsigned long base_pixel, *plane_masks;

struct throbber {
  int x, y;
  int size;
  int max_size;
  int thickness;
  int speed;
  int fuse;
  GC gc;
  void (*draw) (Display *, Drawable, struct throbber *);
};

static void
draw_star (Display *dpy, Drawable w, struct throbber *t)
{
  XPoint points[11];
  int x = t->x;
  int y = t->y;
  int s = t->size / 0.383;  /* trial and error, I forget how to derive this */
  int s2 = t->size;
  double c = M_PI * 2;
  double o = -M_PI / 2;

  points[0].x = x + s  * cos(o + 0.0*c); points[0].y = y + s  * sin(o + 0.0*c);
  points[1].x = x + s2 * cos(o + 0.1*c); points[1].y = y + s2 * sin(o + 0.1*c);
  points[2].x = x + s  * cos(o + 0.2*c); points[2].y = y + s  * sin(o + 0.2*c);
  points[3].x = x + s2 * cos(o + 0.3*c); points[3].y = y + s2 * sin(o + 0.3*c);
  points[4].x = x + s  * cos(o + 0.4*c); points[4].y = y + s  * sin(o + 0.4*c);
  points[5].x = x + s2 * cos(o + 0.5*c); points[5].y = y + s2 * sin(o + 0.5*c);
  points[6].x = x + s  * cos(o + 0.6*c); points[6].y = y + s  * sin(o + 0.6*c);
  points[7].x = x + s2 * cos(o + 0.7*c); points[7].y = y + s2 * sin(o + 0.7*c);
  points[8].x = x + s  * cos(o + 0.8*c); points[8].y = y + s  * sin(o + 0.8*c);
  points[9].x = x + s2 * cos(o + 0.9*c); points[9].y = y + s2 * sin(o + 0.9*c);
  points[10] = points[0];

  XDrawLines (dpy, w, t->gc, points, countof(points), CoordModeOrigin);
}

static void
draw_circle (Display *dpy, Drawable w, struct throbber *t)
{
  XDrawArc (dpy, w, t->gc,
            t->x - t->size / 2,
            t->y - t->size / 2,
            t->size, t->size,
            0, 360*64);
}

static void
draw_hlines (Display *dpy, Drawable w, struct throbber *t)
{
  XDrawLine (dpy, w, t->gc, 0,
             t->y - t->size, t->max_size,
             t->y - t->size);
  XDrawLine (dpy, w, t->gc, 0,
             t->y + t->size, t->max_size,
             t->y + t->size);
}

static void
draw_vlines (Display *dpy, Drawable w, struct throbber *t)
{
  XDrawLine (dpy, w, t->gc,
             t->x - t->size, 0,
             t->x - t->size, t->max_size);
  XDrawLine (dpy, w, t->gc,
             t->x + t->size, 0,
             t->x + t->size, t->max_size);
}

static void
draw_corners (Display *dpy, Drawable w, struct throbber *t)
{
  int s = (t->size + t->thickness) / 2;
  XPoint points[3];

  points[0].x = 0;        points[0].y = t->y - s;
  points[1].x = t->x - s; points[1].y = t->y - s;
  points[2].x = t->x - s; points[2].y = 0;
  XDrawLines (dpy, w, t->gc, points, countof(points), CoordModeOrigin);

  points[0].x = 0;        points[0].y = t->y + s;
  points[1].x = t->x - s; points[1].y = t->y + s;
  points[2].x = t->x - s; points[2].y = t->max_size;
  XDrawLines (dpy, w, t->gc, points, countof(points), CoordModeOrigin);

  points[0].x = t->x + s;    points[0].y = 0;
  points[1].x = t->x + s;    points[1].y = t->y - s;
  points[2].x = t->max_size; points[2].y = t->y - s;
  XDrawLines (dpy, w, t->gc, points, countof(points), CoordModeOrigin);

  points[0].x = t->x + s;    points[0].y = t->max_size;
  points[1].x = t->x + s;    points[1].y = t->y + s;
  points[2].x = t->max_size; points[2].y = t->y + s;
  XDrawLines (dpy, w, t->gc, points, countof(points), CoordModeOrigin);
}


static struct throbber *
make_throbber (Display *dpy, Drawable d, int w, int h, unsigned long pixel)
{
  XGCValues gcv;
  unsigned long flags;
  struct throbber *t = (struct throbber *) malloc (sizeof (*t));
  t->x = w / 2;
  t->y = h / 2;
  t->max_size = w;
  t->speed = get_integer_resource ("speed", "Speed");
  t->fuse = 1 + (random() % 4);
  t->thickness = get_integer_resource ("thickness", "Thickness");

  if (t->speed < 0) t->speed = -t->speed;
  t->speed += (((random() % t->speed) / 2) - (t->speed / 2));
  if (t->speed > 0) t->speed = -t->speed;

  if (random() % 4)
    t->size = t->max_size;
  else
    t->size = t->thickness, t->speed = -t->speed;

  flags = GCForeground;
  if (transparent_p)
    {
      gcv.foreground = ~0L;
      gcv.plane_mask = base_pixel | plane_masks[random() % nplanes];
      flags |= GCPlaneMask;
    }
  else
    {
      gcv.foreground = pixel;
    }

  gcv.line_width = t->thickness;
  gcv.line_style = LineSolid;
  gcv.cap_style = CapProjecting;
  gcv.join_style = JoinMiter;

  flags |= (GCLineWidth | GCLineStyle | GCCapStyle | GCJoinStyle);
  t->gc = XCreateGC (dpy, d, flags, &gcv);

  switch (random() % 11) {
  case 0: case 1: case 2: case 3: t->draw = draw_star; break;
  case 4: case 5: case 6: case 7: t->draw = draw_circle; break;
  case 8: t->draw = draw_hlines; break;
  case 9: t->draw = draw_vlines; break;
  case 10: t->draw = draw_corners; break;
  default: abort(); break;
  }

  return t;
}

static int
throb (Display *dpy, Drawable window, struct throbber *t)
{
  t->size += t->speed;
  if (t->size <= (t->thickness / 2))
    {
      t->speed = -t->speed;
      t->size += (t->speed * 2);
    }
  else if (t->size > t->max_size)
    {
      t->speed = -t->speed;
      t->size += (t->speed * 2);
      t->fuse--;
    }

  if (t->fuse <= 0)
    {
      XFreeGC (dpy, t->gc);
      memset (t, 0, sizeof(*t));
      free (t);
      return -1;
    }
  else
    {
      t->draw (dpy, window, t);
      return 0;
    }
}



char *progclass = "Deluxe";

char *defaults [] = {
  ".background:		black",
  ".foreground:		white",
  "*delay:		5000",
  "*count:		5",
  "*thickness:		50",
  "*speed:		15",
  "*ncolors:		20",
  "*nlayers:		0",
  "*transparent:	False",
  "*doubleBuffer:	True",
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  "*useDBE:		True",
  "*useDBEClear:	True",
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
  0
};

XrmOptionDescRec options [] = {
  { "-delay",		".delay",	XrmoptionSepArg, 0 },
  { "-thickness",	".thickness",	XrmoptionSepArg, 0 },
  { "-count",		".count",	XrmoptionSepArg, 0 },
  { "-ncolors",		".ncolors",	XrmoptionSepArg, 0 },
  { "-speed",		".speed",	XrmoptionSepArg, 0 },
  { "-transparent",	".transparent",	 XrmoptionNoArg,  "True" },
  { "-opaque",		".transparent",	 XrmoptionNoArg,  "False" },
  { "-db",		".doubleBuffer", XrmoptionNoArg,  "True" },
  { "-no-db",		".doubleBuffer", XrmoptionNoArg,  "False" },
  { 0, 0, 0, 0 }
};

void
screenhack (Display *dpy, Window window)
{
  int count = get_integer_resource ("count", "Integer");
  int delay = get_integer_resource ("delay", "Integer");
  int ncolors = get_integer_resource ("ncolors", "Integer");
  Bool dbuf = get_boolean_resource ("doubleBuffer", "Boolean");
  Bool dbeclear_p = get_boolean_resource ("useDBEClear", "Boolean");
  XColor *colors = 0;
  XGCValues gcv;
  GC erase_gc = 0;
  int i;
  struct throbber **throbbers;
  XWindowAttributes xgwa;
  Pixmap b=0, ba=0, bb=0;	/* double-buffer to reduce flicker */
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  XdbeBackBuffer backb = 0;
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */

  XGetWindowAttributes (dpy, window, &xgwa);

  transparent_p = get_boolean_resource("transparent", "Transparent");

  colors = (XColor *) calloc (sizeof(*colors), ncolors);

  if (get_boolean_resource("mono", "Boolean"))
    {
    MONO:
      ncolors = 1;
      colors[0].pixel = get_pixel_resource("foreground", "Foreground",
                                           dpy, xgwa.colormap);
    }
  else if (transparent_p)
    {
      nplanes = get_integer_resource ("planes", "Planes");
      if (nplanes <= 0)
        nplanes = (random() % (xgwa.depth-2)) + 2;

      allocate_alpha_colors (xgwa.screen, xgwa.visual, xgwa.colormap,
                             &nplanes, True, &plane_masks,
			     &base_pixel);
      if (nplanes <= 1)
	{
	  fprintf (stderr,
         "%s: couldn't allocate any color planes; turning transparency off.\n",
		   progname);
          transparent_p = False;
	  goto COLOR;
	}
    }
  else
    {
    COLOR:
      make_random_colormap (dpy, xgwa.visual, xgwa.colormap,
                            colors, &ncolors, True, True, 0, True);
      if (ncolors < 2)
        goto MONO;
    }

  if (dbuf)
    {
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
      if (dbeclear_p)
        b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
      else
        b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
      backb = b;
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */

      if (!b)
        {
          ba = XCreatePixmap (dpy, window, xgwa.width, xgwa.height,xgwa.depth);
          bb = XCreatePixmap (dpy, window, xgwa.width, xgwa.height,xgwa.depth);
          b = ba;
        }
    }
  else
    {
      b = window;
    }

  throbbers = (struct throbber **) calloc (count, sizeof(struct throbber *));
  for (i = 0; i < count; i++)
    throbbers[i] = make_throbber (dpy, b, xgwa.width, xgwa.height,
                                  colors[random() % ncolors].pixel);

  gcv.foreground = get_pixel_resource ("background", "Background",
                                       dpy, xgwa.colormap);
  erase_gc = XCreateGC (dpy, b, GCForeground, &gcv);

  if (ba) XFillRectangle (dpy, ba, erase_gc, 0, 0, xgwa.width, xgwa.height);
  if (bb) XFillRectangle (dpy, bb, erase_gc, 0, 0, xgwa.width, xgwa.height);

  while (1)
    {
      if (!dbeclear_p ||
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
          !backb
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
          )
        XFillRectangle (dpy, b, erase_gc, 0, 0, xgwa.width, xgwa.height);

      for (i = 0; i < count; i++)
        if (throb (dpy, b, throbbers[i]) < 0)
          throbbers[i] = make_throbber (dpy, b, xgwa.width, xgwa.height,
                                        colors[random() % ncolors].pixel);

#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
      if (backb)
        {
          XdbeSwapInfo info[1];
          info[0].swap_window = window;
          info[0].swap_action = (dbeclear_p ? XdbeBackground : XdbeUndefined);
          XdbeSwapBuffers (dpy, info, 1);
        }
      else
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
      if (dbuf)
        {
          XCopyArea (dpy, b, window, erase_gc, 0, 0,
                     xgwa.width, xgwa.height, 0, 0);
          b = (b == ba ? bb : ba);
        }

      XSync (dpy, False);
      screenhack_handle_events (dpy);
      if (delay)
        usleep (delay);
    }
}
