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

/* Algorithm from a Mac program by Chris Tate, written in 1988 or so. */

/* 18-Sep-97: Johannes Keukelaar (johannes@nada.kth.se): Improved screen
 *            eraser.
 * 10-May-97: merged ellipse code by Dan Stromberg <strombrg@nis.acs.uci.edu>
 *            as found in xlockmore 4.03a10.
 * 1992:      jwz created.
 */

/* 25 April 2002: Matthew Strait <straitm@mathcs.carleton.edu> added
-subdelay option so the drawing process can be watched */

#include <math.h>
#include "screenhack.h"
#include "erase.h"

enum draw_state { HELIX, DRAW_HELIX, TRIG, DRAW_TRIG, LINGER, ERASE };

struct state {
  enum draw_state dstate;
  double sins [360];
  double coss [360];

  GC draw_gc;
  unsigned int default_fg_pixel;
  int sleep_time;
  int subdelay;
  eraser_state *eraser;

  int width, height;
  Colormap cmap;

  int x1, y1, x2, y2, angle, i;

  int radius1, radius2, d_angle, factor1, factor2, factor3, factor4;
  int d_angle_offset;
  int offset, dir, density;
};

static void *
helix_init (Display *dpy, Window window)
{
  struct state *st = (struct state *) calloc (1, sizeof(*st));
  int i;
  XGCValues gcv;
  XWindowAttributes xgwa;

  st->sleep_time = get_integer_resource(dpy, "delay", "Integer");
  st->subdelay = get_integer_resource(dpy, "subdelay", "Integer");

  XGetWindowAttributes (dpy, window, &xgwa);
  st->width = xgwa.width;
  st->height = xgwa.height;
  st->cmap = xgwa.colormap;
  gcv.foreground = st->default_fg_pixel =
    get_pixel_resource (dpy, st->cmap, "foreground", "Foreground");
  st->draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
  gcv.foreground = get_pixel_resource (dpy, st->cmap, "background", "Background");

  for (i = 0; i < 360; i++)
    {
      st->sins [i] = sin ((((double) i) / 180.0) * M_PI);
      st->coss [i] = cos ((((double) i) / 180.0) * M_PI);
    }

  st->dstate = (random() & 1) ? HELIX : TRIG;

  return st;
}

static int
gcd (int a, int b)
{
  while (b > 0)
    {
      int tmp;
      tmp = a % b;
      a = b;
      b = tmp;
    }
  return (a < 0 ? -a : a);
}

static void
helix (Display *dpy, Window window, struct state *st)
{
  int xmid = st->width / 2;
  int ymid = st->height / 2;
  int limit = 1 + (360 / gcd (360, st->d_angle));

  if (st->i == 0)
    {
      st->x1 = xmid;
      st->y1 = ymid + st->radius2;
      st->x2 = xmid;
      st->y2 = ymid + st->radius1;
      st->angle = 0;
    }
  
/*  for (st->i = 0; st->i < limit; st->i++)*/
    {
      int tmp;
#define pmod(x,y) (tmp=((x) % (y)), (tmp >= 0 ? tmp : (tmp + (y))))

      st->x1 = xmid + (((double) st->radius1) * st->sins [pmod ((st->angle * st->factor1), 360)]);
      st->y1 = ymid + (((double) st->radius2) * st->coss [pmod ((st->angle * st->factor2), 360)]);
      XDrawLine (dpy, window, st->draw_gc, st->x1, st->y1, st->x2, st->y2);
      st->x2 = xmid + (((double) st->radius2) * st->sins [pmod ((st->angle * st->factor3), 360)]);
      st->y2 = ymid + (((double) st->radius1) * st->coss [pmod ((st->angle * st->factor4), 360)]);
      XDrawLine (dpy, window, st->draw_gc, st->x1, st->y1, st->x2, st->y2);
      st->angle += st->d_angle;
    }
    st->i++;

    if (st->i >= limit)
      st->dstate = LINGER;
}

static void
trig (Display *dpy, Window window, struct state *st)
{
  int xmid = st->width / 2;
  int ymid = st->height / 2;

/*  while (st->d_angle >= -360 && st->d_angle <= 360)*/
    {
      int tmp;
      int angle = st->d_angle + st->d_angle_offset;
      st->x1 = (st->sins [pmod(angle * st->factor1, 360)] * xmid) + xmid;
      st->y1 = (st->coss [pmod(angle * st->factor1, 360)] * ymid) + ymid;
      st->x2 = (st->sins [pmod(angle * st->factor2 + st->offset, 360)] * xmid) + xmid;
      st->y2 = (st->coss [pmod(angle * st->factor2 + st->offset, 360)] * ymid) + ymid;
      XDrawLine(dpy, window, st->draw_gc, st->x1, st->y1, st->x2, st->y2);
      tmp = (int) 360 / (2 * st->density * st->factor1 * st->factor2);
      if (tmp == 0)	/* Do not want it getting stuck... */
	tmp = 1;	/* Would not need if floating point */
      st->d_angle += st->dir * tmp;
    }

  if (st->d_angle < -360 || st->d_angle > 360)
    st->dstate = LINGER;
}


#define min(a,b) ((a)<(b)?(a):(b))

static void
random_helix (Display *dpy, Window window, struct state *st,
              XColor *color, Bool *got_color)
{
  int radius;
  double divisor;

  radius = min (st->width, st->height) / 2;

  st->i = 0;
  st->d_angle = 0;
  st->factor1 = 2;
  st->factor2 = 2;
  st->factor3 = 2;
  st->factor4 = 2;

  divisor = ((frand (3.0) + 1) * (((random() & 1) * 2) - 1));

  if ((random () & 1) == 0)
    {
      st->radius1 = radius;
      st->radius2 = radius / divisor;
    }
  else
    {
      st->radius2 = radius;
      st->radius1 = radius / divisor;
    }

  while (gcd (360, st->d_angle) >= 2)
    st->d_angle = random () % 360;

#define random_factor()				\
  (((random() % 7) ? ((random() & 1) + 1) : 3)	\
   * (((random() & 1) * 2) - 1))

  while (gcd (gcd (gcd (st->factor1, st->factor2), st->factor3), st->factor4) != 1)
    {
      st->factor1 = random_factor ();
      st->factor2 = random_factor ();
      st->factor3 = random_factor ();
      st->factor4 = random_factor ();
    }

  if (mono_p)
    XSetForeground (dpy, st->draw_gc, st->default_fg_pixel);
  else
    {
      hsv_to_rgb (random () % 360, frand (1.0), frand (0.5) + 0.5,
		  &color->red, &color->green, &color->blue);
      if ((*got_color = XAllocColor (dpy, st->cmap, color)))
	XSetForeground (dpy, st->draw_gc, color->pixel);
      else
	XSetForeground (dpy, st->draw_gc, st->default_fg_pixel);
    }

  XClearWindow (dpy, window);
}

static void
random_trig (Display *dpy, Window window, struct state *st,
             XColor *color, Bool *got_color)
{
  st->d_angle = 0;
  st->factor1 = (random() % 8) + 1;
  do {
    st->factor2 = (random() % 8) + 1;
  } while (st->factor1 == st->factor2);

  st->dir = (random() & 1) ? 1 : -1;
  st->d_angle_offset = random() % 360;
  st->offset = ((random() % ((360 / 4) - 1)) + 1) / 4;
  st->density = 1 << ((random() % 4) + 4);	/* Higher density, higher angles */

  if (mono_p)
    XSetForeground (dpy, st->draw_gc, st->default_fg_pixel);
  else
    {
      hsv_to_rgb (random () % 360, frand (1.0), frand (0.5) + 0.5,
		  &color->red, &color->green, &color->blue);
      if ((*got_color = XAllocColor (dpy, st->cmap, color)))
	XSetForeground (dpy, st->draw_gc, color->pixel);
      else
	XSetForeground (dpy, st->draw_gc, st->default_fg_pixel);
    }

  XClearWindow (dpy, window);
}


/* random_helix_or_trig */
static unsigned long
helix_draw (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  Bool free_color = False;
  XColor color;
  int delay = st->subdelay;
  int erase_delay = 10000;
  int ii;

  if (st->eraser) {
    st->eraser = erase_window (dpy, window, st->eraser);
    if (st->eraser) 
      delay = erase_delay;
    goto END;
  }

  switch (st->dstate) 
    {
    case LINGER:
      delay = st->sleep_time * 1000000;
      st->dstate = ERASE;
      break;

    case ERASE:
      st->eraser = erase_window (dpy, window, st->eraser);
      delay = erase_delay;
      if (free_color) XFreeColors (dpy, st->cmap, &color.pixel, 1, 0);
      st->dstate = (random() & 1) ? HELIX : TRIG;
      break;

    case DRAW_HELIX:
      for (ii = 0; ii < 10; ii++) {
        helix (dpy, window, st);
        if (st->dstate != DRAW_HELIX)
          break;
      }
      break;

    case DRAW_TRIG:
      for (ii = 0; ii < 5; ii++) {
        trig (dpy, window, st);
        if (st->dstate != DRAW_TRIG)
          break;
      }
      break;

    case HELIX:
      random_helix (dpy, window, st, &color, &free_color);
      st->dstate = DRAW_HELIX;
      break;

    case TRIG:
      random_trig(dpy, window, st, &color, &free_color);
      st->dstate = DRAW_TRIG;
      break;

    default: 
      abort();
    }

 END:
  return delay;
}

static void
helix_reshape (Display *dpy, Window window, void *closure, 
                 unsigned int w, unsigned int h)
{
  struct state *st = (struct state *) closure;
  st->width = w;
  st->height = h;
}

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

static void
helix_free (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  free (st);
}



static const char *helix_defaults [] = {
  ".background: black",
  ".foreground: white",
  "*delay:      5",
  "*subdelay:	20000",
  0
};

static XrmOptionDescRec helix_options [] = {   
  { "-delay",           ".delay",               XrmoptionSepArg, 0 },
  { "-subdelay",        ".subdelay",            XrmoptionSepArg, 0 },
  { 0,			0,			0,		 0 },
};

XSCREENSAVER_MODULE ("Helix", helix)
