/* -*- Mode: C; tab-width: 4 -*- */
/* deluxe ---  */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)deluxe.c	5.22 2006/03/07 xlockmore";

#endif

/* 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.
 * 
 * adapted for xlockmore : March 2006
 */

#ifdef STANDALONE
#define MODE_deluxe
#define PROGCLASS "Deluxe"
#define HACK_INIT init_deluxe
#define HACK_DRAW draw_deluxe
#define deluxe_opts xlockmore_opts
#define DEFAULTS "*delay: 10000 \n" \
 "*size: 4 \n" \
 "*ncolors: 8 \n" \
 "*fullrandom: True \n" \
 "*verbose: False \n" \
  ".background:		black\n",
  ".foreground:		white\n",
  "*delay:		5000\n",
  "*count:		5\n",
  "*thickness:		50\n",
  "*speed:		15\n",
  "*ncolors:		20\n",
  "*nlayers:		0\n",
  "*transparent:	False\n",
  "*doubleBuffer:	True\n",
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  "*useDBE:		True\n",
  "*useDBEClear:	True\n",
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
  0
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */
#include "color.h"
#endif /* STANDALONE */

#ifdef MODE_deluxe

#include <math.h>

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

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

#define DEF_THICKNESS "50"
#define DEF_SPEED "15"
#define DEF_TRANSPARENT "False"
#define DEF_DB "True"
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
#define DEF_USEDBECLEAR "True" 
#endif
#define DEF_MONO "False"
#define DEF_PLANES "0"
  
static int st_thickness;
static int st_speed;
static Bool st_transparent;
static Bool st_db;
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
static Bool st_dbeclear_p;
#endif
static Bool st_mono;
static int st_planes;

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

typedef struct {
  Bool transparent_p;
  int nplanes;
  unsigned long base_pixel, *plane_masks;
   int count;
   int ncolors;
   Bool dbuf;
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
   Bool dbeclear_p;
  XdbeBackBuffer backb;
#endif
   XColor *colors;
   GC erase_gc;
   struct throbber **throbbers;
   XWindowAttributes xgwa;
   Pixmap b, ba, bb;	/* double-buffer to reduce flicker */
} deluxestruct;

static deluxestruct *deluxes = (deluxestruct *) NULL;

static XrmOptionDescRec opts [] = {
  { "-thickness",	"deluxe.thickness",	XrmoptionSepArg,  (caddr_t) NULL },
  { "-speed",		"deluxe.speed",	XrmoptionSepArg,  (caddr_t) NULL },
  { "-planes",		"deluxe.planes",	XrmoptionSepArg,  (caddr_t) NULL },
  { "-transparent",	"deluxe.transparent",	 XrmoptionNoArg,  (caddr_t) "on" },
  { "+transparent",	"deluxe.transparent",	 XrmoptionNoArg,  (caddr_t) "off" },
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  { "-useDBEClear",		"deluxe.useDBEClear", XrmoptionNoArg,  (caddr_t) "on" },
  { "+useDBEClear",		"deluxe.useDBEClear", XrmoptionNoArg,  (caddr_t) "off" }
#endif
  { "-mono",		"deluxe.mono", XrmoptionNoArg,  (caddr_t) "on" },
  { "+mono",		"deluxe.mono", XrmoptionNoArg,  (caddr_t) "off" },
  { "-db",		"deluxe.doubleBuffer", XrmoptionNoArg,  (caddr_t) "on" },
  { "+db",		"deluxe.doubleBuffer", XrmoptionNoArg,  (caddr_t) "off" }
};

static argtype vars[] =
{
	{(void *) & st_thickness, (char *) "thickness", (char *) "thickness",
	     (char *) DEF_THICKNESS, t_Int},
	{(void *) & st_speed, (char *) "speed", (char *) "speed",
	     (char *) DEF_SPEED, t_Int},
	{(void *) & st_planes, (char *) "planes", (char *) "Planes",
	     (char *) DEF_PLANES, t_Int},
	{(void *) & st_transparent, (char *) "transparent",
	     (char *) "Transparent", (char *) DEF_TRANSPARENT, t_Bool},
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
	{(void *) & st_dbeclear_p, (char *) "useDBEClear",
	     (char *) "useDBEClear",
	     (char *) DEF_USEDBECLEAR, t_Bool},
#endif
	{(void *) & st_mono, (char *) "mono", (char *) "mono",
	     (char *) DEF_MONO, t_Bool},
	{(void *) & st_db, (char *) "db", (char *) "db",
	     (char *) DEF_DB, t_Bool}

};
static OptionStruct desc[] =
{
	{(char *) "-thickness num", (char *) "Figure thickness"},
	{(char *) "-speed num", (char *) "Animation speed"},
	{(char *) "-planes num", (char *) "Number of planes"},
	{(char *) "-/+transparent", (char *) "turn on/off transparancy"},
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
     {(char *) "-/+useDBEClear", (char *) "turn on/off useDBEClear"}
#endif
     {(char *) "-/+mono", (char *) "turn on/off monochromatic mode"},
     {(char *) "-/+db", (char *) "turn on/off double buffering"}
};

ModeSpecOpt deluxe_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES
ModStruct   deluxe_description =
{"deluxe", "init_deluxe", "draw_deluxe", "release_deluxe",
 "refresh_deluxe", "init_deluxe", (char *) NULL, &deluxe_opts,
 5000, 5, 1, 1, 64, 1.0, "",
 "Shows pulsing sequence of stars, circles, and lines.", 0, NULL};

#endif


static void
free_deluxe(Display *dpy, deluxestruct *dlp)
{
   int i;
   
   for (i = 0; i < dlp->count; i++)
     if ( dlp->throbbers[i] )
       {
          if (dlp->throbbers[i]->gc != None)
             XFreeGC (dpy, dlp->throbbers[i]->gc);
	  free( dlp->throbbers[i] );
       }
   if ( dlp->throbbers )
     {
	free( dlp->throbbers );
	dlp->throbbers = ( struct throbber** ) NULL;
     }
   if ( dlp->colors )
     {
	free( dlp->colors );
	dlp->colors = (XColor *) NULL;
     }
   if ( dlp->plane_masks )
     {
	free( dlp->plane_masks );
	dlp->plane_masks = (unsigned long*) NULL;
     }
   if (dlp->erase_gc != None)
     {
        XFreeGC (dpy, dlp->erase_gc);
   	dlp->erase_gc = None;
     }
}

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

  points[0].x = (short) (x + s  * cos(o + 0.0*c));
  points[0].y = (short) (y + s  * sin(o + 0.0*c));
  points[1].x = (short) (x + s2 * cos(o + 0.1*c));
  points[1].y = (short) (y + s2 * sin(o + 0.1*c));
  points[2].x = (short) (x + s  * cos(o + 0.2*c));
  points[2].y = (short) (y + s  * sin(o + 0.2*c));
  points[3].x = (short) (x + s2 * cos(o + 0.3*c));
  points[3].y = (short) (y + s2 * sin(o + 0.3*c));
  points[4].x = (short) (x + s  * cos(o + 0.4*c));
  points[4].y = (short) (y + s  * sin(o + 0.4*c));
  points[5].x = (short) (x + s2 * cos(o + 0.5*c));
  points[5].y = (short) (y + s2 * sin(o + 0.5*c));
  points[6].x = (short) (x + s  * cos(o + 0.6*c));
  points[6].y = (short) (y + s  * sin(o + 0.6*c));
  points[7].x = (short) (x + s2 * cos(o + 0.7*c));
  points[7].y = (short) (y + s2 * sin(o + 0.7*c));
  points[8].x = (short) (x + s  * cos(o + 0.8*c));
  points[8].y = (short) (y + s  * sin(o + 0.8*c));
  points[9].x = (short) (x + s2 * cos(o + 0.9*c));
  points[9].y = (short) (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 ,
	       deluxestruct *dlp )
{
  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 = st_speed;
  t->fuse = 1 + (NRAND(4));
  t->thickness = st_thickness;

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

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

  flags = GCForeground;
  if (dlp->transparent_p)
    {
      gcv.foreground = (unsigned long) ~0L;
      gcv.plane_mask = dlp->base_pixel |
	      dlp->plane_masks[NRAND(dlp->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 (NRAND(11)) {
  case 0: case 1: case 2: case 3: t->draw = deluxe_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;
    }
}

void
init_deluxe(ModeInfo * mi)
{
  deluxestruct *dlp;
  Display *dpy = MI_DISPLAY(mi);
  Window window = MI_WINDOW(mi);
  XGCValues gcv;
  int i;

   /* initialize */
   if (deluxes == NULL) {
      if ((deluxes = (deluxestruct *) calloc(MI_NUM_SCREENS(mi),
					     sizeof (deluxestruct))) == NULL)
	return;
   }
   dlp = &deluxes[MI_SCREEN(mi)];
   free_deluxe(dpy, dlp);

  dlp->count = MI_COUNT(mi);
  dlp->ncolors =  MI_NCOLORS(mi);
  dlp->dbuf = st_db;
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  dlp->dbeclear_p = st_dbeclear_p;
#endif
  dlp->colors = 0;
  dlp->erase_gc = 0;
  dlp->b=0;
  dlp->ba=0;
  dlp->bb=0;	/* double-buffer to reduce flicker */
#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
  dlp->backb =0;
#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */

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

  dlp->transparent_p = st_transparent;

  dlp->colors = (XColor *) calloc (sizeof(XColor), dlp->ncolors);

  if ( st_mono )
    {
    MONO:
      dlp->ncolors = 1;
      dlp->colors[0].pixel = MI_WHITE_PIXEL(mi);
    }
  else if (dlp->transparent_p)
    {
      dlp->nplanes = st_planes;
      if (dlp->nplanes <= 0)
        dlp->nplanes = NRAND(dlp->xgwa.depth-2) + 2;

      allocate_alpha_colors (dlp->xgwa.screen, dlp->xgwa.visual, dlp->xgwa.colormap,
                             &dlp->nplanes, True, &dlp->plane_masks,
			     &dlp->base_pixel , mi );
      if (dlp->nplanes <= 1)
	{
	  fprintf (stderr,
         "deluxe: couldn't allocate any color planes; turning transparency off.\n"
		  );
          dlp->transparent_p = False;
	  goto COLOR;
	}
    }
  else
    {
       Bool writeable = (MI_IS_INSTALL(mi) && MI_NPIXELS(mi) > 2);
    COLOR:
      make_random_colormap (mi , dlp->xgwa.colormap,
                            dlp->colors, &dlp->ncolors, True, True, &writeable
			    );
      if (dlp->ncolors < 2)
        goto MONO;
    }

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

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

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

  gcv.foreground = MI_BLACK_PIXEL(mi);
  dlp->erase_gc = XCreateGC (dpy, dlp->b, GCForeground, &gcv);

  if ( dlp->ba) XFillRectangle (dpy, dlp->ba, dlp->erase_gc, 0, 0,
				dlp->xgwa.width, dlp->xgwa.height);
  if ( dlp->bb) XFillRectangle (dpy, dlp->bb, dlp->erase_gc, 0, 0,
				dlp->xgwa.width, dlp->xgwa.height);
}

void draw_deluxe (ModeInfo * mi)
{
   deluxestruct *dlp = &deluxes[MI_SCREEN(mi)];
   Display *dpy = MI_DISPLAY(mi);
   Window window = MI_WINDOW(mi);
   int i;
   
   if (deluxes == NULL)
	return;
   MI_IS_DRAWN(mi) = True;


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

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

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

      XSync (dpy, False);
}

void
release_deluxe(ModeInfo * mi)
{
   if (deluxes != NULL) {
      int screen;

      for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
	free_deluxe(MI_DISPLAY(mi), &deluxes[screen]);
      free(deluxes);
      deluxes = (deluxestruct *) NULL;
   }
}

void
refresh_deluxe(ModeInfo * mi)
{
  if (deluxes == NULL)
    return;
}
#endif /* MODE_deluxe */
