/*
 *  @(#) vermiculate.c
 *  @(#) Copyright (C) 2001 Tyler Pierce (tyler@alumni.brown.edu)
 *  The full program, with documentation, is available at:
 *    http://freshmeat.net/projects/fdm
 *
 * 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 "screenhack.h"

#include <ctype.h>
#include <math.h>

#define degs 360
#define degs2 (degs/2)
#define degs4 (degs/4)
#define degs8 (degs/8)
#define dtor 0.0174532925	/*  pi / degs2; */
#define thrmax 120
#define tailmax (thrmax * 2 + 1)
#define tmodes '7'
#define ymax (st->hei - 1)
#define ymin 0
#define xmax (st->wid - 1)
#define xmin 0
#define rlmax 200
#define SPEEDINC 10
#define SPEEDMAX 1000
#define wraparound(VAL,LOWER,UPPER) { 	\
		    if (VAL >= UPPER)	\
		      VAL -= UPPER - LOWER;	\
		    else if (VAL < LOWER)	\
		      VAL += UPPER - LOWER; }
#define arrcpy(DEST,SRC) memcpy (DEST, SRC, sizeof(DEST))

typedef double real;
typedef unsigned char banktype[thrmax];

typedef struct linedata
{
  int deg, spiturn, turnco, turnsize;
  unsigned char col;
  Bool dead;

  char orichar;
  real x, y;
  int tmode, tsc, tslen, tclim, otslen, ctinc, reclen, recpos, circturn, prey,
    slice;
  int xrec[rlmax + 1], yrec[rlmax + 1];
  int turnseq[50];
  Bool filled, killwalls, vhfollow,
    selfbounce, tailfollow, realbounce, little;
}
linedata;

static const struct stringAndSpeed
{
  const char * const str;
  int speed;
}
sampleStrings[] =
{
/*  { "]]]]]]]]7ces123400-9-8#c123456#s9998880004#ma3#car9ma6#c-#r1", 600} , 
	{ "bmarrrr#c1234#lc5678#lyet]", 600} , */
  { "AEBMN222222223#CAR9CAD4CAOV", 150} ,
  { "mn333#c23#f1#]]]]]]]]]]]3bc9#r9#c78#f9#ma4#", 600} ,
  { "AEBMN22222#CAD4CAORc1#f2#c1#r6", 100} , 
/*  { "mn6666666#c1i#f1#y2#sy2#vyety1#ry13i#l", 40} , */
  { "aebmnrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr#", 500} , 
/*  { "bg+++++++++++++++++++++++#mnrrrrrrrrrrrrrrrrrrrrrrr#y1#k", 500},  */
/*  { "BMN22222223#CAD4CAOVYAS", 150}, */
/*  { "aebmnrrrrrrrrrrrrrrrr#yaryakg--#", 100} , */
  { "mn6rrrrrrrrrrrrrrr#by1i#lcalc1#fnyav" , 200 } ,
  { "mn1rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr#by1i#lcalc1#fn", 2000 },
  { "baeMn333333333333333333333333#CerrYerCal", 800 },
  { "baeMn1111111111111111111111111111111111111111111111111111111111#Cer9YesYevYerCal", 1200 },
  { "baMn111111222222333333444444555555#Ct1#lCt2#lCt3#lCt4#lCt5#lCerrYerYet", 1400 },
  { "baMn111111222222333333444444555555#Ct1#lCt2#lCt3#lCt4#lCt5#lCerrYerYetYt1i#lYt1i#sYt1#v", 1400 }
};

struct state {
  Display *dpy;
  Window window;

  GC mygc;
  Colormap mycmap;
  XWindowAttributes xgwa;
  XColor mycolors[tailmax];

  int hei, wid, speed;
  Bool erasing, cleared, autopal;
  char *oinstring;       /* allocated */
  const char *instring;  /* consumed */
  int max_ticks;

  real sinof[degs], cosof[degs], tanof[degs];
  unsigned char *point;

  linedata thread[thrmax];
  banktype bank;
  int bnkt;
  int boxw, boxh, curviness, gridden, ogd, bordcorn;
  unsigned char bordcol, threads;
  char ch, boolop;

  int reset_p;
  int cyc;
  int pscale;
};



static void
consume_instring(struct state *st);

static Bool
wasakeypressed (struct state *st)
{
  if (*st->instring != 0)
    return True;
  else
    return False;
}

static char
readkey (struct state *st)
{
  char readkey_result;
  if (*st->instring == 0)
    {
      readkey_result = '#';
    }
  else
    {
      readkey_result = *st->instring;
      st->instring++;
    };
  return toupper (readkey_result);
}

static unsigned int
random1 (unsigned int i)
{
  return (ya_random () % i);
}

static unsigned long
waitabit (struct state *st)
{
  int result = 0;
  st->cyc += st->threads;
  while (st->cyc > st->speed)
    {
      result += 10000;
      st->cyc -= st->speed;
    }
  return result;
}

static void
clearscreen (struct state *st)
{
  XClearWindow (st->dpy, st->window);
  memset (st->point, 0, st->wid * st->hei);
}

static void
sp (struct state *st, int x, int y, int c)
{
  XSetForeground (st->dpy, st->mygc, st->mycolors[c].pixel);
  XFillRectangle (st->dpy, st->window, st->mygc, x, y, st->pscale, st->pscale);
  st->point[(st->wid * y) + x] = c;
}

static int
gp (struct state *st, int x, int y)
{
  return st->point[(st->wid * y) + x];
}

static void
redraw (struct state *st, int x, int y, int width, int height)
{
  int xc, yc;
  for (xc = x; xc <= x + width - 1; xc++)
    for (yc = y; yc <= y + height - 1; yc++)
      if (st->point[st->wid * yc + xc] != 0)
	sp (st, xc, yc, st->point[st->wid * yc + xc]);
}

static void
palupdate (struct state *st, Bool forceUpdate)
{
  if (forceUpdate || *st->instring == 0)
    {
      redraw (st, xmin, ymin, st->wid, st->hei);
    }
}

static void
randpal (struct state *st)
{
  int ncolors = tailmax - 1;
  make_random_colormap (st->xgwa.screen, st->xgwa.visual, st->mycmap,
                        &st->mycolors[1], &ncolors, True, True, 0, True);
  if (ncolors < tailmax - 1)
    {
      int c;
      for (c = 1; c < tailmax; c++)
	st->mycolors[c].pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
    }
}

static void
gridupdate (struct state *st, Bool interruptible)
{
  int x, y;
  if (st->gridden > 0)
    for (x = 0; x <= xmax && !(wasakeypressed (st) && interruptible); x += st->boxw)
      for (y = 0; y <= ymax; y += st->boxh)
	{
	  if (random1 (15) < st->gridden)
	    {
#define lesser(A,B) ( ((A)<(B)) ? (A) : (B) )
	      int max = lesser (x + st->boxw, xmax);
	      int xc;
	      for (xc = x; xc <= max; xc++)
		sp (st, xc, y, 1);
	    }
	  if (random1 (15) < st->gridden)
	    {
	      int max = lesser (y + st->boxh, ymax);
	      int yc;
	      for (yc = y; yc <= max; yc++)
		sp (st, x, yc, 1);
	    }
	}
}

static void
bordupdate (struct state *st)
{
  int xbord, ybord;

  if (st->bordcorn == 0 || st->bordcorn == 1)
    ybord = ymin;
  else
    ybord = ymax;
  if (st->bordcorn == 0 || st->bordcorn == 3)
    xbord = xmin;
  else
    xbord = xmax;
  {
    int x, y;
    for (x = xmin; x <= xmax; x++)
      sp (st, x, ybord, st->bordcol);
    for (y = ymin; y <= ymax; y++)
      sp (st, xbord, y, st->bordcol);
  }
}

static Bool
inbank (struct state *st, unsigned char thr)
{
  int c;
  if (st->bnkt > 0)
    for (c = 1; c <= st->bnkt; c++)
      if (st->bank[c - 1] == thr)
	return True;
  return False;
}

static void
pickbank (struct state *st)
{
  unsigned char thr = 1;
  st->bnkt = 0;
  st->ch = '\0';
  do
    {
      while (inbank (st, thr))
	thr = thr % st->threads + 1;

      palupdate (st, False);
      st->ch = readkey (st);
      palupdate (st, False); 
      switch (st->ch)
	{
	case '+':
	case '-':
	  do
	    {
	      if (st->ch == '+')
		thr++;
	      else
		thr--;
	      wraparound (thr, 1, st->threads + 1);
	    }
	  while (inbank (st, thr));
	  break;
	case ' ':
	  st->bank[++st->bnkt - 1] = thr;
	  break;
	case '1': case '2': case '3':
        case '4': case '5': case '6':
        case '7': case '8': case '9':

	  st->bank[++st->bnkt - 1] = st->ch - '0';
	  if (st->bank[st->bnkt - 1] > st->threads)
	    st->bnkt--;
	  break;
	case 'I':
	  {
	    banktype tbank;
	    int tbankt = 0;
	    int c;
	    for (c = 1; c <= st->threads; c++)
	      if (!inbank (st, c))
		tbank[++tbankt - 1] = c;
	    st->bnkt = tbankt;
	    arrcpy (st->bank, tbank);
	  }
	  break;
	case 'T':
	  st->ch = readkey (st);
	  switch (st->ch)
	    {
	    case '1': case '2': case '3':
            case '4': case '5': case '6':
            case '7': case '8': case '9':

	      {
		int c;
		for (c = 1; c <= st->threads; c++)
		  if (st->thread[c - 1].tmode == st->ch - '0')
		    st->bank[++st->bnkt - 1] = c;
	      }
	      break;
	    }
	  break;
	case 'A':
	  for (st->bnkt = 1; st->bnkt <= st->threads; st->bnkt++)
	    st->bank[st->bnkt - 1] = st->bnkt;
	  st->bnkt = st->threads;
	  break;
	case 'E':
	  for (st->bnkt = 1; st->bnkt <= thrmax; st->bnkt++)
	    st->bank[st->bnkt - 1] = st->bnkt;
	  st->bnkt = thrmax;
	  break;
	}
    }
  while (!(st->bnkt >= st->threads || st->ch == 'N' || st->ch == '\15' || st->ch == '#'));
  if (st->bnkt == 0 && st->ch != 'N')
    {
      st->bnkt = 1;
      st->bank[0] = thr;
    }
  palupdate (st, False);
}

static void
bankmod (char boolop, Bool * Bool_)
{
  switch (boolop)
    {
    case 'T':
      *Bool_ = !*Bool_;
      break;
    case 'Y':
      *Bool_ = True;
      break;
    case 'N':
      *Bool_ = False;
      break;
    }
}

static void
newonscreen (struct state *st, unsigned char thr)
{
  linedata *LP = &st->thread[thr - 1];
  LP->filled = False;
  LP->dead = False;
  LP->reclen = (LP->little) ? 
	random1 (10) + 5 : random1 (rlmax - 30) + 30;
  LP->deg = random1 (degs);
  LP->y = random1 (st->hei);
  LP->x = random1 (st->wid);
  LP->recpos = 0;
  LP->turnco = 2;
  LP->turnsize = random1 (4) + 2;
}

static void
firstinit (struct state *st, unsigned char thr)
{
  linedata *LP = &st->thread[thr - 1];
  LP->col = thr + 1;
  LP->prey = 0;
  LP->tmode = 1;
  LP->slice = degs / 3;
  LP->orichar = 'R';
  LP->spiturn = 5;
  LP->selfbounce = False;
  LP->realbounce = False;
  LP->vhfollow = False;
  LP->tailfollow = False;
  LP->killwalls = False;
  LP->little = False;
  LP->ctinc = random1 (2) * 2 - 1;
  LP->circturn = ((thr % 2) * 2 - 1) * ((thr - 1) % 7 + 1);
  LP->tsc = 1;
  LP->tslen = 6;
  LP->turnseq[0] = 6;
  LP->turnseq[1] = -6;
  LP->turnseq[2] = 6;
  LP->turnseq[3] = 6;
  LP->turnseq[4] = -6;
  LP->turnseq[5] = 6;
  LP->tclim = (unsigned char) (((real) degs) / 2 / 12);
}

static void
maininit (struct state *st)
{
  if (!st->instring)
    {
      int n = random1 (sizeof (sampleStrings) / sizeof (sampleStrings[0]));
      if (st->oinstring) free (st->oinstring);
      st->instring = st->oinstring = strdup (sampleStrings[n].str);
      st->speed = sampleStrings[n].speed;
    }
  st->boxh = 10;
  st->boxw = 10;
  st->gridden = 0;
  st->bordcorn = 0;
  st->threads = 4;
  st->curviness = 30;
  st->bordcol = 1;
  st->ogd = 8;
  st->ch = '\0';
  st->erasing = True;
  {
    unsigned char thr;
    for (thr = 1; thr <= thrmax; thr++)
      {
	firstinit (st, thr);
	newonscreen (st, thr);
      }
  }
  {
    int d;
    for (d = degs - 1; d >= 0; d--)
      {
	st->sinof[d] = sin (d * dtor);
	st->cosof[d] = cos (d * dtor);
	if (d % degs4 == 0)
	  st->tanof[d] = st->tanof[d + 1];
	else
	  st->tanof[d] = tan (d * dtor);
      }
  }
  randpal (st);
}

static Bool
move (struct state *st, unsigned char thr)
{
  linedata *LP = &st->thread[thr - 1];
  if (LP->dead)
    return (False);
  if (LP->prey == 0)
    switch (LP->tmode)
      {
      case 1:
	LP->deg += random1 (2 * LP->turnsize + 1) - LP->turnsize;
	break;
      case 2:
	if (LP->slice == degs || LP->slice == degs2 || LP->slice == degs4)
	  {
	    if (LP->orichar == 'D')
	      {
		if (LP->deg % degs4 != degs8)
		  LP->deg = degs4 * random1 (4) + degs8;
	      }
	    else if (LP->orichar == 'V')
	      if (LP->deg % degs4 != 0)
		LP->deg = degs4 * random1 (4);
	  }
	if (random1 (100) == 0)
	  {
	    if (LP->slice == 0)
	      LP->deg = LP->deg - degs4 + random1 (degs2);
	    else
	      LP->deg += (random1 (2) * 2 - 1) * LP->slice;
	  }
	break;
      case 3:
	LP->deg += LP->circturn;
	break;
      case 4:
	if (abs (LP->spiturn) > 11)
	  LP->spiturn = 5;
	else
	  LP->deg += LP->spiturn;
	if (random1 (15 - abs (LP->spiturn)) == 0)
	  {
	    LP->spiturn += LP->ctinc;
	    if (abs (LP->spiturn) > 10)
	      LP->ctinc *= -1;
	  }
	break;
      case 5:
	LP->turnco = abs (LP->turnco) - 1;
	if (LP->turnco == 0)
	  {
	    LP->turnco = st->curviness + random1 (10);
	    LP->circturn *= -1;
	  }
	LP->deg += LP->circturn;
	break;
      case 6:
	if (abs (LP->turnco) == 1)
	  LP->turnco *= -1 * (random1 (degs2 / abs (LP->circturn)) + 5);
	else if (LP->turnco == 0)
	  LP->turnco = 2;
	else if (LP->turnco > 0)
	  {
	    LP->turnco--;
	    LP->deg += LP->circturn;
	  }
	else
	  LP->turnco++;
	break;
      case 7:
	LP->turnco++;
	if (LP->turnco > LP->tclim)
	  {
	    LP->turnco = 1;
	    LP->tsc = (LP->tsc % LP->tslen) + 1;
	  }
	LP->deg += LP->turnseq[LP->tsc - 1];
	break;
      }
  else
    {
      int desdeg;
      real dy, dx;
      if (LP->tailfollow || LP->prey == thr)
	{
	  dx = st->thread[LP->prey - 1].xrec[st->thread[LP->prey - 1].recpos] - LP->x;
	  dy = st->thread[LP->prey - 1].yrec[st->thread[LP->prey - 1].recpos] - LP->y;
	}
      else
	{
	  dx = st->thread[LP->prey - 1].x - LP->x;
	  dy = st->thread[LP->prey - 1].y - LP->y;
	}
      desdeg =
	(LP->vhfollow) ?
	((fabs (dx) > fabs (dy)) ?
	 ((dx > 0) ?
	  0 * degs4
	  :
	  2 * degs4)
	 :
	 ((dy > 0) ?
	  1 * degs4
	  :
	  3 * degs4))
	:
	((dx > 0) ?
	 ((dy > 0) ?
	  1 * degs8 : 7 * degs8) : ((dy > 0) ? 3 * degs8 : 5 * degs8));
      if (desdeg - desdeg % degs4 != LP->deg - LP->deg % degs4
	  || LP->vhfollow)
	{
	  if (!LP->vhfollow)
           { 
              /* Using atan2 here doesn't seem to slow things down: */
              desdeg = atan2 (dy, dx) / dtor;
              wraparound (desdeg, 0, degs);
	   }
	  if (abs (desdeg - LP->deg) <= abs (LP->circturn))
	    LP->deg = desdeg;
	  else
	    LP->deg +=
	      (desdeg > LP->deg) ?
	      ((desdeg - LP->deg > degs2) ?
	       -abs (LP->circturn) : abs (LP->circturn))
	      : ((LP->deg - desdeg > degs2) ?
		 abs (LP->circturn) : -abs (LP->circturn));
	}
      else
	LP->deg +=
	  (st->tanof[LP->deg] >
	   dy / dx) ? -abs (LP->circturn) : abs (LP->circturn);
    }

  wraparound (LP->deg, 0, degs);
  {
    unsigned char oldcol;
    real oldy = LP->y, oldx = LP->x;
    LP->x += st->cosof[LP->deg];
    wraparound (LP->x, xmin, xmax + 1);
    LP->y += st->sinof[LP->deg];
    wraparound (LP->y, ymin, ymax + 1);
#define xi ((int) LP->x)
#define yi ((int) LP->y)

    oldcol = gp (st, xi, yi);
    if (oldcol != 0)
      {
	Bool vertwall = False, horiwall = False;
	if (oldcol == 1 && ((LP->killwalls && st->gridden > 0) || LP->realbounce))
	  {
	    vertwall = (gp (st, xi, (int) oldy) == 1);
	    horiwall = (gp (st, (int) oldx, yi) == 1);
	  }
	if (oldcol == 1 && LP->realbounce && (vertwall || horiwall))
	  {
	    if (vertwall)
	      LP->deg = -LP->deg + degs2;
	    else
	      LP->deg = -LP->deg;
	  }
	else
	  {
	    if ((oldcol != LP->col && LP->realbounce)
		|| (oldcol == LP->col && LP->selfbounce))
	      LP->deg += degs4 * (random1 (2) * 2 - 1);
	    else if (oldcol != LP->col)
	      LP->deg += degs2;
	  }
	if (LP->killwalls && st->gridden > 0 && oldcol == 1)
	  {
	    if (vertwall && xi + 1 <= xmax)
	      {
		int yy;
		for (yy = yi - yi % st->boxh;
		     yy <= yi - yi % st->boxh + st->boxh && yy <= ymax; yy++)
		  if (gp (st, xi + 1, yy) != 1 || yy == ymax)
		    sp (st, xi, yy, 0);
	      }
	    if (horiwall && yi + 1 <= ymax)
	      {
		int xx;
		for (xx = xi - xi % st->boxw;
		     xx <= xi - xi % st->boxw + st->boxw && xx <= xmax; xx++)
		  if (gp (st, xx, yi + 1) != 1 || xx == xmax)
		    sp (st, xx, yi, 0);
	      }
	  }
	if (oldcol != LP->col || LP->selfbounce)
	  {
	    LP->x = oldx;
	    LP->y = oldy;
	  }
	wraparound (LP->deg, 0, degs);
      }
  }

  sp (st, xi, yi, LP->col);
  if (LP->filled)
    {
      if (st->erasing)
	sp (st, LP->xrec[LP->recpos], LP->yrec[LP->recpos], 0);
      else
	sp (st, LP->xrec[LP->recpos], LP->yrec[LP->recpos], LP->col + thrmax);
    }
  LP->yrec[LP->recpos] = yi;
  LP->xrec[LP->recpos] = xi;
  if (LP->recpos == LP->reclen - 1)
    LP->filled = True;
  if (LP->filled && !st->erasing)
    {
      int co = LP->recpos;
      LP->dead = True;
      do
	{
	  int nextco = co + 1;
	  wraparound (nextco, 0, LP->reclen);
	  if (LP->yrec[co] != LP->yrec[nextco]
	      || LP->xrec[co] != LP->xrec[nextco])
	    LP->dead = False;
	  co = nextco;
	}
      while (!(!LP->dead || co == LP->recpos));
    }
  LP->recpos++;
  wraparound (LP->recpos, 0, LP->reclen);
  return (!LP->dead);
}

static unsigned long
vermiculate_draw (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  int had_instring = (st->instring != 0);
  int tick = 0;
  int this_delay = 0;
  int loop = 0;

  
 AGAIN:
  if (st->reset_p) 
    {
      st->reset_p = 0;

      clearscreen (st);
      {
	unsigned char thr;
	for (thr = 1; thr <= st->threads; thr++)
	  newonscreen (st, thr);
      }
      if (st->autopal)
	{
	  randpal (st);
	  palupdate (st, False);
	}
      bordupdate (st);
      gridupdate (st, False);
    }

  {
    Bool alltrap = True;
    unsigned char thr;
    for (thr = 1; thr <= st->threads; thr++)
      if (move (st, thr))
        alltrap = False;
    if (alltrap)	/* all threads are trapped */
      st->reset_p = True;
    if (st->speed != SPEEDMAX)
      this_delay = waitabit (st);
  }

  if (tick++ > st->max_ticks && !had_instring)
    {
      tick = 0;
      if (st->oinstring) free (st->oinstring);
      st->instring = st->oinstring = 0;
      maininit(st);
      st->reset_p = True;
      st->autopal = False;
    }

  if (this_delay == 0 && loop++ < 1000)
    goto AGAIN;

  return this_delay;
}

static void *
vermiculate_init (Display *d, Window w)
{
  struct state *st = (struct state *) calloc (1, sizeof(*st));
  st->dpy = d;
  st->window = w;
  st->reset_p = 1;
  st->oinstring = get_string_resource (st->dpy, "instring", "Instring");
  if (st->oinstring && !*st->oinstring) {
    free (st->oinstring);
    st->oinstring = 0;
  }
  st->instring = st->oinstring;

  st->max_ticks = get_integer_resource (st->dpy, "ticks", "Integer");
  st->speed = get_integer_resource (st->dpy, "speed", "Speed");
  if (st->speed <= 0) st->speed = 1;
  st->mycolors[0].pixel = BlackPixel (st->dpy, DefaultScreen (st->dpy));

  XSetWindowBackground (st->dpy, st->window,
			BlackPixel (st->dpy, DefaultScreen (st->dpy)));
  {
    XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
    st->wid = st->xgwa.width;
    st->hei = st->xgwa.height;
    st->mycmap = st->xgwa.colormap;
  }
  {
    XGCValues mygcv;
    st->mygc = XCreateGC (st->dpy, st->window, 0, &mygcv);
  }

  st->pscale = 1;
  if (st->xgwa.width > 2560 || st->xgwa.height > 2560)
    st->pscale = 3;  /* Retina displays */
  /* We aren't increasing the spacing between the pixels, just the size. */

  st->point = (unsigned char *) calloc (1, st->wid * st->hei);
  maininit (st);
  palupdate (st, True);
  consume_instring(st);
  return st;
}


static void
vermiculate_reshape (Display *dpy, Window window, void *closure, 
                 unsigned int w, unsigned int h)
{
  struct state *st = (struct state *) closure;
  st->wid = w;
  st->hei = h;
  free (st->point);
  st->point = (unsigned char *) calloc (1, st->wid * st->hei);
}

static Bool
vermiculate_event (Display *dpy, Window window, void *closure, XEvent *event)
{
  struct state *st = (struct state *) closure;
  if (screenhack_event_helper (dpy, window, event))
    {
      st->reset_p = 1;
      return True;
    }
  return False;
}

static void
consume_instring(struct state *st)
{
  char boolop;

	  while (wasakeypressed (st))
	    {
	      st->ch = readkey (st);
	      switch (st->ch)
		{
		case 'M':
		  st->ch = readkey (st);
		  switch (st->ch)
		    {
		    case 'A':
		    case 'N':
		      {
			unsigned char othreads = st->threads;
			if (st->ch == 'N')
			  st->threads = 0;
			do
			  {
			    st->ch = readkey (st);
			    switch (st->ch)
			      {
                              case '1': case '2': case '3':
                              case '4': case '5': case '6':
                              case '7': case '8': case '9':
				st->thread[++st->threads - 1].tmode = st->ch - '0';
				break;
			      case 'R':
				st->thread[++st->threads - 1].tmode =
				  random1 (tmodes - '0') + 1;
				break;
			      }
			  }
			while (!(st->ch == '\15' || st->ch == '#'
				 || st->threads == thrmax));
			if (st->threads == 0)
			  st->threads = othreads;
			st->reset_p = 1;
		      }
		      break;
		    }
		  break;
		case 'C':
		  pickbank (st);
		  if (st->bnkt > 0)
		    {
		      st->ch = readkey (st);
		      switch (st->ch)
			{
			case 'D':
			  st->ch = readkey (st);
			  switch (st->ch)
			    {
                            case '1': case '2': case '3':
                            case '4': case '5': case '6':
                            case '7': case '8': case '9':
/* Careful!  The following macro needs to be at the beginning of any
block in which it's invoked, since it declares variables: */
#define forallinbank(LDP) linedata *LDP; int bankc; \
		for (bankc = 1;	\
		((bankc <= st->bnkt) ? ( \
			(LDP = &st->thread[st->bank[bankc - 1] - 1],	1) \
			) : 0) ; bankc++)
			      {
				forallinbank (L) L->slice = degs / (st->ch - '0');
			      }
			      break;
			    case 'M':
			      {
				forallinbank (L) L->slice = 0;
			      }
			      break;
			    }
			  break;
			case 'S':
			  {
			    forallinbank (L)
			    {
			      L->otslen = L->tslen;
			      L->tslen = 0;
			    }
			  }
			  do
			    {
			      char oldch = st->ch;
			      st->ch = readkey (st);
			      {
				forallinbank (L)
				{
				  switch (st->ch)
				    {
                                    case '0':
                                    case '1': case '2': case '3':
                                    case '4': case '5': case '6':
                                    case '7': case '8': case '9':
				      L->tslen++;
				      L->turnseq[L->tslen - 1] = st->ch - '0';
				      if (oldch == '-')
					L->turnseq[L->tslen - 1] *= -1;
				      if (bankc % 2 == 0)
					L->turnseq[L->tslen - 1] *= -1;
				      break;
				    }
				}
			      }
			    }
			  while (!(st->ch == '\15' || st->ch == '#'
				   || st->thread[st->bank[0] - 1].tslen == 50));
			  {
			    forallinbank (L)
			    {
			      int seqSum = 0, c;

			      if (L->tslen == 0)
				L->tslen = L->otslen;
			      for (c = 1; c <= L->tslen; c++)
				seqSum += L->turnseq[c - 1];
			      if (seqSum == 0)
				L->tclim = 1;
			      else
				L->tclim =
				  (int) (((real) degs2) / abs (seqSum));
			      L->tsc = random1 (L->tslen) + 1;
			    }
			  }
			  break;
			case 'T':
			  {
			    st->ch = readkey (st);
			    {
			      forallinbank (L)
			      {
				switch (st->ch)
				  {
                                  case '1': case '2': case '3':
                                  case '4': case '5': case '6':
                                  case '7': case '8': case '9':
				    L->tmode = st->ch - '0';
				    break;
				  case 'R':
				    L->tmode = random1 (tmodes - '0') + 1;
				    break;
				  }
			      }
			    }
			  }
			  break;
			case 'O':
			  st->ch = readkey (st);
			  {
			    forallinbank (L) L->orichar = st->ch;
			  }
			  break;
			case 'F':
			  {
			    banktype fbank;
			    arrcpy (fbank, st->bank);
			    {
			      int fbnkt = st->bnkt;
			      int bankc;
			      pickbank (st);
			      for (bankc = 1; bankc <= fbnkt; bankc++)
				{
				  linedata *L = &st->thread[fbank[bankc - 1] - 1];
				  if (st->ch == 'N')
				    L->prey = 0;
				  else
				    L->prey = st->bank[0 + (bankc - 1) % st->bnkt];
				}
			    }
			  }
			  break;
			case 'L':
			  {
			    forallinbank (L) L->prey = st->bank[bankc % st->bnkt];
			  }
			  break;
			case 'R':
			  st->ch = readkey (st);
			  {
			    forallinbank (L) switch (st->ch)
			      {
                              case '1': case '2': case '3':
                              case '4': case '5': case '6':
                              case '7': case '8': case '9':
				L->circturn = 10 - (st->ch - '0');
				break;
			      case 'R':
				L->circturn = random1 (7) + 1;
				break;
			      }
			  }
			  break;
			}
		    }
		  break;
		case 'T':
		case 'Y':
		case 'N':
		  boolop = st->ch;
		  pickbank (st);
		  if (st->bnkt > 0)
		    {
		      st->ch = readkey (st);
		      {
			forallinbank (L)
			{
			  switch (st->ch)
			    {
			    case 'S':
			      bankmod (boolop, &L->selfbounce);
			      break;
			    case 'V':
			      bankmod (boolop, &L->vhfollow);
			      break;
			    case 'R':
			      bankmod (boolop, &L->realbounce);
			      break;
			    case 'L':
			      bankmod (boolop, &L->little);
			      st->cleared = True;
			      break;
			    case 'T':
			      bankmod (boolop, &L->tailfollow);
			      break;
			    case 'K':
			      bankmod (boolop, &L->killwalls);
			      break;
			    }
			}
		      }
		    }
		  break;
		case 'R':
		  if (st->bordcol == 1)
		    {
		      st->bordcol = 0;
		      bordupdate (st);
		      st->bordcorn = (st->bordcorn + 1) % 4;
		      st->bordcol = 1;
		      bordupdate (st);
		    }
		  break;
                case '1': case '2': case '3':
                case '4': case '5': case '6':
                case '7': case '8': case '9':
		  {
		    int c;
		    for (c = 1; c <= thrmax; c++)
		      st->thread[c - 1].tmode = st->ch - '0';
		  }
		  break;
		case '\40':
		  st->cleared = True;
		  break;
		case 'E':
		  st->erasing = !st->erasing;
		  break;
		case 'P':
		  randpal (st);
		  palupdate (st, True);
		  break;
		case 'G':
		  {
		    char dimch = 'B';
		    Bool gridchanged = True;
		    if (st->gridden == 0)
		      st->gridden = st->ogd;
		    do
		      {
			int msize = 0;
			if (gridchanged)
			  {
			    clearscreen (st);
			    gridupdate (st, True);
			  }
			st->ch = readkey (st);
			gridchanged = True;
			switch (st->ch)
			  {
			  case '+':
			    msize = 1;
			    break;
			  case '-':
			    msize = -1;
			    break;
			  case ']':
			    if (st->gridden < 15)
			      st->gridden++;
			    break;
			  case '[':
			    if (st->gridden > 0)
			      st->gridden--;
			    break;
			  case 'O':
			    st->ogd = st->gridden;
			    st->gridden = 0;
			    break;
			  case 'S':
			    st->boxw = st->boxh;
			  case 'W':
			  case 'H':
			  case 'B':
			    dimch = st->ch;
			    break;
			  default:
			    gridchanged = False;
			  }
			if (dimch == 'W' || dimch == 'B')
			  st->boxw += msize;
			if (dimch == 'H' || dimch == 'B')
			  st->boxh += msize;
			if (st->boxw == 0)
			  st->boxw = 1;
			if (st->boxh == 0)
			  st->boxh = 1;
		      }
		    while (!(st->ch == '\15' || st->ch == '#' || st->ch == 'O'));
		    st->cleared = True;
		  }
		  break;
		case 'A':
		  st->autopal = !st->autopal;
		  break;
		case 'B':
		  st->bordcol = 1 - st->bordcol;
		  bordupdate (st);
		  break;
		case '-':
		  st->speed -= SPEEDINC;
		  if (st->speed < 1)
		    st->speed = 1;
		  break;
		case '+':
		  st->speed += SPEEDINC;
		  if (st->speed > SPEEDMAX)
		    st->speed = SPEEDMAX;
		  break;
		case '/':
		  if (st->curviness > 5)
		    st->curviness -= 5;
		  break;
		case '*':
		  if (st->curviness < 50)
		    st->curviness += 5;
		  break;
		case ']':
		  if (st->threads < thrmax)
		    newonscreen (st, ++st->threads);
		  break;
		case '[':
		  if (st->threads > 1)
		    {
		      linedata *L = &st->thread[st->threads - 1];
		      int lastpos = (L->filled) ? L->reclen - 1 : L->recpos;
		      int c;
		      for (c = 0; c <= lastpos; c++)
			sp (st, L->xrec[c], L->yrec[c], 0);
		      st->threads--;
		    }
		  break;
		}
	    }
}

static void
vermiculate_free (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  XFreeGC (dpy, st->mygc);
  if (st->oinstring) free (st->oinstring);
  if (st->point) free(st->point);
  free (st);
}


static const char *vermiculate_defaults[] = {
/*  ".lowrez: true", */
  ".background: Black",
  "*ticks: 20000",
  "*fpsSolid:	true",
  "*speed: 1",
  "*instring: ",
#ifdef HAVE_MOBILE
  "*ignoreRotation: True",
#endif
  0
};

static XrmOptionDescRec vermiculate_options[] = {
  {"-speed", ".speed", XrmoptionSepArg, 0},
  {"-instring", ".instring", XrmoptionSepArg, 0},
  {0, 0, 0, 0}
};


XSCREENSAVER_MODULE ("Vermiculate", vermiculate)
