/* circles.c - Draw circles whose color depends on the time of day.
   Jim Blandy <jimb@occs.cs.oberlin.edu> - March 1992.  */

#include <stdio.h>
#include <X11/Xlib.h>
#include <sys/time.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) */

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

char *disp_name = NULL;

#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;

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

void give_usage (void);
void draw_circles (void);
void tick (struct timeval *t);
void set_color (int);
int strcmp_abbrev (char *abbrev, char *model);

int
main (argc, argv)
     int argc;
     char **argv;
{
  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
	{
	  give_usage ();
	  break;
	}
    else
      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 = 10;
    circle_gc = XCreateGC (disp, root, GCLineWidth | GCForeground, &v);

    v.foreground = BlackPixel (disp, screen);
    v.line_width = 14;
    border_gc = XCreateGC (disp, root, GCLineWidth | GCForeground, &v);
  }
  
  {
    fd_set inputfds;
    struct timeval next_tick;

    tick (&next_tick);
    draw_circles ();

    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 ();
		break;

	      case MappingNotify:
		break;

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

	      }
	  }

	tick (&next_tick);
      }
  }

  XCloseDisplay (disp);
  
  exit (0);
}

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

void
draw_circles ()
{
  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);
}

void
give_usage ()
{
  char *usage = "\
Usage: %s [-display displayname] [-period <seconds> ] [-phase <time> ]\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\
";
  
  fprintf (stderr, usage, 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;
}
