/*
 * Copyright (c) 2000 by Chris Leger (xrayjones@users.sourceforge.net)
 *
 * xrayswarm - a shameless ripoff of the 'swarm' screensaver on SGI
 *   boxes.
 *
 * Version 1.0 - initial release.  doesn't read any special command-line
 *   options, and only supports the variable 'delay' via Xresources.
 *   (the delay resouces is most useful on systems w/o gettimeofday, in
 *   which case automagical level-of-detail for FPS maintainance can't
 *   be used.)
 *
 *   The code isn't commented, but isn't too ugly. It should be pretty
 *   easy to understand, with the exception of the colormap stuff.
 *
 */
/*
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of the X Consortium shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization
from the X Consortium.
*/

#include <math.h>
#include <sys/time.h>
#include "screenhack.h"
#include "config.h"


/**********************************************************************
 *								       
 * window crap 
 *
 **********************************************************************/

char *progclass="xrayswarm";

char *defaults [] ={
	".background:		black",
	"*delay:		0",
	0
};

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

static unsigned char colors[768];

static Display *dpy;
static Window win;
static GC fgc[256];
static GC cgc;
static int xsize, ysize;
static int xc, yc;
static unsigned long delay;
static float maxx, maxy;

/**********************************************************************
 *								       
 * bug structs & variables
 *
 **********************************************************************/
#define MAX_TRAIL_LEN 60
#define MAX_BUGS 100
#define MAX_TARGETS 10
#define sq(x) ((x)*(x))

#define MAX_FPS 150
#define MIN_FPS 16
#define DESIRED_DT 0.2

typedef struct _sbug {
  float pos[3];
  int hist[MAX_TRAIL_LEN][2];
  float vel[2];
  struct _sbug *closest;
} bug;

#define GRAY_TRAILS 0
#define GRAY_SCHIZO 1
#define COLOR_TRAILS 2
#define RANDOM_TRAILS 3
#define RANDOM_SCHIZO 4
#define COLOR_SCHIZO 5
#define NUM_SCHEMES 6 /* too many schizos; don't use last 2 */  

static float dt = 0.3;
static float targetVel = 0.03;
static float targetAcc = 0.02;
static float maxVel = 0.05;
static float maxAcc = 0.03;
static float noise = 0.01;
static float minVelMultiplier = 0.5;

static int nbugs = -1;
static int ntargets = -1;
static int trailLen = -1;

/* vars dependent on those above */
static float dtInv;
static float halfDtSq;
static float targetVelSq;
static float maxVelSq;
static float minVelSq;
static float minVel;

static bug bugs[MAX_BUGS];
static bug targets[MAX_TARGETS];
static int head = 0;
static int tail = 0;
static int colorScheme = -1;
static float changeProb = 0.08;

static int grayIndex[MAX_TRAIL_LEN];
static int redIndex[MAX_TRAIL_LEN];
static int blueIndex[MAX_TRAIL_LEN];
static int graySIndex[MAX_TRAIL_LEN];
static int redSIndex[MAX_TRAIL_LEN];
static int blueSIndex[MAX_TRAIL_LEN];
static int randomIndex[MAX_TRAIL_LEN];
static int numColors;
static int numRandomColors;

typedef struct {
  float dt;
  float targetVel;
  float targetAcc;
  float maxVel;
  float maxAcc;
  float noise;

  int nbugs;
  int ntargets;
  int trailLen;
  int colorScheme;
  int changeProb;
} bugParams;

bugParams good1 = {
  0.3,  /* dt */
  0.03,  /* targetVel */
  0.02,  /* targetAcc */
  0.05,  /* maxVel */
  0.03,  /* maxAcc */
  0.01,  /* noise */
  -1,    /* nbugs */
  -1,     /* ntargets */
  60,    /* trailLen */
  2,     /* colorScheme */
  0.15  /* changeProb */
};

bugParams *goodParams[] = { 
&good1
};

int numParamSets = 1;

void initCMap(void) {
  int i, n;
  int temp;

  n = 0;

  /* color 0 is black */
  colors[n++] = 0;
  colors[n++] = 0;
  colors[n++] = 0;

  /* color 1 is red */
  colors[n++] = 255;
  colors[n++] = 0;
  colors[n++] = 0;

  /* color 2 is green */
  colors[n++] = 255;
  colors[n++] = 0;
  colors[n++] = 0;

  /* color 3 is blue */
  colors[n++] = 255;
  colors[n++] = 0;
  colors[n++] = 0;

  /* start greyscale colors at 4; 16 levels */
  for (i = 0; i < 16; i++) {
    temp = i*16;
    if (temp > 255) temp = 255;
    colors[n++] = 255 - temp;
    colors[n++] = 255 - temp;
    colors[n++] = 255 - temp;
  }

  /* start red fade at 20; 16 levels */
  for (i = 0; i < 16; i++) {
    temp = i*16;
    if (temp > 255) temp = 255;
    colors[n++] = 255 - temp;
    colors[n++] = 255 - pow(i/16.0+0.001, 0.3)*255;
    colors[n++] = 65 - temp/4;
  }

  /* start blue fade at 36; 16 levels */
  for (i = 0; i < 16; i++) {
    temp = i*16;
    if (temp > 255) temp = 255;
    colors[n++] = 32 - temp/8;
    colors[n++] = 180 - pow(i/16.0+0.001, 0.3)*180;
    colors[n++] = 255 - temp;
  }

  /* random colors start at 52 */
  numRandomColors = MAX_TRAIL_LEN;

  colors[n] = random()&255; n++;
  colors[n] = random()&255; n++;
  colors[n] = colors[n-2]/2 + colors[n-3]/2; n++;

  for (i = 0; i < numRandomColors; i++) {
    colors[n] = (colors[n-3] + (random()&31) - 16)&255; n++;
    colors[n] = (colors[n-3] + (random()&31) - 16)&255; n++;
    colors[n] = colors[n-2]/(float)(i+2) + colors[n-3]/(float)(i+2); n++;
  }
  
  numColors = n/3 + 1;
}

static int initGraphics(void) {
  XGCValues xgcv;
  XWindowAttributes xgwa;
  XSetWindowAttributes xswa;
  Colormap cmap;
  XColor color;
  int n, i;
  
  initCMap();

  XGetWindowAttributes(dpy,win,&xgwa);
  cmap=xgwa.colormap;
  xswa.backing_store=Always;
  XChangeWindowAttributes(dpy,win,CWBackingStore,&xswa);
  xgcv.function=GXcopy;

  delay = get_integer_resource("delay","Integer");
  
  xgcv.foreground=get_pixel_resource ("background", "Background", dpy, cmap);
  fgc[0]=XCreateGC(dpy, win, GCForeground|GCFunction,&xgcv);
  
  n=0;
  if (mono_p) {
    xgcv.foreground=get_pixel_resource ("foreground", "Foreground", dpy, cmap);
    fgc[1]=XCreateGC(dpy,win,GCForeground|GCFunction,&xgcv);
    for (i=0;i<numColors;i+=2) fgc[i]=fgc[0];
    for (i=1;i<numColors;i+=2) fgc[i]=fgc[1];
  } else {
    for (i = 0; i < numColors; i++) {
      color.red=colors[n++]<<8;
      color.green=colors[n++]<<8;
      color.blue=colors[n++]<<8;
      color.flags=DoRed|DoGreen|DoBlue;
      XAllocColor(dpy,cmap,&color);
      xgcv.foreground=color.pixel;
      fgc[i] = XCreateGC(dpy, win, GCForeground | GCFunction,&xgcv);
    }
  }
  cgc = XCreateGC(dpy,win,GCForeground|GCFunction,&xgcv);
  XSetGraphicsExposures(dpy,cgc,False);

  xsize = xgwa.width;
  ysize = xgwa.height;
  xc = xsize >> 1;
  yc = ysize >> 1;

  maxx = 1.0;
  maxy = ysize/(float)xsize;

  if (colorScheme < 0) colorScheme = random()%NUM_SCHEMES;

  return True;
}

static void initBugs(void) {
  register bug *b;
  int i;

  head = tail = 0;

  memset((char *)bugs, 0,MAX_BUGS*sizeof(bug));
  memset((char *)targets, 0, MAX_TARGETS*sizeof(bug));

  if (ntargets < 0) ntargets = (0.25+frand(0.75)*frand(1))*MAX_TARGETS;
  if (ntargets < 1) ntargets = 1;

  if (nbugs < 0) nbugs = (0.25+frand(0.75)*frand(1))*MAX_BUGS;
  if (nbugs <= ntargets) nbugs = ntargets+1;

  if (trailLen < 0) {
    trailLen = (1.0 - frand(0.6)*frand(1))*MAX_TRAIL_LEN;
  }

  if (nbugs > MAX_BUGS) nbugs = MAX_BUGS;
  if (ntargets > MAX_TARGETS) ntargets = MAX_TARGETS;
  if (trailLen > MAX_TRAIL_LEN) trailLen = MAX_TRAIL_LEN;

  b = bugs;
  for (i = 0; i < nbugs; i++, b++) {
    b->pos[0] = frand(maxx);
    b->pos[1] = frand(maxy);
    b->vel[0] = frand(maxVel/2);
    b->vel[1] = frand(maxVel/2);

    b->hist[head][0] = b->pos[0]*xsize;
    b->hist[head][1] = b->pos[1]*xsize;
    b->closest = &targets[random()%ntargets];
  }

  b = targets;
  for (i = 0; i < ntargets; i++, b++) {
    b->pos[0] = frand(maxx);
    b->pos[1] = frand(maxy);

    b->vel[0] = frand(targetVel/2);
    b->vel[1] = frand(targetVel/2);

    b->hist[head][0] = b->pos[0]*xsize;
    b->hist[head][1] = b->pos[1]*xsize;
  }
}

static void pickNewTargets(void) {
  register int i;
  register bug *b;

  b = bugs;
  for (i = 0; i < nbugs; i++, b++) {
    b->closest = &targets[random()%ntargets];
  }
}

#if 0
static void addBugs(int numToAdd) {
  register bug *b;
  int i;

  if (numToAdd + nbugs > MAX_BUGS) numToAdd = MAX_BUGS-nbugs;
  else if (numToAdd < 0) numToAdd = 0;

  for (i = 0; i < numToAdd; i++) {
    b = &bugs[random()%nbugs];
    memcpy((char *)&bugs[nbugs+i], (char *)b, sizeof(bug));
    b->closest = &targets[random()%ntargets];
  }

  nbugs += numToAdd;
}

static void addTargets(int numToAdd) {
  register bug *b;
  int i;

  if (numToAdd + ntargets > MAX_TARGETS) numToAdd = MAX_TARGETS-ntargets;
  else if (numToAdd < 0) numToAdd = 0;

  for (i = 0; i < numToAdd; i++) {
    b = &targets[random()%ntargets];
    memcpy((char *)&targets[ntargets+i], (char *)b, sizeof(bug));
    b->closest = &targets[random()%ntargets];
  }

  ntargets += numToAdd;
}
#endif

static void computeConstants(void) {
  halfDtSq = dt*dt*0.5;
  dtInv = 1.0/dt;
  targetVelSq = targetVel*targetVel;
  maxVelSq = maxVel*maxVel;
  minVel = maxVel*minVelMultiplier;
  minVelSq = minVel*minVel;
}

void computeColorIndices(void) {
  int i;
  int schizoLength;

  /* note: colors are used in *reverse* order! */

  /* grayscale */
  for (i = 0; i < trailLen; i++) {
    grayIndex[trailLen-1-i] = 4 + i*16.0/trailLen + 0.5;
    if (grayIndex[trailLen-1-i] > 19) grayIndex[trailLen-1-i] = 19;
  }

  /* red */
  for (i = 0; i < trailLen; i++) {
    redIndex[trailLen-1-i] = 20 + i*16.0/trailLen + 0.5;
    if (redIndex[trailLen-1-i] > 35) redIndex[trailLen-1-i] = 35;
  }

  /* blue */
  for (i = 0; i < trailLen; i++) {
    blueIndex[trailLen-1-i] = 36 + i*16.0/trailLen + 0.5;
    if (blueIndex[trailLen-1-i] > 51) blueIndex[trailLen-1-i] = 51;
  }

  /* gray schizo - same as gray*/
  for (i = 0; i < trailLen; i++) {
    graySIndex[trailLen-1-i] = 4 + i*16.0/trailLen + 0.5;
    if (graySIndex[trailLen-1-i] > 19) graySIndex[trailLen-1-i] = 19;
  }

  schizoLength = trailLen/4;
  if (schizoLength < 3) schizoLength = 3;
  /* red schizo */
  for (i = 0; i < trailLen; i++) {
    /*    redSIndex[trailLen-1-i] = 
	  20 + 16.0*(i%schizoLength)/(schizoLength-1.0) + 0.5;*/
    redSIndex[trailLen-1-i] = 20 + i*16.0/trailLen + 0.5;
    if (redSIndex[trailLen-1-i] > 35) redSIndex[trailLen-1-i] = 35;
  }

  schizoLength = trailLen/2;
  if (schizoLength < 3) schizoLength = 3;
  /* blue schizo is next */
  for (i = 0; i < trailLen; i++) {
    blueSIndex[trailLen-1-i] = 
      36 + 16.0*(i%schizoLength)/(schizoLength-1.0) + 0.5;
    if (blueSIndex[trailLen-1-i] > 51) blueSIndex[trailLen-1-i] = 51;
  }

  /* random is next */
  for (i = 0; i < trailLen; i++) {
    randomIndex[i] = 52 + random()%(numRandomColors);
  }
}

#if 0
static void setParams(bugParams *p) {
  dt = p->dt;
  targetVel = p->targetVel;
  targetAcc = p->targetAcc;
  maxVel = p->maxVel;
  maxAcc = p->maxAcc;
  noise = p->noise;

  nbugs = p->nbugs;
  ntargets = p->ntargets;
  trailLen = p->trailLen;
  colorScheme = p->colorScheme;
  changeProb = p->changeProb;
  computeConstants();
  computeColorIndices();
}
#endif

static void drawBugs(int *tColorIdx, int tci0, int tnc,
		     int *colorIdx, int ci0, int nc) {
  register bug *b;
  register int i, j;
  int temp;

  if (((head+1)%trailLen) == tail) {
    /* first, erase last segment of bugs if necessary */
    temp = (tail+1) % trailLen;
    
    b = bugs;
    for (i = 0; i < nbugs; i++, b++) {
      XDrawLine(dpy, win, fgc[0],
		b->hist[tail][0], b->hist[tail][1],
		b->hist[temp][0], b->hist[temp][1]);
    }
  
    b = targets;
    for (i = 0; i < ntargets; i++, b++) {
      XDrawLine(dpy, win, fgc[0],
		b->hist[tail][0], b->hist[tail][1],
		b->hist[temp][0], b->hist[temp][1]);
    }
    tail = (tail+1)%trailLen;
  }
  
  for (j = tail; j != head; j = temp) {
    temp = (j+1)%trailLen;

    b = bugs;
    for (i = 0; i < nbugs; i++, b++) {
      XDrawLine(dpy, win, fgc[colorIdx[ci0]],
		b->hist[j][0], b->hist[j][1],
		b->hist[temp][0], b->hist[temp][1]);
    }
    
    b = targets;
    for (i = 0; i < ntargets; i++, b++) {
      XDrawLine(dpy, win, fgc[tColorIdx[tci0]],
		b->hist[j][0], b->hist[j][1],
		b->hist[temp][0], b->hist[temp][1]);
    }
    ci0 = (ci0+1)%nc;  
    tci0 = (tci0+1)%tnc;  
  }
}

static void clearBugs(void) {
  register bug *b;
  register int i, j;
  int temp;

  tail = tail-1;
  if (tail < 0) tail = trailLen-1;

  if (((head+1)%trailLen) == tail) {
    /* first, erase last segment of bugs if necessary */
    temp = (tail+1) % trailLen;
    
    b = bugs;
    for (i = 0; i < nbugs; i++, b++) {
      XDrawLine(dpy, win, fgc[0],
		b->hist[tail][0], b->hist[tail][1],
		b->hist[temp][0], b->hist[temp][1]);
    }
  
    b = targets;
    for (i = 0; i < ntargets; i++, b++) {
      XDrawLine(dpy, win, fgc[0],
		b->hist[tail][0], b->hist[tail][1],
		b->hist[temp][0], b->hist[temp][1]);
    }
    tail = (tail+1)%trailLen;
  }
  
  for (j = tail; j != head; j = temp) {
    temp = (j+1)%trailLen;

    b = bugs;
    for (i = 0; i < nbugs; i++, b++) {
      XDrawLine(dpy, win, fgc[0],
		b->hist[j][0], b->hist[j][1],
		b->hist[temp][0], b->hist[temp][1]);
    }
    
    b = targets;
    for (i = 0; i < ntargets; i++, b++) {
      XDrawLine(dpy, win, fgc[0],
		b->hist[j][0], b->hist[j][1],
		b->hist[temp][0], b->hist[temp][1]);
    }
  }
}

void updateState(void) {
  register int i;
  register bug *b;
  register float ax, ay, temp;
  float theta;
  static int checkIndex = 0;
  bug *b2;
  int j;

  head = (head+1)%trailLen;
  
  for (j = 0; j < 5; j++) {
    /* update closets bug for the bug indicated by checkIndex */
    checkIndex = (checkIndex+1)%nbugs;
    b = &bugs[checkIndex];

    ax = b->closest->pos[0] - b->pos[0];
    ay = b->closest->pos[1] - b->pos[1];
    temp = ax*ax + ay*ay;
    for (i = 0; i < ntargets; i++) {
      b2 = &targets[i];
      if (b2 == b->closest) continue;
      ax = b2->pos[0] - b->pos[0];
      ay = b2->pos[1] - b->pos[1];
      theta = ax*ax + ay*ay;
      if (theta < temp*2) {
	b->closest = b2;
	temp = theta;
      }
    }
  }
  
  /* update target state */

  b = targets;
  for (i = 0; i < ntargets; i++, b++) {
    theta = frand(6.28);
    ax = targetAcc*cos(theta);
    ay = targetAcc*sin(theta);

    b->vel[0] += ax*dt;
    b->vel[1] += ay*dt;

    /* check velocity */
    temp = sq(b->vel[0]) + sq(b->vel[1]);
    if (temp > targetVelSq) {
      temp = targetVel/sqrt(temp);
      /* save old vel for acc computation */
      ax = b->vel[0];
      ay = b->vel[1];

      /* compute new velocity */
      b->vel[0] *= temp;
      b->vel[1] *= temp;
      
      /* update acceleration */
      ax = (b->vel[0]-ax)*dtInv;
      ay = (b->vel[1]-ay)*dtInv;
    }

    /* update position */
    b->pos[0] += b->vel[0]*dt + ax*halfDtSq;
    b->pos[1] += b->vel[1]*dt + ay*halfDtSq;

    /* check limits on targets */
    if (b->pos[0] < 0) {
      /* bounce */
      b->pos[0] = -b->pos[0];
      b->vel[0] = -b->vel[0];
    } else if (b->pos[0] >= maxx) {
      /* bounce */
      b->pos[0] = 2*maxx-b->pos[0];
      b->vel[0] = -b->vel[0];
    }
    if (b->pos[1] < 0) {
      /* bounce */
      b->pos[1] = -b->pos[1];
      b->vel[1] = -b->vel[1];
    } else if (b->pos[1] >= maxy) {
      /* bounce */
      b->pos[1] = 2*maxy-b->pos[1];
      b->vel[1] = -b->vel[1];
    }

    b->hist[head][0] = b->pos[0]*xsize;
    b->hist[head][1] = b->pos[1]*xsize;
  }

  /* update bug state */
  b = bugs;
  for (i = 0; i < nbugs; i++, b++) {
    theta = atan2(b->closest->pos[1] - b->pos[1] + frand(noise),
		  b->closest->pos[0] - b->pos[0] + frand(noise));
    ax = maxAcc*cos(theta);
    ay = maxAcc*sin(theta);

    b->vel[0] += ax*dt;
    b->vel[1] += ay*dt;

    /* check velocity */
    temp = sq(b->vel[0]) + sq(b->vel[1]);
    if (temp > maxVelSq) {
      temp = maxVel/sqrt(temp);

      /* save old vel for acc computation */
      ax = b->vel[0];
      ay = b->vel[1];

      /* compute new velocity */
      b->vel[0] *= temp;
      b->vel[1] *= temp;
      
      /* update acceleration */
      ax = (b->vel[0]-ax)*dtInv;
      ay = (b->vel[1]-ay)*dtInv;
    } else if (temp < minVelSq) {
      temp = minVel/sqrt(temp);

      /* save old vel for acc computation */
      ax = b->vel[0];
      ay = b->vel[1];

      /* compute new velocity */
      b->vel[0] *= temp;
      b->vel[1] *= temp;
      
      /* update acceleration */
      ax = (b->vel[0]-ax)*dtInv;
      ay = (b->vel[1]-ay)*dtInv;
    }

    /* update position */
    b->pos[0] += b->vel[0]*dt + ax*halfDtSq;
    b->pos[1] += b->vel[1]*dt + ay*halfDtSq;

    /* check limits on targets */
    if (b->pos[0] < 0) {
      /* bounce */
      b->pos[0] = -b->pos[0];
      b->vel[0] = -b->vel[0];
    } else if (b->pos[0] >= maxx) {
      /* bounce */
      b->pos[0] = 2*maxx-b->pos[0];
      b->vel[0] = -b->vel[0];
    }
    if (b->pos[1] < 0) {
      /* bounce */
      b->pos[1] = -b->pos[1];
      b->vel[1] = -b->vel[1];
    } else if (b->pos[1] >= maxy) {
      /* bounce */
      b->pos[1] = 2*maxy-b->pos[1];
      b->vel[1] = -b->vel[1];
    }

    b->hist[head][0] = b->pos[0]*xsize;
    b->hist[head][1] = b->pos[1]*xsize;
  }
}

void mutateBug(int which) {
  int i, j;

  if (which == 0) {
    /* turn bug into target */
    if (ntargets < MAX_TARGETS-1 && nbugs > 1) {
      i = random() % nbugs;
      memcpy((char *)&targets[ntargets], (char *)&bugs[i], sizeof(bug));
      memcpy((char *)&bugs[i], (char *)&bugs[nbugs-1], sizeof(bug));
      targets[ntargets].pos[0] = frand(maxx);
      targets[ntargets].pos[1] = frand(maxy);
      nbugs--;
      ntargets++;

      for (i = 0; i < nbugs; i += ntargets) {
	bugs[i].closest = &targets[ntargets-1];
      }
    }
  } else {
    /* turn target into bug */
    if (ntargets > 1 && nbugs < MAX_BUGS-1) {
      /* pick a target */
      i = random() % ntargets;

      /* copy state into a new bug */
      memcpy((char *)&bugs[nbugs], (char *)&targets[i], sizeof(bug));
      ntargets--;

      /* pick a target for the new bug */
      bugs[nbugs].closest = &targets[random()%ntargets];

      for (j = 0; j < nbugs; j++) {
	if (bugs[j].closest == &targets[ntargets]) {
	  bugs[j].closest = &targets[i];
	} else if (bugs[j].closest == &targets[i]) {
	  bugs[j].closest = &targets[random()%ntargets];
	}
      }
      nbugs++;
      
      /* copy the last ntarget into the one we just deleted */
      memcpy(&targets[i], (char *)&targets[ntargets], sizeof(bug));
    }
  }
}

void mutateParam(float *param) {
  *param *= 0.75+frand(0.5);
}

void randomSmallChange(void) {
  int whichCase = 0;
  static int callDepth = 0;

  whichCase = random()%11;

  if (++callDepth > 10) {
    callDepth--;
    return;
  }
  
  switch(whichCase) {
  case 0:
    /* acceleration */
    mutateParam(&maxAcc);
    break;

  case 1:
    /* target acceleration */
    mutateParam(&targetAcc);
    break;

  case 2:
    /* velocity */
    mutateParam(&maxVel);
    break;

  case 3: 
    /* target velocity */
    mutateParam(&targetVel);
    break;

  case 4:
    /* noise */
    mutateParam(&noise);
    break;

  case 5:
    /* minVelMultiplier */
    mutateParam(&minVelMultiplier);
    break;
    
  case 6:
  case 7:
    /* target to bug */
    if (ntargets < 2) break;
    mutateBug(1);
    break;

  case 8:
    /* bug to target */
    if (nbugs < 2) break;
    mutateBug(0);
    if (nbugs < 2) break;
    mutateBug(0);
    break;

  case 9:
    /* color scheme */
    colorScheme = random()%NUM_SCHEMES;
    if (colorScheme == RANDOM_SCHIZO || colorScheme == COLOR_SCHIZO) {
      /* don't use these quite as much */
      colorScheme = random()%NUM_SCHEMES;
    }
    break;

  default:
    randomSmallChange();
    randomSmallChange();
    randomSmallChange();
    randomSmallChange();
  }

  if (minVelMultiplier < 0.3) minVelMultiplier = 0.3;
  else if (minVelMultiplier > 0.9) minVelMultiplier = 0.9;
  if (noise < 0.01) noise = 0.01;
  if (maxVel < 0.02) maxVel = 0.02;
  if (targetVel < 0.02) targetVel = 0.02;
  if (targetAcc > targetVel*0.7) targetAcc = targetVel*0.7;
  if (maxAcc > maxVel*0.7) maxAcc = maxVel*0.7;
  if (targetAcc > targetVel*0.7) targetAcc = targetVel*0.7;
  if (maxAcc < 0.01) maxAcc = 0.01;
  if (targetAcc < 0.005) targetAcc = 0.005;

  computeConstants();
  callDepth--;
}

void randomBigChange(void) {
  static int whichCase = 0;
  static int callDepth = 0;
  int temp;

  whichCase = random()%4;
  
  if (++callDepth > 3) {
    callDepth--;
    return;
  }

  switch(whichCase) {
  case 0:
    /* trail length */
    temp = (random()%(MAX_TRAIL_LEN-25)) + 25;
    clearBugs();
    trailLen = temp;
    computeColorIndices();
    initBugs();
    break;

  case 1:  
    /* Whee! */
    randomSmallChange();
    randomSmallChange();
    randomSmallChange();
    randomSmallChange();
    randomSmallChange();
    randomSmallChange();
    randomSmallChange();
    randomSmallChange();
    break;

  case 2:
    clearBugs();
    initBugs();
    break;
    
  case 3:
    pickNewTargets();
    break;
    
  default:
    temp = random()%ntargets;
    targets[temp].pos[0] += frand(maxx/4)-maxx/8;
    targets[temp].pos[1] += frand(maxy/4)-maxy/8;
    /* updateState() will fix bounds */
    break;
  }

  callDepth--;
}

void updateColorIndex(int **tColorIdx, int *tci0, int *tnc,
		      int **colorIdx, int *ci0, int *nc) {
  switch(colorScheme) {
  case COLOR_TRAILS:
    *tColorIdx = redIndex;
    *tci0 = 0;
    *tnc = trailLen;
    *colorIdx = blueIndex;
    *ci0 = 0;
    *nc = trailLen;
    break;

  case GRAY_SCHIZO:
    *tColorIdx = graySIndex;
    *tci0 = head;
    *tnc = trailLen;
    *colorIdx = graySIndex;
    *ci0 = head;
    *nc = trailLen;
    break;

  case COLOR_SCHIZO:
    *tColorIdx = redSIndex;
    *tci0 = head;
    *tnc = trailLen;
    *colorIdx = blueSIndex;
    *ci0 = head;
    *nc = trailLen;
    break;

  case GRAY_TRAILS:
    *tColorIdx = grayIndex;
    *tci0 = 0;
    *tnc = trailLen;
    *colorIdx = grayIndex;
    *ci0 = 0;
    *nc = trailLen;
    break;

  case RANDOM_TRAILS:
    *tColorIdx = redIndex;
    *tci0 = 0;
    *tnc = trailLen;
    *colorIdx = randomIndex;
    *ci0 = 0;
    *nc = trailLen;
    break;

  case RANDOM_SCHIZO:
    *tColorIdx = redIndex;
    *tci0 = head;
    *tnc = trailLen;
    *colorIdx = randomIndex;
    *ci0 = head;
    *nc = trailLen;
    break;
  }
}

#if HAVE_GETTIMEOFDAY
static struct timeval startupTime;
static void initTime(void) {
#if GETTIMEOFDAY_TWO_ARGS
  gettimeofday(&startupTime, NULL);
#else
  gettimeofday(&startupTime);
#endif
}

static double getTime(void) {
  struct timeval t;
  float f;
#if GETTIMEOFDAY_TWO_ARGS
  gettimeofday(&t, NULL);
#else
  gettimeofday(&t);
#endif
  t.tv_sec -= startupTime.tv_sec;
  f = ((double)t.tv_sec) + t.tv_usec*1e-6;
  return f;
}
#endif

void screenhack(Display *d, Window w) {
  int nframes, i;
  float fps;
  float timePerFrame, elapsed;
  int *targetColorIndex, *colorIndex;
  int targetStartColor, targetNumColors;
  int startColor, numColors;
  double start, end;
  int cnt;
  int sleepCount = 0;
  int delayAccum = 0;

  dpy = d;
  win = w;

  if (!initGraphics()) return;

  computeConstants();
  initBugs();
  initTime();
  computeColorIndices();

  if (changeProb > 0) {
    for (i = random()%5+5; i >= 0; i--) {
      randomSmallChange();
    }
  }

  nframes = 0;
#if HAVE_GETTIMEOFDAY
  start = getTime();
#endif

  while (1) {
    if (delay > 0) {
      cnt = 2;
      dt = DESIRED_DT/2;
    } else {
      cnt = 1;
      dt = DESIRED_DT;
    }

    for (; cnt > 0; cnt--) {
      updateState();
      updateColorIndex(&targetColorIndex, &targetStartColor, &targetNumColors,
		       &colorIndex, &startColor, &numColors);
      drawBugs(targetColorIndex, targetStartColor, targetNumColors,
	       colorIndex, startColor, numColors);
      XSync(dpy, False);
      screenhack_handle_events (dpy);
    }
#if HAVE_GETTIMEOFDAY
    end = getTime();
    nframes++;

    if (end > start+0.5) {
      if (frand(1.0) < changeProb) randomSmallChange();
      if (frand(1.0) < changeProb*0.3) randomBigChange();
      elapsed = end-start;
	
      timePerFrame = elapsed/nframes - delay*1e-6;
      fps = nframes/elapsed;
      /*
      printf("elapsed: %.3f\n", elapsed);
      printf("fps: %.1f  secs per frame: %.3f  delay: %f\n", 
	     fps, timePerFrame, delay);
      */

      if (fps > MAX_FPS) {
	delay = (1.0/MAX_FPS - (timePerFrame + delay*1e-6))*1e6;
      } else if (dt*fps < MIN_FPS*DESIRED_DT) {
	/* need to speed things up somehow */
	if (0 && nbugs > 10) {
	  /*printf("reducing bugs to improve speed.\n");*/
	  clearBugs();
	  nbugs *= fps/MIN_FPS;
	  if (ntargets >= nbugs/2) mutateBug(1);
	} else if (0 && dt < 0.3) {
	  /*printf("increasing dt to improve speed.\n");*/
	  dt *= MIN_FPS/fps;
	  computeConstants();
	} else if (trailLen > 10) {
	  /*printf("reducing trail length to improve speed.\n");*/
	  clearBugs();
	  trailLen = trailLen * (fps/MIN_FPS);
	  if (trailLen < 10) trailLen = 10;
	  computeColorIndices();
	  initBugs();
	}
      }

      start = getTime();
      nframes = 0;
    }
#else
    if (frand(1) < changeProb*2/100.0) randomSmallChange();
    if (frand(1) < changeProb*0.3/100.0) randomBigChange();
#endif
    
    if (delay > 10000) usleep(delay);
    else {
      delayAccum += delay;
      if (delayAccum > 10000) {
	usleep(delayAccum);
	delayAccum = 0;
	sleepCount = 0;
      }
      if (++sleepCount > 2) {
	sleepCount = 0;
	usleep(10000);
      }
    }
  }
}
