/* circles.c - Draw circles whose color depends on the time of day.
   Jim Blandy <jimb@occs.cs.oberlin.edu> - March 1992.  
   Modified by Karl Fogel <kfogel@red-bean.com>, 1996. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <sys/time.h>
#include <math.h>

#if	defined(vms)
#include <types.h>
#include <time.h>
#else
#include <sys/types.h>
#if	defined(USG) || defined(FTIME_MISSING)
/*
**  If you need to do a tzset() call to set the
**  timezone, and don't have ftime().
*/
struct timeb {
  time_t		time;		/* Seconds since the epoch	*/
  unsigned short	millitm;	/* Field not used		*/
  short		timezone;
  short		dstflag;	/* Field not used		*/
};
#else
#include <sys/timeb.h>
#endif	/* defined(USG) || defined(FTIME_MISSING) */
#if	defined(BSD4_2) || defined(BSD4_1C)
#include <sys/time.h>
#else
#include <time.h>
#endif	/* defined(BSD4_2) */
#endif	/* defined(vms) */

/* On some systems -- DECStations that us the C library distributed
 * with Ultrix, for example -- [s]rand() has cyclic properties in the
 * lower bits that cause the worm to get stuck in certain orbits.
 * Therefore, we use [s]random instead, but #define them so porting to
 * systems without [s]random is easier.
 */
#define RANDOM() random ()
#define SRANDOM(x) srandom (x)

/* What is the chance that the worm will jump to another orbit? */
#define JUMP_PROB 2

extern char *strcpy (char *dest, const char *src);
extern char *getenv (const char *var);
  
char *progname;

char *disp_name = NULL;

/* Note: the code is not actually as general as this implies: */ 
#define NUM_CIRCLES 3       
/* From these two, deduce that there a 2-pixel border: */
#define INTERIOR_WIDTH 10   
#define BORDER_WIDTH   14

/* These two tell a worm which way to slither. */
#define CLOCKWISE        0
#define COUNTERCLOCKWISE 1

/* This tells a draw_worm() whether to draw its whole body or just the
   current segment. */
#define FROM_EXPOSURE     1
#define NOT_FROM_EXPOSURE 0

#define SQUARE(x) ((x) * (x))

#define USECSPERSEC (1000 * 1000)

/* The amount of time in which the circles should go through their
   entire cycle, in microseconds - defaults to one hour.  */
long long period = ((long long) 60 * 60 * USECSPERSEC);

/* A time at which the cycle should be at its starting position.
   By default, aligned with the hour. */
char *ascii_phase = "12:00";

/* The above, converted to microseconds since the epoch.  */
long long phase;

Display *disp;
int screen;
Window root;
GC circle_gc, border_gc;
Colormap default_cmap;
unsigned long circle_pixel;
unsigned long border_pixel;

/* A worm slithers along in the trough created by the inner and
 * outer edges of each circle.  There are three circles, but only
 * one trough, because of the way the circles's edges intersect.
 * 
 * The worm will keep coming to "forks in the road" -- points where
 * it is simultaneously in the orbits of two different circles.  Does
 * it stay orbiting the current one, or does it get "captured" by the
 * other one?  It flips an N-sided coin, of course.  (The worm can
 * tell when it's at a fork because then it's equidistant from the
 * centers of the two different circles.)
 *
 * Because there are an odd number of circles, the worm will, if left
 * slithering long enough, eventually get an opportunity to travel in
 * both directions along every circle.
 *
 * But none of this happens unless you pass the "-worm <LENGTH>" option.
 */
struct worm {
  int *center_x;       /* center of worm's image (X) */
  int *center_y;       /* center of worm's image (Y) */
  int length;          /* how long a worm izzit? */
  int idx;             /* index into above arrays */
  int radius;          /* radius of worm images */
  int sol;             /* center of worm's orbit (indexes in arrays below) */
  int last_sol;        /* history for above */
  int au;              /* radius of worm's orbit */
  int direction;       /* CLOCKWISE or COUNTERCLOCKWISE */

  /* (Hey, I have it on good authority that mixing metaphors does not
     affect the efficiency of compiled code.) */

  /* The centers of the three possible orbits.  We store them here
     because they get regenerated by draw_circle every time, and even
     though it's unlikely that they would ever change after the first
     call, we do want to be _sure_ of staying current. */
  int sol_x[NUM_CIRCLES];
  int sol_y[NUM_CIRCLES];

  /* Bookkeeping. */
  GC fore_gc, back_gc;
  Colormap default_cmap;
};

#define array_length(array) (sizeof (array) / sizeof ((array)[0]))

void give_usage (void);
void draw_circles (struct worm *worm, int from_exposure_p);
void place_worm (struct worm *worm);
void move_worm (struct worm *worm);
void draw_worm (struct worm *worm, int from_exposure_p);
void tick (struct timeval *t);
void set_color (int);
int strcmp_abbrev (char *abbrev, char *model);


int
main (argc, argv)
     int argc;
     char **argv;
{
  struct worm *worm = NULL;
  progname = argv[0];

  for (argc--, argv++; argc>0; argc--, argv++)
  {
    if (argv[0][0] == '-')
    {
      if (strcmp_abbrev (argv[0], "-display") == 0)
      {
        if (argc <= 1)
          give_usage ();
        disp_name = argv[1];
        argc--, argv++;
      }
      else if (strcmp_abbrev (argv[0], "-phase") == 0)
      {
        if (argc <= 1)
          give_usage ();
        
        ascii_phase = argv[1];
        argc--, argv++;
      }
      else if (strcmp_abbrev (argv[0], "-period") == 0)
      {
        /* The argument to the -period parameter can contain a decimal
           point.  Check it and parse the whole thing.  */
        char *secs;
        char *usecs = NULL;
        char *scan;
        
        if (argc <= 1)
          give_usage ();
        
        secs = (char *) alloca (strlen (argv[1]) + 10);
        secs = strcpy (secs, argv[1]);
        
        for (scan = secs; *scan != '\0'; scan++)
          switch (*scan)
          {
          case '0': case '1': case '2': case '3': case '4':
          case '5': case '6': case '7': case '8': case '9':
            break;
            
          case '.':
            *scan = '\0';
            usecs = scan + 1;
            break;
            
          default:
            give_usage ();
          }
        
        /* If there is no decimal point, start usecs after the '\0'.  */
        if (!usecs)
          usecs = ++scan;
        
        /* Add zeros until usecs has six digits.  */
        while (scan < usecs + 6)
          *scan++ = '0';
        
        /* Accept no more than six digits.  */
        usecs[6] = '\0';
        
        period = (long long) atoi (secs) * USECSPERSEC + atoi (usecs);
        
        argc--, argv++;
      }
      else if ((strcmp_abbrev (argv[0], "-worm") == 0))
      {
        int length;
        int *x, *y;
        int i;
        
        if (argc <= 1)
          give_usage ();
        
        length = atoi (argv[1]);
        if (length <= 0)
          give_usage ();
        else
          length++;   /* No o-b-o-e playing here! */
        
        /* The pointer-to-worm serves as a flag var, since it was
           initialized to NULL above. */
        worm = (struct worm *) malloc ( sizeof (struct worm));
        if (worm == NULL)
        {
          perror (progname);
          exit (1);
        }
        
        /* Init to impossible values so draw_circles() knows whether
           worm has been seen before. */
        x = (int *) malloc (length * sizeof (int));
        y = (int *) malloc (length * sizeof (int));
        if (!x || !y)
        {
          perror (progname);
          exit (1);
        }
        
        worm->length     = length;
        worm->center_x   = x;
        worm->center_y   = y;
        worm->radius     = -1;
        worm->sol        = -1;
        worm->au         = -1;
        worm->direction  = -1;
        
        /* Init to impossible values so draw_circles() knows whether
           worm has been seen before. */
        for (i = 0; i < length; i++)
        {
          worm->center_x[i] = -1;
          worm->center_y[i] = -1;
        }
        
        /* Push our arglist along. */
        argv++, argc--;
        
        /* draw_circles() will handle worm from here. */
      }
      else
      {
        give_usage ();
        break;
      }
    }
    else 
    {
      /* unrecognized option */
      give_usage ();
    }
  }

  /* Try to grok the ascii_phase specification.  */
  {
    phase = get_date (ascii_phase, NULL);
    if (phase == -1)
    {
      fprintf (stderr,
               "%s: can't parse `%s' as a time\n",
               progname, ascii_phase);
      exit (1);
    }

    /* Find the smallest equivalent value for phase.  */
    phase = (phase * USECSPERSEC) % period;
  }

  disp_name || (disp_name = getenv ("DISPLAY"));

  disp = XOpenDisplay (disp_name);
  if (! disp)
  {
    fprintf (stderr, "%s: Can't open display %s\n", progname, disp_name);
    exit (1);
  }

  screen = DefaultScreen (disp);
  root = RootWindow (disp, screen);

  /* Try to allocate a color cell for the changing color.  */
  default_cmap = DefaultColormap (disp, screen);

  if (! XAllocColorCells (disp, default_cmap, False,
			  NULL, 0, &circle_pixel, 1))
  {
    fprintf (stderr,
             "%s: In order to run, the display %s must have "
             "a read/write color cell available.\n",
             progname, disp_name);
    exit (2);
  }

  {
    XGCValues v;

    v.foreground = circle_pixel;
    v.line_width = INTERIOR_WIDTH;
    circle_gc = XCreateGC (disp, root, GCLineWidth | GCForeground, &v);

    v.foreground = BlackPixel (disp, screen);
    v.line_width = BORDER_WIDTH;
    border_gc = XCreateGC (disp, root, GCLineWidth | GCForeground, &v);

    if (worm)
    {
      /* The worm's foreground is black, and its "background" is the
         same color as the circles, so when we repaint it somewhere
         else, drawing over the old one with back_gc works. */

      v.foreground = BlackPixel (disp, screen);
      v.line_width = INTERIOR_WIDTH - 4;
      worm->fore_gc = XCreateGC (disp, root,
                                 GCLineWidth | GCForeground, &v);

      v.foreground = circle_pixel;
      v.line_width = INTERIOR_WIDTH - 4;
      worm->back_gc = XCreateGC (disp, root,
                                 GCLineWidth | GCForeground, &v);
    }
  }
  
  {
    fd_set inputfds;
    struct timeval next_tick;

    tick (&next_tick);
    draw_circles (worm, NOT_FROM_EXPOSURE);

    XSelectInput (disp, root, ExposureMask);

    for (;;)
    {
      int has_event;
	  
      XFlush (disp);

      FD_ZERO (&inputfds);
      FD_SET (ConnectionNumber (disp), &inputfds);

      if ((has_event = select (32, &inputfds, NULL, NULL, &next_tick)) == -1)
      {
        perror (progname);
        exit (2);
      }

      if (has_event)
      {
        XEvent event;
        XNextEvent (disp, &event);
	  
        switch (event.type)
        {

        case Expose:
          /* Remove any other queued expose events; one
             draw_circles call will take care of them all.  */
          while (XCheckMaskEvent (disp, ExposureMask, &event))
            ;
          draw_circles (worm, FROM_EXPOSURE);
          break;

        case MappingNotify:
          break;

        default:
          fprintf (stderr, "%s: Unexpected event type: %d\n",
                   progname, event.type);
          exit (2);

        }
      }

      tick (&next_tick);

      if (worm)
      {
        move_worm (worm);
        draw_worm (worm, NOT_FROM_EXPOSURE);
      }
    }
  }

  XCloseDisplay (disp);
  
  exit (0);
}

#define CENTER_RADIUS(cx, cy, r) (cx) - (r), (cy)-(r), 2*(r), 2*(r)

void
draw_circles (struct worm *worm, int from_exposure_p)
{
  int width = DisplayWidth (disp, screen);
  int height = DisplayHeight (disp, screen);
  int center_x = width / 2;
  int center_y = height / 2;
  int radius = ((width < height) ? width : height) / 6;
  int spacing = radius / .86602540378443864690; /* cos (30) */

  int center_x_1 = center_x + spacing *  .96592582628906828678; /* cos (15) */
  int center_y_1 = center_y - spacing *  .25881904510252076221; /* sin (15) */
  
  int center_x_2 = center_x + spacing * -.70710678118654752351; /* cos (135) */
  int center_y_2 = center_y - spacing *  .70710678118654752528; /* sin (135) */
  
  int center_x_3 = center_x + spacing * -.25881904510252076462; /* cos (255) */
  int center_y_3 = center_y - spacing * -.96592582628906828615; /* sin (255) */
  
  XDrawArc (disp, root, border_gc,
            CENTER_RADIUS (center_x_1, center_y_1, radius),
            0 * 64, 360 * 64);
  XDrawArc (disp, root, border_gc,
            CENTER_RADIUS (center_x_2, center_y_2, radius),
            0 * 64, 360 * 64);
  XDrawArc (disp, root, border_gc,
            CENTER_RADIUS (center_x_3, center_y_3, radius),
            0 * 64, 360 * 64);
  
  XDrawArc (disp, root, circle_gc,
            CENTER_RADIUS (center_x_1, center_y_1, radius),
            0 * 64, 360 * 64);
  XDrawArc (disp, root, circle_gc,
            CENTER_RADIUS (center_x_2, center_y_2, radius),
            0 * 64, 360 * 64);
  XDrawArc (disp, root, circle_gc,
            CENTER_RADIUS (center_x_3, center_y_3, radius),
            0 * 64, 360 * 64);

  /* Maybe set up the worm. */
  if (worm)
  {
    int which;

    SRANDOM (time (NULL));
    which = RANDOM () % NUM_CIRCLES;

    worm->sol_x[0] = center_x_1;
    worm->sol_y[0] = center_y_1;

    worm->sol_x[1] = center_x_2;
    worm->sol_y[1] = center_y_2;

    worm->sol_x[2] = center_x_3;
    worm->sol_y[2] = center_y_3;

    /* Around whom orbit we?  (One of above three.) */
    if (worm->sol == -1)
      worm->sol = which;
    if (worm->au == -1)
      worm->au = radius;
    if (worm->direction == -1)
      worm->direction = RANDOM () % 2;

    /* Calculate some point on a circle, given radius and center. */
    if ((worm->center_x[0] == -1) || (worm->center_y[0] == -1))
      place_worm (worm);            /* sets worm's center */
    if (worm->radius == -1)
      worm->radius   = INTERIOR_WIDTH - 8;

    /* Finally, put it on the screen. */
    draw_worm (worm, from_exposure_p);
  }
}



/* Initial placement of the worm.  Movement after this is done by
 * incrementing or decrementing worm->center_x and adjusting
 * worm->center_y accordingly, or vice_versa.
 */
void
place_worm (struct worm *worm)
{
  /* Be only somewhat random about this. */
  int compass_pt, i;

  compass_pt = RANDOM () % 4;

  /* The compass points around the circle with center C are:
   *
   *                    1
   *
   *               4    C    2
   *
   *                    3
   */

  switch (compass_pt)
  {
  case 0:
    worm->center_x[0] = worm->sol_x[worm->sol];
    worm->center_y[0] = worm->sol_y[worm->sol] + worm->au;
    break;
  case 1:
    worm->center_x[0] = worm->sol_x[worm->sol] + worm->au;
    worm->center_y[0] = worm->sol_y[worm->sol];
    break;
  case 2:
    worm->center_x[0] = worm->sol_x[worm->sol];
    worm->center_y[0] = worm->sol_y[worm->sol] - worm->au;
    break;
  case 3:
  default:      /* `default' is impossible, but what the heck */
    worm->center_x[0] = worm->sol_x[worm->sol] - worm->au;
    worm->center_y[0] = worm->sol_y[worm->sol];
    break;
  }

  for (i = 1; i < worm->length; i++)
  {
    /* Init the rest of the array to point off-screen, so we don't
       erase random patches of display before the worm has reached
       its full length. */
    worm->center_x[i] = -1;
    worm->center_y[i] = -1;
  }

  worm->last_sol      = worm->sol;
}


void
move_worm (struct worm *worm)
{
  static int jumped = 0;
  static int maybe = 1;
  int idx_old, idx_new, sol, au;

  /* This is for knowing when not to jump; it is functionally
   * unrelated to worm->last_sol.  It preserves the same information,
   * but longer.
   */
  static int last_sol;

  /* Quadrant boundaries around a circle with center C are:
   *
   *                    1
   *
   *               4    C    2
   *
   *                    3    
   *
   * Source is inclusive and destination is non-inclusive; thus,
   * quadrant 1-2 includes point 1, but not 2.  The order in which the
   * boundary numbers are given indicates whether direction is
   * clockwise or counter-clockwise; however I think the direction is
   * not usually relevant.
   *
   *    Quadrant 1-2 / 2-1: worm(x) >= C(x) && worm(y) > C(y):
   *                        increment x to move clockwise
   *                        decrement x to move counterclockwise
   *
   *    Quadrant 2-3 / 3-2: worm(x) > C(x) && worm(y) <= C(y):
   *                        decrement y to move clockwise
   *                        increment y to move counterclockwise
   *
   *    Quadrant 3-4 / 4-3: worm(x) <= C(x) && worm(y) < C(y):
   *                        decrement x to move clockwise
   *                        increment x to move counterclockwise
   *
   *    Quadrant 4-1 / 4-1: worm(x) < C(x) && worm(y) >= C(y):
   *                        increment y to move clockwise
   *                        decrement y to move counterclockwise
   */

  /* First save the old coordinates, for repaint. */
  idx_old = worm->idx;
  idx_new = (idx_old == (worm->length - 1)) ? 0 : (idx_old + 1);

  /* Advance our worm's segment pointer. */
  worm->idx = idx_new;

  /* Copy the previous segment's information into this segment, since
     worms must slither nondisjointly. */
  worm->center_x[idx_new] = worm->center_x[idx_old];
  worm->center_y[idx_new] = worm->center_y[idx_old];

  /* Record whom we're orbiting. */
  worm->last_sol = worm->sol;

  /* For brevity's sake. */
  sol = worm->sol;
  au  = worm->au;

  /* 1. find out which quadrant we're in
   * 2. do the operation indicated by worm->direction (see table above)
   * 3. then adjust the other coordinate accordingly
   */
  if (   (worm->center_x[idx_new] >= worm->sol_x[sol])         /* 1-2 */
         && (worm->center_y[idx_new] < worm->sol_y[sol]))
  {
    if (worm->direction == CLOCKWISE)
    {
      worm->center_x[idx_new]++;
      worm->center_y[idx_new] = worm->sol_y[sol]
        - sqrt (abs (SQUARE (au)
                     - SQUARE (abs (worm->center_x[idx_new] -
                                    worm->sol_x[sol]))));
    }
    else if (worm->direction == COUNTERCLOCKWISE)
    {
      worm->center_x[idx_new]--;
      worm->center_y[idx_new] = worm->sol_y[sol]
        - sqrt (abs (SQUARE (au)
                     - SQUARE (abs (worm->center_x[idx_new] -
                                    worm->sol_x[sol]))));
    }
  }
  else if (   (worm->center_x[idx_new] > worm->sol_x[sol])     /* 2-3 */
              && (worm->center_y[idx_new] >= worm->sol_y[sol]))
  {
    if (worm->direction == CLOCKWISE)
    {
      worm->center_y[idx_new]++;
      worm->center_x[idx_new] = worm->sol_x[sol]
        + sqrt (abs (SQUARE (au)
                     - SQUARE (abs (worm->center_y[idx_new] -
                                    worm->sol_y[sol]))));
    }
    else if (worm->direction == COUNTERCLOCKWISE)
    {
      worm->center_y[idx_new]--;
      worm->center_x[idx_new] = worm->sol_x[sol]
        + sqrt (abs (SQUARE (au)
                     - SQUARE (abs (worm->center_y[idx_new] -
                                    worm->sol_y[sol]))));
    }
  }
  else if (   (worm->center_x[idx_new] <= worm->sol_x[sol])     /* 3-4 */
              && (worm->center_y[idx_new] > worm->sol_y[sol]))
  {
    if (worm->direction == CLOCKWISE)
    {
      worm->center_x[idx_new]--;
      worm->center_y[idx_new] = worm->sol_y[sol]
        + sqrt (abs (SQUARE (au)
                     - SQUARE (abs (worm->center_x[idx_new] -
                                    worm->sol_x[sol]))));
    }
    else if (worm->direction == COUNTERCLOCKWISE)
    {
      worm->center_x[idx_new]++;
      worm->center_y[idx_new] = worm->sol_y[sol]
        + sqrt (abs (SQUARE (au)
                     - SQUARE (abs (worm->center_x[idx_new] -
                                    worm->sol_x[sol]))));
    }
  }
  else if (   (worm->center_x[idx_new] < worm->sol_x[sol])      /* 4-1 */
              && (worm->center_y[idx_new] <= worm->sol_y[sol]))
  {
    if (worm->direction == CLOCKWISE)
    {
      worm->center_y[idx_new]--;
      worm->center_x[idx_new] = worm->sol_x[sol]
        - sqrt (abs (SQUARE (au)
                     - SQUARE (abs (worm->center_y[idx_new] -
                                    worm->sol_y[sol]))));
    }
    else if (worm->direction == COUNTERCLOCKWISE)
    {
      worm->center_y[idx_new]++;
      worm->center_x[idx_new] = worm->sol_x[sol]
        - sqrt (abs (SQUARE (au)
                     - SQUARE (abs (worm->center_y[idx_new] -
                                    worm->sol_y[sol]))));
    }
  }
  else
  {
    fprintf (stderr, "%s: impossible situation\n", progname);
    exit (1);
  }

  /* Check if we've come to a fork in the road, take it if so. */
  {
    int i;
    int x, y;
    int dist;

    /* Have we recently jumped (i.e., are we still in the "jumpy"
       region)? */
    if (jumped)
    {
      x = abs (worm->center_x[idx_new] - worm->sol_x[last_sol]);
      y = abs (worm->center_y[idx_new] - worm->sol_y[last_sol]);
      dist = (int) sqrt (SQUARE (x) + SQUARE (y));
        
      if (dist > au)
      {
        /* Once we're far enough along on the current circle, we
         * can reset jump, but until we do that, we can't jump at
         * all.  This insures that we only attempt one jump as we
         * pass through one of the "jumpy" regions.
         */
        jumped = 0;
        last_sol = sol;

        /* Will we jump next time?  We only want to jump at half
         * the forks we come to, so the worm has a chance to
         * slither in every direction on every circle.
         */
        maybe = RANDOM () % JUMP_PROB;
      }
    }
    else /* !jumped */
    {
      /* Save sol for next time we jump. */
      last_sol = sol;
        
      for (i = 0; i < NUM_CIRCLES; i++)
      {
        /* No need to consider the current orbit. */
        if (i == sol)
          continue;
            
        x = abs (worm->center_x[idx_new] - worm->sol_x[i]);
        y = abs (worm->center_y[idx_new] - worm->sol_y[i]);
        dist = (int) sqrt (SQUARE (x) + SQUARE (y));
            
        if (dist <= au)
        {
          if (maybe)
          {
            /* Hop over to the next circle. */
            jumped = 1;
            worm->sol = i;
            worm->direction = ! (worm->direction);
          }
          else /* !maybe */
          {
            jumped = 1;
            last_sol = i;
          }
        }
      }
    }
  }
}


void
draw_worm (struct worm *worm, int from_exposure_p)
{
  if (from_exposure_p) /* Redraw the whole body -- all segments. */
  {
      
    int i, len;
    len = worm->length;
      
    for (i = 0; i < len; i++)
      XDrawArc (disp, root, worm->fore_gc,
                CENTER_RADIUS (worm->center_x[i],
                               worm->center_y[i],
                               worm->radius),
                0 * 64, 360 * 64);

    /* Call again, only to re-erase the ghost of the last segment. */
    draw_worm (worm, NOT_FROM_EXPOSURE);
  }
  else /* Just erase least recent segment and draw next segment. */
  {
    int tail, idx;
    idx  = worm->idx;
    tail = (idx == (worm->length - 1)) ? 0 : (idx + 1);
      
    XDrawArc (disp, root, worm->back_gc,
              CENTER_RADIUS (worm->center_x[tail],
                             worm->center_y[tail],
                             worm->radius),
              0 * 64, 360 * 64);
      
    XDrawArc (disp, root, worm->fore_gc,
              CENTER_RADIUS (worm->center_x[idx],
                             worm->center_y[idx],
                             worm->radius),
              0 * 64, 360 * 64);
  }
}



void
give_usage ()
{
  char *usage = "\
Usage: %s [-display <displayname>] [-period <secs> ] [-phase <time> ] [-worm <length>]\n\
%s displays a pattern of circles on the screen whose color changes to\n\
reflect the current time.\n\
\n\
An argument of the form \"-period N\" indicates that the pattern\n\
should go through its entire color cycle in N seconds.  N need not be\n\
integral; %s can interpret a decimal point.  The period defaults to\n\
one hour.\n\
\n\
An argument of the form \"-phase <time>\" indicates that the color\n\
cycle's phase should be chosen so that it would be at its starting\n\
point at <time>.  <time> may be specified in most reasonable ways.  By\n\
default, the cycle is aligned to start at 12:00 noon on the day %s is\n\
started.\n\
\n\
An argument of the form \"-worm <length>\" causes a worm of <length>\n\
segments to slither along the trough created by the intersection of\n\
the circles.  Works best with a fast period; for example, try:\n\
\n\
\"%s -period 1 -worm 1000\"\n\
\n";
  
  fprintf (stderr, usage, progname, progname, progname, progname, progname);
  exit (1);
}

#ifndef COLORCLASS
#define COLORCLASS
#endif
COLORCLASS unsigned char red[] = {
  128, 130, 133, 136, 139, 142, 145, 148, 
  151, 154, 157, 160, 162, 165, 168, 171, 
  173, 176, 179, 181, 184, 187, 189, 192, 
  194, 197, 199, 201, 204, 206, 208, 210, 
  212, 214, 216, 218, 220, 222, 224, 226, 
  227, 229, 230, 232, 233, 235, 236, 237, 
  238, 239, 240, 241, 242, 243, 244, 245, 
  245, 246, 246, 247, 247, 247, 247, 247, 
  247, 247, 247, 247, 247, 247, 246, 246, 
  245, 245, 244, 243, 242, 241, 240, 239, 
  238, 237, 236, 235, 233, 232, 230, 229, 
  227, 226, 224, 222, 220, 218, 216, 214, 
  212, 210, 208, 206, 204, 201, 199, 197, 
  194, 192, 189, 187, 184, 181, 179, 176, 
  173, 171, 168, 165, 162, 160, 157, 154, 
  151, 148, 145, 142, 139, 136, 133, 130, 
  128, 126, 123, 120, 117, 114, 111, 108, 
  105, 102,  99,  96,  94,  91,  88,  85, 
  83,  80,  77,  75,  72,  69,  67,  64, 
  62,  59,  57,  55,  52,  50,  48,  46, 
  44,  42,  40,  38,  36,  34,  32,  30, 
  29,  27,  26,  24,  23,  21,  20,  19, 
  18,  17,  16,  15,  14,  13,  12,  11, 
  11,  10,  10,   9,   9,   9,   9,   9, 
  9,   9,   9,   9,   9,   9,  10,  10, 
  11,  11,  12,  13,  14,  15,  16,  17, 
  18,  19,  20,  21,  23,  24,  26,  27, 
  29,  30,  32,  34,  36,  38,  40,  42, 
  44,  46,  48,  50,  52,  55,  57,  59, 
  62,  64,  67,  69,  72,  75,  77,  80, 
  83,  85,  88,  91,  94,  96,  99, 102, 
  105, 108, 111, 114, 117, 120, 123, 126
},
  blue[] = {
    230, 229, 227, 226, 224, 222, 220, 218, 
    216, 214, 212, 210, 208, 206, 204, 201, 
    199, 197, 194, 192, 189, 187, 184, 181, 
    179, 176, 173, 171, 168, 165, 162, 160, 
    157, 154, 151, 148, 145, 142, 139, 136, 
    133, 130, 128, 126, 123, 120, 117, 114, 
    111, 108, 105, 102,  99,  96,  94,  91, 
    88,  85,  83,  80,  77,  75,  72,  69, 
    67,  64,  62,  59,  57,  55,  52,  50, 
    48,  46,  44,  42,  40,  38,  36,  34, 
    32,  30,  29,  27,  26,  24,  23,  21, 
    20,  19,  18,  17,  16,  15,  14,  13, 
    12,  11,  11,  10,  10,   9,   9,   9, 
    9,   9,   9,   9,   9,   9,   9,   9, 
    10,  10,  11,  11,  12,  13,  14,  15, 
    16,  17,  18,  19,  20,  21,  23,  24, 
    26,  27,  29,  30,  32,  34,  36,  38, 
    40,  42,  44,  46,  48,  50,  52,  55, 
    57,  59,  62,  64,  67,  69,  72,  75, 
    77,  80,  83,  85,  88,  91,  94,  96, 
    99, 102, 105, 108, 111, 114, 117, 120, 
    123, 126, 128, 130, 133, 136, 139, 142, 
    145, 148, 151, 154, 157, 160, 162, 165, 
    168, 171, 173, 176, 179, 181, 184, 187, 
    189, 192, 194, 197, 199, 201, 204, 206, 
    208, 210, 212, 214, 216, 218, 220, 222, 
    224, 226, 227, 229, 230, 232, 233, 235, 
    236, 237, 238, 239, 240, 241, 242, 243, 
    244, 245, 245, 246, 246, 247, 247, 247, 
    247, 247, 247, 247, 247, 247, 247, 247, 
    246, 246, 245, 245, 244, 243, 242, 241, 
    240, 239, 238, 237, 236, 235, 233, 232
  },
    green[] = {
      24,  23,  21,  20,  19,  18,  17,  16, 
      15,  14,  13,  12,  11,  11,  10,  10, 
      9,   9,   9,   9,   9,   9,   9,   9, 
      9,   9,   9,  10,  10,  11,  11,  12, 
      13,  14,  15,  16,  17,  18,  19,  20, 
      21,  23,  24,  26,  27,  29,  30,  32, 
      34,  36,  38,  40,  42,  44,  46,  48, 
      50,  52,  55,  57,  59,  62,  64,  67, 
      69,  72,  75,  77,  80,  83,  85,  88, 
      91,  94,  96,  99, 102, 105, 108, 111, 
      114, 117, 120, 123, 126, 128, 130, 133, 
      136, 139, 142, 145, 148, 151, 154, 157, 
      160, 162, 165, 168, 171, 173, 176, 179, 
      181, 184, 187, 189, 192, 194, 197, 199, 
      201, 204, 206, 208, 210, 212, 214, 216, 
      218, 220, 222, 224, 226, 227, 229, 230, 
      232, 233, 235, 236, 237, 238, 239, 240, 
      241, 242, 243, 244, 245, 245, 246, 246, 
      247, 247, 247, 247, 247, 247, 247, 247, 
      247, 247, 247, 246, 246, 245, 245, 244, 
      243, 242, 241, 240, 239, 238, 237, 236, 
      235, 233, 232, 230, 229, 227, 226, 224, 
      222, 220, 218, 216, 214, 212, 210, 208, 
      206, 204, 201, 199, 197, 194, 192, 189, 
      187, 184, 181, 179, 176, 173, 171, 168, 
      165, 162, 160, 157, 154, 151, 148, 145, 
      142, 139, 136, 133, 130, 128, 126, 123, 
      120, 117, 114, 111, 108, 105, 102,  99, 
      96,  94,  91,  88,  85,  83,  80,  77, 
      75,  72,  69,  67,  64,  62,  59,  57, 
      55,  52,  50,  48,  46,  44,  42,  40, 
      38,  36,  34,  32,  30,  29,  27,  26
    };

/* Choose the appropriate color for the current time, and set
   *t to the amount of time until the next color change.  */
void
tick (struct timeval *t)
{
  static long long tick_length = -1;
  long long now;

  if (tick_length == -1)
    tick_length = period / array_length (red);

  {
    struct timeval tv;

    if (gettimeofday (&tv, NULL) == -1)
    {
      perror (progname);
      exit (2);
    }
    now = (long long) tv.tv_sec * USECSPERSEC + tv.tv_usec;
  }
    
  /* Calculate how far we are into the current cycle.  */
  now = (now - phase) % period;

  set_color ((int) ((now * array_length (red)) / period));

  /* Calculate how far we are into the current tick.  */
  now = tick_length - (now % tick_length);
  
  t->tv_sec  = now / USECSPERSEC;
  t->tv_usec = now % USECSPERSEC;
}

/* Set the current color to theta; 0 <= theta < array_length (red).  */
void
set_color (int theta)
{
  static int last_theta = -1;

  if (theta != last_theta)
  {
    XColor c;

    c.pixel = circle_pixel;
    c.red   = 256 * red[theta];
    c.green = 256 * green[theta];
    c.blue  = 256 * blue[theta];
    c.flags = DoRed | DoGreen | DoBlue;
    XStoreColor (disp, default_cmap, &c);

    last_theta = theta;
  }
}

int
strcmp_abbrev (char *abbrev, char *model)
{
  while (*abbrev == *model && *model)
    abbrev++, model++;
  
  if (*abbrev == '\0')
    return 0;
  else
    return *abbrev - *model;
}
