/* The Spiral Generator, Copyright (c) 2000 
 * by Rohit Singh <rohit_singh@hotmail.com>
 * 
 * Contains code from / To be used with:
 * xscreensaver, Copyright (c) 1992, 1995, 1996, 1997
 * 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 notices appear in all copies and that both that
 * copyright notices 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.
 *
 * Modified (Dec 2001) by Matthew Strait <straitm@mathcs.carleton.edu>
 * Added -subdelay and -alwaysfinish
 * Prevented redrawing over existing lines
 */

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

struct state {
  Display *dpy;
  Window window;
  XWindowAttributes xgwa;

  GC 	draw_gc;
  int 	long_delay;
  int 	sub_sleep_time;
  int 	num_layers;
  unsigned int default_fg_pixel;
  Bool	always_finish_p;
  XColor	color;
  int got_color;

  int theta;
  float firstx, firsty;
  int x1, y1, x2, y2;

  int counter;
  int distance;
  int radius1, radius2;
  double divisor;

  enum curstate { NEW_LAYER, DRAW, ERASE1, ERASE2 } drawstate;
  eraser_state *eraser;
};


static void
init_tsg (struct state *st)
{
  XGCValues	gcv;
  Colormap	cmap;

  XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
  cmap = st->xgwa.colormap;
  gcv.line_width = 1;
  if (st->xgwa.width > 2560 || st->xgwa.height > 2560)
    gcv.line_width = 3;  /* Retina displays */
  gcv.foreground = st->default_fg_pixel =
    get_pixel_resource (st->dpy, cmap, "foreground", "Foreground");
  st->draw_gc = XCreateGC (st->dpy, st->window, GCForeground|GCLineWidth, &gcv);
  gcv.foreground = get_pixel_resource (st->dpy, cmap, "background", "Background");
}


static Bool
go (struct state *st, int radius1, int radius2, int d)
{
  int width, height;
  int xmid, ymid;
  float tmpx, tmpy;
  int delta;

  width  = st->xgwa.width;
  height = st->xgwa.height;
  delta = 1;
  xmid = width / 2;
  ymid = height / 2;

  if (st->theta == 1) {
    st->x1 = xmid + radius1 - radius2 + d;
    st->y1 = ymid;
  }

/*  for (theta = 1; / * theta < ( 360 * 100 ) * /; theta++) */
                  /* see below about alwaysfinish */
    {
	tmpx = xmid + ((       radius1	          /* * * * *            */
                  - radius2        )		 /* This algo simulates	*/
                  * cos((      st->theta 		/* the rotation of a    */
                  * M_PI           ) 		/* circular disk inside */
                  / 180           )) 		/* a hollow circular 	*/
                  + (              d 		/* rim. A point on the  */
                  * cos((((  radius1 		/* disk dist d from the	*/
                  * st->theta      )		/* centre, traces the 	*/
                  - delta          )		/* path given by this   */
                  / radius2        )  		/* equation.	        */
                  *             M_PI		/* A deviation (error)  */
                  / 180            )            /* of delta needs to be */
                                   );           /* given, which greatly */
						/* adds to the beauty   */
	tmpy = ymid + (				/* of the figure.       */
                     ( radius1 - radius2	/*			*/
                      ) * sin			/* Imperfection adds to */
                       (			/* beauty, symbolically */
                        ( st->theta * M_PI     	/* ...			*/
                         ) / 180		/* Algo deduced by      */
                          )			/* Rohit Singh, Jan'00  */
                           ) +                  /* based on a toy he    */
                            ( d * sin           /* used to play with    */
                             (                  /* when he was a kid.  */
                              (                 /*            * * * * */
                               (          	
                                ( radius1 * st->theta
                                 ) - delta
                                  ) / radius2
                                   ) * M_PI / 180
                                    )
                                     );
        
	/*makes integers from the calculated values to do the drawing*/
	st->x2 = tmpx;
	st->y2 = tmpy;

	/*stores the first values for later reference*/
	if(st->theta == 1)
	{
  		st->firstx = tmpx;
  		st->firsty = tmpy;
	}

        if (st->theta != 1)
          XDrawLine (st->dpy, st->window, st->draw_gc, 
                     st->x1, st->y1, st->x2, st->y2);

	st->x1 = st->x2;
	st->y1 = st->y2;

	/* compares the exact values calculated to the first
	   exact values calculated */
	/* this will break when nothing new is being drawn */
	if(tmpx == st->firstx && tmpy == st->firsty && st->theta != 1) {
          st->firstx = st->firsty = 0;
          st->theta = 1;
          return True;
        }

	/* this will break after 36000 iterations if 
	   the -alwaysfinish option is not specified */
	if(!st->always_finish_p && st->theta > ( 360 * 100 ) ) {
          st->firstx = st->firsty = 0;
          st->theta = 1;
          return True;
        }
    }

    st->theta++;

    return False;
}


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


static void
pick_new (struct state *st)
{
  int radius = min (st->xgwa.width, st->xgwa.height) / 2;
  st->divisor = ((frand (3.0) + 1) * (((random() & 1) * 2) - 1));
  st->radius1 = radius;
  st->radius2 = radius / st->divisor + 5;
  st->distance = 100 + (random() % 200);
  st->theta = 1;
}


static void *
xspirograph_init (Display *dpy, Window window)
{
  struct state *st = (struct state *) calloc (1, sizeof(*st));
  st->dpy = dpy;
  st->window = window;
  st->long_delay = get_integer_resource(st->dpy, "delay", "Integer");
  st->sub_sleep_time = get_integer_resource(st->dpy, "subdelay", "Integer");
  st->num_layers = get_integer_resource(st->dpy, "layers", "Integer");
  st->always_finish_p = get_boolean_resource (st->dpy, "alwaysfinish", "Boolean");
  
  XGetWindowAttributes (st->dpy, st->window, &st->xgwa);

  init_tsg (st);
  st->theta = 1;
  st->drawstate = NEW_LAYER;

  return st;
}


static void
new_colors (struct state *st)
{
  if (mono_p)
    XSetForeground (st->dpy, st->draw_gc, st->default_fg_pixel);
  else
    {
      hsv_to_rgb (random () % 360, frand (1.0), frand (0.5) + 0.5,
		  &st->color.red, &st->color.green, &st->color.blue);
      if ((st->got_color = XAllocColor (st->dpy, st->xgwa.colormap,
                                        &st->color)))
	XSetForeground (st->dpy, st->draw_gc, st->color.pixel);
      else
	XSetForeground (st->dpy, st->draw_gc, st->default_fg_pixel);
    }
}



static unsigned long
xspirograph_draw (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  Bool free_color = False;
  Bool flip_p = (st->counter & 1);
  int i;

  switch (st->drawstate) {
    case ERASE1:
      /* 5 sec delay before starting the erase */
      st->drawstate = ERASE2;
      /* shouldn't this use the configured long_delay value??? */
      return (st->long_delay == 0 ? 0 : 5000000);

    case ERASE2:
      /* erase, delaying 1/50th sec between frames */
      st->eraser = erase_window(st->dpy, st->window, st->eraser);
      if (st->eraser)
	/* shouldn't this be a configured pause??? */
	return 20000;
      st->drawstate = NEW_LAYER;
      /* just finished erasing -- leave screen black for 1 sec */
      return (st->long_delay == 0 ? 0 : 1000000);

    case DRAW:
      /* most common case put in front */
      for (i = 0; i < 1000; i++) {
        if (go(st, st->radius1, (flip_p ? st->radius2 : -st->radius2),
	       st->distance)) {
	  st->drawstate = NEW_LAYER;
	  break;
	}
      }
      /* Next draw is delayed sleep_time (initialized value)*/
      return st->sub_sleep_time;

    case NEW_LAYER:
      /* Increment counter */
      st->counter++;
      if (st->counter > (2 * st->num_layers)) {
	/* reset to zero, free, and erase next time through */
	st->counter = 0;
	if (free_color)
	  XFreeColors (st->dpy, st->xgwa.colormap, &st->color.pixel, 1, 0);
	st->drawstate = ERASE1;
      } else {
	/* first, third, fifth, ... time through */
	if (!flip_p)
	  pick_new (st);

	new_colors (st);
	st->drawstate = DRAW;
      }
      /* No delay to the next draw */
      return 0;

    default:
      /* OOPS!! */
      fprintf(stderr, "%s: invalid state\n", progname);
      exit(1);
  }

  return st->sub_sleep_time;
  /* notreached */
}


static void
xspirograph_reshape (Display *dpy, Window window, void *closure, 
                 unsigned int w, unsigned int h)
{
  struct state *st = (struct state *) closure;
  XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
}

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

static void
xspirograph_free (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  XFreeGC (dpy, st->draw_gc);
  free (st);
}


static const char *xspirograph_defaults [] = {
  ".background:		black",
  ".foreground:		white",
  "*fpsSolid:		true",
  "*delay:      	5",
  "*subdelay:   	20000",
  "*layers:     	2",
  "*alwaysfinish:	false",
#ifdef HAVE_MOBILE
  "*ignoreRotation:     True",
#endif
  0
};

static XrmOptionDescRec xspirograph_options [] = {   
  { "-delay",           ".delay",               XrmoptionSepArg, 0 },
  { "-subdelay",        ".subdelay",            XrmoptionSepArg, 0 },
  { "-layers",          ".layers",        	XrmoptionSepArg, 0 },
  { "-alwaysfinish",    ".alwaysfinish",	XrmoptionNoArg, "true"},
  { "-noalwaysfinish",  ".alwaysfinish",	XrmoptionNoArg, "false"},
  { 0, 0, 0, 0 }
};

XSCREENSAVER_MODULE ("XSpirograph", xspirograph)
