/*--------------------------------*-C-*---------------------------------*
 * Copyright 1992, 1993 Robert Nation <nation@rocket.sanders.lockheed.com>
 *
 * You can do what you like with this source code as long as you include an
 * unaltered copy of this message (including the copyright).
 *
 * As usual, the author accepts no responsibility for anything, nor does he
 * guarantee anything whatsoever.
 *
 * Additional modifications by mj olesen <olesen@me.QueensU.CA>
 * No additional restrictions are applied.
 *----------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#else
/* # define STDC_HEADERS */
# define HAVE_UNISTD_H
# define TIME_WITH_SYS_TIME
# define HAVE_SYS_TIME_H
# ifdef _AIX
#  define HAVE_SYS_SELECT_H
# endif
#endif

#include "feature.h"

#include <ctype.h>
/* #ifdef STDC_HEADERS */
# include <stdarg.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
/* #endif */

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#if defined (__svr4__)
# include <sys/resource.h>	/* for struct rlimit */
#endif

#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include <sys/stat.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#define APL_CLASS	"Clock"
#define APL_NAME	"rclock"
#define MSG_CLASS	"Appointment"
#define MSG_NAME	"Appointment"
#define CONFIG_FILE	".rclock"

#ifndef EXIT_SUCCESS	/* missed from <stdlib.h> ? */
# define EXIT_SUCCESS	0
# define EXIT_FAILURE	1
#endif

/*----------------------------------------------------------------------*/

static Display*	Xdisplay;	/* X display */
static int	Xfd;		/* file descriptor of server connection */
static GC	Xgc, Xrvgc;	/* normal, reverse video GC */

#define	Xscreen		DefaultScreen(Xdisplay)
#define Xcmap		DefaultColormap(Xdisplay,Xscreen)

/* windows and their sizes */
typedef struct {
   Window win;
   int height, width;
} mywindow_t;

static mywindow_t	Clock = {None, 80, 80};	/* parent window */

#define COLORFG	0
#define COLORBG 1
static const char *	rs_color [2] = { FG_COLOR_NAME, BG_COLOR_NAME };
static unsigned long	pixel_colors [2];
static const char *	rs_geometry = NULL;

#define MARGIN	1	/* Window border width */

#ifdef ICONWIN
static const char *	rs_iconGeometry = NULL;
static mywindow_t	Icon = {None, 65, 65};	/* icon window */
static int		iconic_state = NormalState; /* iconic startup? */
#endif

#if defined (REMINDERS) && !defined (CONFIG_FILE)
# undef REMINDERS
#endif

#ifdef REMINDERS
static mywindow_t	Msg = {0, 0, 0};    /* message window */
static struct {
   Window Dismiss, Defer;
} msgButton;

static XFontStruct *	Xfont;
static int	font_height;
static int	Msg_Mapped = 0;		/* message window mapped? */
static int	alarmTime = -1;
static char	message [256] = "";
static const char * reminders_file = NULL;	/* name of ~/.rclock file */
#endif

static int	clockUpdate = CLOCKUPDATE;

#ifdef MAIL_BELL
# ifndef MAIL
#  define MAIL
# endif
#endif
#ifdef MAIL
static int mailUpdate = MAILUPDATE;
static char * mail_file = NULL;
#endif

static XSizeHints szHint = {
   PMinSize | PResizeInc | PBaseSize | PWinGravity,
   0, 0, 80, 80,		/* x, y, width and height */
   1, 1,			/* Min width and height */
   0, 0,			/* Max width and height */
   1, 1,			/* Width and height increments */
   {1, 1},			/* x, y increments */
   {1, 1},			/* Aspect ratio - not used */
   0, 0,			/* base size */
   NorthWestGravity		/* gravity */
};

/* subroutine declarations */
static void	geometry2sizehint (mywindow_t *win, const char * geom);
static void	Create_Windows (int argc, char *argv[]);
static void	getXevent (void);
static void	print_error (const char *fmt, ...);

static void	Draw_Window (mywindow_t *this_win, int full_redraw);
static void	SetOffAlarm (void);
static void	GetNewAlarm (int update_only);

/* Arguments for GetNewAlarm() */
#define REPLACE 0
#define UPDATE 1

/*----------------------------------------------------------------------*/

static void
usage (void)
{
   int i;
   struct {
      const char * const opt;
      const char * const desc;
#define optList_size()		(sizeof(optList)/sizeof(optList[0]))
   } optList[] = {
	{ "-display displayname", "X server to contact" },
	{ "-geometry geom",	"size (in pixels) and position" },
	{ "-bg color",		"background color" },
	{ "-fg color",		"foreground color" },
#ifdef REMINDERS
	{ "-fn fontname",	"normal font for messages" },
#endif
#ifdef ICONWIN
	{ "-iconic",		"start iconic" },
#endif
	{ "-update seconds",	"clock update interval" },
#ifdef MAIL
	{ "-mail seconds",	"check $MAIL interval" },
#endif
	{ "#geom",		"icon window geometry" }
   };

   fprintf (stderr, "\nUsage:\n  " APL_NAME " [options]\n\n"
	    "where options include:\n");

   for (i = 0; i < optList_size(); i++)
     fprintf (stderr, "    %-29s%s\n", optList[i].opt, optList[i].desc);
}

/*----------------------------------------------------------------------*
 * rclock - Rob's clock
 * simple X windows clock with appointment reminder
 *----------------------------------------------------------------------*/
int
main (int argc, char *argv[])
{
   int i;
   char *opt, *val;
   const char *display_name = NULL;
   XGCValues gcv;

#ifdef REMINDERS
   const char *rs_font = FONT_NAME;

   /* find the ~/.rclock file */
   if ((val = getenv ("HOME")) != NULL)
     {
	char *p = malloc (strlen (CONFIG_FILE) + strlen (val) + 2);
	if (p == NULL)
	  goto Malloc_Error;

	strcpy (p, val);
	strcat (p, "/" CONFIG_FILE);

	reminders_file = p;
     }
#endif
#ifdef MAIL
   /* get the mail spool file name */
   val = getenv ("MAIL");
#ifdef MAIL_SPOOL
   if (val == NULL)	/* csh doesn't set $MAIL */
     {
	char * user = getenv ("USER");	/* assume this works */
	const char * spool = MAIL_SPOOL;
	char *p = malloc (strlen (spool) + strlen (user) + 1);
	if (p == NULL)
	  goto Malloc_Error;
	strcpy (p, spool);
	strcat (p, user);
	mail_file = p;
     }
   else
#endif
     mail_file = val;
#endif

   if ((display_name = getenv ("DISPLAY")) == NULL)
     display_name = ":0";

   /* parse the command line */
   for (i = 1; i < argc; i += 2)
     {
	opt = argv[i];
	val = argv[i+1];

	switch (*opt++) {
	 case '-':
	   break;
#ifdef ICONWIN
	 case '#':
	   rs_iconGeometry = opt;   /* drop */
#endif
	 default:
	   continue;
	   break;
	}

	if (*opt == 'd')	display_name = val;	/* "d", "display" */
	else if (*opt == 'g')	rs_geometry = val;	/* "g", "geometry" */
#ifdef ICONWIN
	else if (*opt == 'i' && *(opt+1) == 'c')	/* "ic", "iconic" */
	  {
	     iconic_state = IconicState;
	     i--;	/* no argument */
	  }
#endif
	else if (!strcmp (opt, "fg"))		rs_color [COLORFG] = val;
	else if (!strcmp (opt, "bg"))		rs_color [COLORBG] = val;
#ifdef REMINDERS
	else if (!strcmp (opt, "fn"))		rs_font = val;
#endif
	else if (!strcmp (opt, "update"))
	  {
	     int x = atoi (val);
	     if (x < 1 || x > 60)
	       print_error ("update: %d sec", clockUpdate);
	     else
	       clockUpdate = x;
	  }
#ifdef MAIL
	else if (!strncmp (opt, "mail", 4))
	  {
	     int x = atoi (val);
	     if (x < 1)
	       print_error ("mail update: %d sec", mailUpdate);
	     else
	       mailUpdate = x;
	  }
#endif
	else
	  {
	     usage ();
	     goto Abort;
	  }
     }

   /* open display */
   Xdisplay = XOpenDisplay (display_name);
   if (!Xdisplay)
     {
	print_error ("can't open display %s", display_name);
	goto Abort;
     }

   /* get display info */
   Xfd = XConnectionNumber (Xdisplay);
     {
	const char * const color_msg = "can't load color \"%s\"";
	XColor xcol;

	/* allocate foreground/background colors */
	if (!XParseColor (Xdisplay, Xcmap, rs_color [COLORFG], &xcol) ||
	    !XAllocColor (Xdisplay, Xcmap, &xcol))
	  {
	     print_error (color_msg, rs_color [COLORFG]);
	     goto Abort;
	  }
	pixel_colors [COLORFG] = xcol.pixel;

	if (!XParseColor (Xdisplay, Xcmap, rs_color [COLORBG], &xcol) ||
	    !XAllocColor (Xdisplay, Xcmap, &xcol))
	  {
	     print_error (color_msg, rs_color [COLORBG]);
	     goto Abort;
	  }
	pixel_colors [COLORBG] = xcol.pixel;
     }

   Create_Windows (argc, argv);
#ifdef REMINDERS
   /* load the font for messages */
   if ((Xfont = XLoadQueryFont (Xdisplay, rs_font)) == NULL)
     {
	print_error ("can't load font \"%s\"", rs_font);
	goto Abort;
     }
   font_height = Xfont->ascent + Xfont->descent;
   gcv.font = Xfont->fid;
#endif
   /*  Create the graphics contexts */
   gcv.foreground = pixel_colors [COLORFG];
   gcv.background = pixel_colors [COLORBG];

   Xgc = XCreateGC (Xdisplay, Clock.win,
#ifdef REMINDERS
		    GCFont |
#endif
		    GCForeground | GCBackground, &gcv);

   gcv.foreground = pixel_colors [COLORBG];
   gcv.background = pixel_colors [COLORFG];
   Xrvgc = XCreateGC (Xdisplay, Clock.win,
#ifdef REMINDERS
		      GCFont |
#endif
		      GCForeground | GCBackground, &gcv);

   getXevent ();
   return EXIT_SUCCESS;

   Malloc_Error:
   print_error ("malloc error");
   Abort:
   print_error ("aborting");
   return EXIT_FAILURE;
}

/*
 * translate geometry string to appropriate sizehint
 */
static void
geometry2sizehint (mywindow_t *win, const char *geom)
{
   int x, y, flags;
   unsigned width, height;

   /* copy in values */
   szHint.width  = win->width;
   szHint.height = win->height;

   if (geom == NULL)
     return;

   flags = XParseGeometry (geom, &x, &y, &width, &height);

   if (flags & WidthValue)
     {
	szHint.width = width + szHint.base_width;
	szHint.flags |= USSize;
     }
   if (flags & HeightValue)
     {
	szHint.height = height + szHint.base_height;
	szHint.flags |= USSize;
     }

   if (flags & XValue)
     {
	if (flags & XNegative)
	  {
	     x += (DisplayWidth (Xdisplay, Xscreen)
		   - (szHint.width + 2 * MARGIN));
	     szHint.win_gravity = NorthEastGravity;
	  }
	szHint.x = x;
	szHint.flags |= USPosition;
     }
   if (flags & YValue)
     {
	if (flags & YNegative)
	  {
	     y += (DisplayHeight (Xdisplay, Xscreen)
		   - (szHint.height + 2 * MARGIN));
	     szHint.win_gravity = (szHint.win_gravity == NorthEastGravity ?
				     SouthEastGravity : SouthWestGravity);
	  }
	szHint.y = y;
	szHint.flags |= USPosition;
     }

   /* copy out values */
   win->width  = szHint.width;
   win->height = szHint.height;
}

/*
 * Open and map the windows
 */
static void
Create_Windows (int argc, char *argv[])
{
   XClassHint classHint;
   XWMHints wmHint;
   Window root = DefaultRootWindow (Xdisplay);

   geometry2sizehint (&Clock, rs_geometry);
   Clock.win = XCreateSimpleWindow (Xdisplay, root,
				    szHint.x, szHint.y,
				    Clock.width, Clock.height,
				    MARGIN,
				    pixel_colors [COLORFG],
				    pixel_colors [COLORBG]);

#ifdef ICONWIN
   geometry2sizehint (&Icon, rs_iconGeometry);
   Icon.win = XCreateSimpleWindow (Xdisplay, root,
				   szHint.x, szHint.y,
				   Icon.width, Icon.height,
				   MARGIN,
				   pixel_colors [COLORFG],
				   pixel_colors [COLORBG]);
   wmHint.initial_state = iconic_state;
   wmHint.icon_window = Icon.win;
   wmHint.flags = InputHint | StateHint | IconWindowHint;
#else
   wmHint.flags = InputHint;
#endif
   wmHint.input = True;

   classHint.res_name  = APL_NAME;
   classHint.res_class = APL_CLASS;
   XSetWMProperties (Xdisplay, Clock.win, NULL, NULL, argv, argc,
		     &szHint, &wmHint, &classHint);

   XSelectInput (Xdisplay, Clock.win,
		 (ExposureMask|StructureNotifyMask|ButtonPressMask));

#ifdef ICONWIN
   XSelectInput (Xdisplay, Icon.win,
		 (ExposureMask|StructureNotifyMask|ButtonPressMask));
#endif
   XMapWindow (Xdisplay, Clock.win);

   /* create, but don't map a window for appointment reminders */
#ifdef REMINDERS
   Msg.win = XCreateSimpleWindow (Xdisplay, root,
				  szHint.x, szHint.y,
				  szHint.width, szHint.height,
				  MARGIN,
				  pixel_colors [COLORFG],
				  pixel_colors [COLORBG]);
   wmHint.input = True;
   wmHint.flags = InputHint;
   szHint.flags |= USPosition;

   classHint.res_name  = MSG_NAME;
   classHint.res_class = MSG_CLASS;

     {
	XTextProperty name;
	char *str = MSG_NAME;
	if (!XStringListToTextProperty (&str, 1, &name))
	  {
	     print_error ("can't allocate window name");
	     exit (EXIT_FAILURE);
	  }
	XSetWMProperties (Xdisplay, Msg.win, &name, &name, argv, argc,
			  &szHint, &wmHint, &classHint);

	XFree (name.value);
     }

   XSelectInput (Xdisplay, Msg.win,
		 (ExposureMask|StructureNotifyMask|ButtonPressMask));

   msgButton.Dismiss = XCreateSimpleWindow (Xdisplay, Msg.win,
					    10, 100,
					    50, 20,
					    MARGIN,
					    pixel_colors [COLORFG],
					    pixel_colors [COLORBG]);

   msgButton.Defer = XCreateSimpleWindow (Xdisplay, Msg.win,
					  10, 100,
					  50, 20,
					  MARGIN,
					  pixel_colors [COLORFG],
					  pixel_colors [COLORBG]);
   XMapWindow (Xdisplay, msgButton.Dismiss);
   XMapWindow (Xdisplay, msgButton.Defer);
#endif
}

static time_t
mk_time (struct tm *tmval)
{
   return (tmval->tm_min
	   + 60 * (tmval->tm_hour
		   + 24 * (tmval->tm_mday
			   + 31 * ((tmval->tm_mon+1)
				   + 12 * tmval->tm_year))));
}

/*----------------------------------------------------------------------*
 * Redraw the whole window after an exposure or size change.
 * Redraw only the hands after a timeout.
 * Sound an alarm if needed
 *----------------------------------------------------------------------*/
static void
Draw_Window (mywindow_t *W, int full_redraw)
{
   /* pre-computed values for sin() x1000, to avoid using floats */
   static const short Sin [60] = {
      0,
	105, 208, 309, 407, 500, 588, 669,
	743, 809, 866, 914, 951, 978, 995,
      1000,
	995, 978, 951, 914, 866, 809, 743,
	669, 588, 500, 407, 309, 208, 105,
      0,
	-105, -208, -309, -407, -500, -588, -669,
	-743, -809, -866, -914, -951, -978, -995,
      -1000,
	-995, -978, -951, -914, -866, -809, -743,
	-669, -588, -500, -407, -309, -208, -105
   };

   static int saved_day = -1;

   time_t currentTime;
   struct tm *tmval;
   int i, j, angle, ctr_x, ctr_y;

   typedef struct {
      int h_x, h_y;		/* hour hand   */
      int m_x, m_y;		/* minute hand */
      int s_x, s_y;		/* second hand */
   } hands_t;			/* hand positions (x,y) */

   hands_t HandsNow, *pHandsOld;

   static hands_t HandsOld = { -1 };
#ifdef ICONWIN
   static hands_t HandsOld_icon = { -1 };
#endif
#ifdef REMINDERS
   static int lastUpdateTime = -10;
#endif
#ifdef MAIL
   static time_t mailTime = 0;
   static int MailUp = 0, MailUp_rvideo = 0;
#ifdef ICONWIN
   static int MailUp_icon = 0;
#endif

#ifdef REMINDERS
   if (W->win != Msg.win)
#endif
     {
	int * pMailUp = (
#ifdef ICONWIN
			 W->win == Icon.win ? &MailUp_icon :
#endif
			 &MailUp);

	if (time (NULL) - mailTime >= mailUpdate)
	  {
	     XSetWindowAttributes attributes;
	     struct stat st;

	     if (
#ifdef ICONWIN
		 MailUp != MailUp_icon ? MailUp :
#endif
		 ((mail_file != NULL) && !stat (mail_file, &st) &&
		  (st.st_size > 0) && (st.st_mtime >= st.st_atime))
		 )
	       {
		  if (!*pMailUp)
		    {
		       *pMailUp = 1;
		       full_redraw = 1;
		       attributes.background_pixel = pixel_colors [COLORFG];
		       XChangeWindowAttributes (Xdisplay, W->win, CWBackPixel,
						&attributes);
#ifdef MAIL_ALERT
		       XBell (Xdisplay, 0);
#endif
		    }
	       }
	     else
	       {
		  if (*pMailUp)
		    {
		       *pMailUp = 0;
		       full_redraw = 1;
		       attributes.background_pixel = pixel_colors [COLORBG];
		       XChangeWindowAttributes (Xdisplay, W->win, CWBackPixel,
						&attributes);
		    }
	       }
#ifdef ICONWIN
	     if (MailUp == MailUp_icon)
#endif
	       mailTime = time (NULL);
	     if (MailUp_rvideo != *pMailUp)
	       {
		  GC gc;
		  gc = Xgc; Xgc = Xrvgc; Xrvgc = gc;
		  MailUp_rvideo = *pMailUp;
	       }
	  }
     }
#endif	/* MAIL */

   currentTime = time (NULL);		/* get the current time */
   tmval = localtime (&currentTime);

   /* once every day, update the window and icon name */
   if (tmval->tm_yday != saved_day)
     {
	XTextProperty name;
	char *str, time_string [20];

	saved_day = tmval->tm_yday;
	strftime (time_string, sizeof(time_string), "%a %h %d", tmval);
	str = time_string;
	if (XStringListToTextProperty (&str, 1, &name))
	  {
	     XSetWMName (Xdisplay, Clock.win, &name);
	     XSetWMIconName (Xdisplay, Clock.win, &name);
	     XFree (name.value);
	  }
     }

   if (full_redraw)
     XClearWindow (Xdisplay, W->win);

#ifdef REMINDERS
   /* for a message window, just re-draw the message */
   if (W->win == Msg.win)
     {
	char *beg, *end, *next;
	int lines = 0;

	for (beg = message; beg; beg = next, lines++)
	  {
	     end = next = strstr (beg, "\\n");
	     if (next)
	       next += 2;
	     else
	       end = beg + strlen (beg);
	     XDrawString (Xdisplay, Msg.win,
			  Xgc,
			  (Msg.width -
			   XTextWidth (Xfont, beg, (end-beg))) / 2,
			  10 + Xfont->ascent + font_height * lines,
			  beg, (end-beg));
	  }

	XDrawString (Xdisplay, msgButton.Dismiss,
		     Xgc,
		     ((5*XTextWidth (Xfont, "M", 1) -
		       XTextWidth (Xfont, "Okay", 4)) / 2) + 2,
		     Xfont->ascent + 2,
		     "Okay", 4);

	XDrawString (Xdisplay, msgButton.Defer,
		     Xgc,
		     ((5*XTextWidth (Xfont, "M", 1) -
		       XTextWidth (Xfont, "Defer", 5)) / 2) + 2,
		     Xfont->ascent + 2,
		     "Defer", 5);
	return;
     }

   /*
    * See if we need to sound an alarm.  Convert multi-field time info to a
    * single integer with a resolution in minutes.
    */
   currentTime = mk_time (tmval);

   /* see if we have an alarm to set off */

   if (alarmTime >= 0 && currentTime >= alarmTime)
     SetOffAlarm ();

   /* every 10 minutes, or at start of day, check for revised alarm entries */
   if (!Msg_Mapped &&
       (currentTime > lastUpdateTime + REMINDERS_TIME ||
	(currentTime != lastUpdateTime &&
	 tmval->tm_hour == 0 && tmval->tm_min == 0)))
     {
	GetNewAlarm (UPDATE);
	lastUpdateTime = currentTime;
     }
#endif

   /* draw clock */

   ctr_x = W->width  / 2;
   ctr_y = W->height / 2;

#define XPOS(i,val) (ctr_x + W->width  * Sin[i] * (val) / 200000)
#define YPOS(i,val) (ctr_y - W->height * Sin[((i)+15)%60] * (val) / 200000)

   /* draw the clock face */
   if (full_redraw)
     {
	for (angle = 0; angle < 60; angle += 5)
	  XDrawLine (Xdisplay, W->win, Xgc,
		     XPOS (angle, 90),
		     YPOS (angle, 90),
		     XPOS (angle, 100),
		     YPOS (angle, 100));
     }

   /* calculate the positions of the hands */
   angle = (tmval->tm_hour % 12) * 5 + tmval->tm_min / 12;
   HandsNow.h_x = XPOS (angle, 60);
   HandsNow.h_y = YPOS (angle, 60);

   angle = tmval->tm_min;
   HandsNow.m_x = XPOS (angle, 85);
   HandsNow.m_y = YPOS (angle, 85);

   if (clockUpdate == 1) {
      angle = tmval->tm_sec;
      HandsNow.s_x = XPOS (angle, 85);
      HandsNow.s_y = YPOS (angle, 85);
   } else {
      HandsNow.s_x = HandsNow.s_y = 0;
   }

   pHandsOld = (
#ifdef ICONWIN
		W->win == Icon.win ?  &HandsOld_icon :
#endif
		&HandsOld);

   if (!full_redraw && memcmp (pHandsOld, &HandsNow, sizeof(hands_t)))
     {
	for (i = -1; i < 2; i++) for (j = -1; j < 2; j++)
	  {
	     XDrawLine (Xdisplay, W->win, Xrvgc,
			ctr_x + i, ctr_y + j, pHandsOld->h_x, pHandsOld->h_y);
	     XDrawLine (Xdisplay, W->win, Xrvgc,
			ctr_x + i, ctr_y + j, pHandsOld->m_x, pHandsOld->m_y);
	  }
	if (clockUpdate == 1)
	  XDrawLine (Xdisplay, W->win, Xrvgc,
		     ctr_x, ctr_y, pHandsOld->s_x, pHandsOld->s_y);
     }

   if (full_redraw || memcmp (pHandsOld, &HandsNow, sizeof(hands_t)))
     {
	for (i = -1; i < 2; i++) for (j = -1; j < 2; j++)
	  {
	     XDrawLine (Xdisplay, W->win, Xgc,
			ctr_x + i, ctr_y + j, HandsNow.h_x, HandsNow.h_y);
	     XDrawLine (Xdisplay, W->win, Xgc,
			ctr_x + i, ctr_y + j, HandsNow.m_x, HandsNow.m_y);
	  }
	if (clockUpdate == 1)
	  XDrawLine (Xdisplay, W->win, Xgc,
		     ctr_x, ctr_y, HandsNow.s_x, HandsNow.s_y);

	*pHandsOld = HandsNow;
     }
}

#ifdef REMINDERS
/*
 * Read a single integer from *pstr, returns default value if it finds "*"
 * DELIM = trailing delimiter to skip
 */
static int
GetOneNum (char **pstr, int def)
{
   int num, hit = 0;

   for (num = 0; isdigit (**pstr); (*pstr)++)
     {
	num = num * 10 + (**pstr - '0');
	hit = 1;
     }
   if (!hit)
     {
	num = def;
	while (**pstr == '*') (*pstr)++;
     }
   return num;
}

/*
 * find if TODAY is found in PSTR
 */
static int
isToday (char **pstr, int wday)
{
   const char *dayNames = DAY_NAMES;
   int rval, today;

   today = dayNames [wday];
   /* no day specified is same as wildcard */
   if (!strchr (dayNames, tolower (**pstr)))
     return 1;
   for (rval = 0; strchr (dayNames, tolower (**pstr)); (*pstr)++)
     {
	if (today == tolower (**pstr) || **pstr == '*')
	  rval = 1;		/* found it */
     }
   return rval;
}

/*
 * Read the ~/.rclock file and find the next alarm to sound
 *
 * update_only = 1
 *	look for a reminder whose time is greater than the current time,
 *	but less than the currently set alarm time
 *
 * update_only = 0
 *	look for a reminder whose time is greater than the alarm that
 *	just went off
 */
static void
GetNewAlarm (int update_only)
{
   struct tm *tmval;
   char buffer [256];
   int yy, mo, dd, mm, hh;
#ifndef INT_MAX
#  define INT_MAX	1e8
#endif
   time_t currentTime;
   int testTime, savedTime = INT_MAX;
   FILE *fd;

   if (reminders_file == NULL || (fd = fopen (reminders_file, "r")) == NULL)
     {
	alarmTime = -1;		/* no config file, don't make alarms */
	return;
     }

   /* get the current time */
   currentTime = time (NULL);
   tmval = localtime (&currentTime);
   currentTime = mk_time (tmval);

   /* initial startup, discard reminders that have already occurred */
   if (alarmTime < 0)
     alarmTime = currentTime;

   /* now scan for next alarm */
   while (fgets (buffer, sizeof(buffer), fd))
     {
	char *text = buffer;

	/*
	 * parse the line, format is hh:mm mo/dd/yr message
	 * any of hh, mm, mo, dd, yr could be a wildcard `*'
	 */
	while (isspace (*text)) text++;
	if (*text == '#')
	  continue;
	hh = GetOneNum (&text, tmval->tm_hour);  if (*text == ':') text++;
	mm = GetOneNum (&text, tmval->tm_min);

	while (isspace (*text)) text++;
	if (!isToday (&text, tmval->tm_wday)) continue;
	while (isspace (*text)) text++;

	mo = GetOneNum (&text, tmval->tm_mon+1); if (*text == '/') text++;
	dd = GetOneNum (&text, tmval->tm_mday);  if (*text == '/') text++;
	yy = GetOneNum (&text, tmval->tm_year);

	while (isspace (*text)) text++;
	if (!*text) continue;

	testTime = (mm + 60 * (hh + 24 * (dd + 31 * (mo + 12 * yy))));

	if (testTime > (update_only ? currentTime : alarmTime))
	  {
	     /* have an alarm whose time is greater than the last alarm,
	      * now make sure it is the smallest available */
	     if (testTime == savedTime)
	       {
		  int n = (sizeof(message) - strlen (message) - 3);
		  if (n > 0)
		    {
		       /* for co-occurring events */
		       strcat (message, "\\n");
		       strncat (message, text, n);
		    }
		  message [strlen (message)-1] = 0;
	       }
	     else if (testTime < savedTime)
	       {
		  savedTime = testTime;
		  strncpy (message, text, sizeof(message));
		  message [strlen (message)-1] = 0;
	       }
	  }
     }
   alarmTime = (savedTime < INT_MAX) ? savedTime : -1;
   fclose (fd);
}

/*
 * Sounds alarm by mapping the message window
 */
static void
SetOffAlarm (void)
{
   char *beg, *end, *next;
   int this_w, this_h, lines = 1;

   if (Msg_Mapped)
     return;

   XBell (Xdisplay, 0);

   /* compute the window size */
   Msg.width = 10 * XTextWidth (Xfont, "M", 1);

   for (beg = message; beg; beg = next, lines++)
     {
	end = next = strstr (beg, "\\n");
	if (next)
	  next += 2;
	else
	  end = beg + strlen (beg);
	this_w = XTextWidth (Xfont, beg, (end-beg));
	if (Msg.width < this_w)
	  Msg.width = this_w;
     }

   Msg.width += 30;
   Msg.height = (lines+1) * font_height + 30;

   /* resize and center the window */
   XMoveResizeWindow (Xdisplay, Msg.win,
		      (DisplayWidth (Xdisplay, Xscreen)  - Msg.width ) / 2,
		      (DisplayHeight (Xdisplay, Xscreen) - Msg.height) / 2,
		      Msg.width, Msg.height);

   this_w = 5 * XTextWidth (Xfont, "M", 1) + 4;
   this_h = font_height + 4;

   XMoveResizeWindow (Xdisplay, msgButton.Dismiss,
		      8,
		      Msg.height - font_height - 12,
		      this_w, this_h);

   XMoveResizeWindow (Xdisplay, msgButton.Defer,
		      Msg.width - 5 * XTextWidth (Xfont, "M", 1) - 12,
		      Msg.height - font_height - 12,
		      this_w, this_h);

   XMapRaised (Xdisplay, Msg.win);
   XBell (Xdisplay, 0);
   Msg_Mapped = 1;
}
#endif	/* REMINDERS */

#ifndef _POSIX_VERSION
# if defined (__svr4__)
static int
getdtablesize (void)
{
   struct rlimit rlim;
   getrlimit (RLIMIT_NOFILE, &rlim);
   return rlim.rlim_cur;
}
# endif
#endif

/*
 * Loops forever, looking for stuff to do. Sleeps 1 minute if nothing to do
 */
static void
getXevent (void)
{
   XEvent ev;
   int num_fds;		/* number of file descriptors being used */
   struct timeval tm;
   struct tm *tmval;
   Atom wm_del_win;
   fd_set in_fdset;

   /* Enable delete window protocol */
   wm_del_win = XInternAtom (Xdisplay, "WM_DELETE_WINDOW", False);
   XSetWMProtocols (Xdisplay, Clock.win, &wm_del_win, 1);
#ifdef ICONWIN
   XSetWMProtocols (Xdisplay, Icon.win,  &wm_del_win, 1);
#endif
#ifdef REMINDERS
   XSetWMProtocols (Xdisplay, Msg.win,   &wm_del_win, 1);
#endif

#ifdef _POSIX_VERSION
   num_fds = sysconf (_SC_OPEN_MAX);
#else
   num_fds = getdtablesize ();
#endif

   while (1) {
      /* take care of all pending X events */
      while (XPending (Xdisplay)) {
	 XNextEvent (Xdisplay, &ev);
	 switch (ev.type) {
	  case ClientMessage:
	    /* check for delete window requests */
	    if ((ev.xclient.format == 32) &&
		(ev.xclient.data.l[0] == wm_del_win))
	      {
#ifdef REMINDERS
		 if (ev.xany.window == Msg.win)
		   {
		      XUnmapWindow (Xdisplay, Msg.win);
		      Msg_Mapped = 0;
		      GetNewAlarm (REPLACE);
		   }
		 else
#endif
		   return;	/* delete window is how this terminates */
	      }
	    break;

	  case Expose:
	  case GraphicsExpose:
	    /* need to re-draw a window */
	    if (ev.xany.window == Clock.win)
	      Draw_Window (&Clock, 1);
#ifdef ICONWIN
	    else if (ev.xany.window == Icon.win)
	      Draw_Window (&Icon, 1);
#endif
#ifdef REMINDERS
	    else
	      Draw_Window (&Msg, 1);
#endif
	    break;

	  case ConfigureNotify:
	    /* window has been re-sized */
	    if (ev.xany.window == Clock.win)
	      {
		 Clock.width  = ev.xconfigure.width;
		 Clock.height = ev.xconfigure.height;
	      }
#ifdef ICONWIN
	    else if (ev.xany.window == Icon.win)
	      {
		 Icon.width  = ev.xconfigure.width;
		 Icon.height = ev.xconfigure.height;
	      }
#endif
#ifdef REMINDERS
	    else
	      {
		 Msg.width  = ev.xconfigure.width;
		 Msg.height = ev.xconfigure.height;
	      }
#endif
	    break;

#ifdef REMINDERS
	  case KeyPress:
	    /* any key press to dismiss message window */
	    if (ev.xany.window == Msg.win)
	      {
		 XUnmapWindow (Xdisplay, Msg.win);
		 Msg_Mapped = 0;
		 GetNewAlarm (REPLACE);
	      }
	    break;
#endif

	  case ButtonPress:
#ifdef REMINDERS
	    /* button press to dismiss message window */
	    if (ev.xany.window == Msg.win)
	      {
		 if (ev.xbutton.subwindow == msgButton.Dismiss)
		   {
		      GetNewAlarm (REPLACE);
		      XUnmapWindow (Xdisplay, Msg.win);
		      Msg_Mapped = 0;
		   }
		 else if (ev.xbutton.subwindow == msgButton.Defer)
		   {
		      time_t t = time (NULL);
		      tmval = localtime (&t);
		      alarmTime = 3 + mk_time (tmval);
		      XUnmapWindow (Xdisplay, Msg.win);
		      Msg_Mapped = 0;
		   }

	      }
#endif
#ifdef MAIL
	    if (ev.xany.window == Clock.win)
	      {
#ifdef MAIL_SPAWN
		 /* left button action - spawn a mail reader */
		 if (ev.xbutton.button == Button1)
		   system (MAIL_SPAWN);
#endif
		 /* redraw the window */
		 Draw_Window (&Clock, 1);
	      }
#endif
            break;
	 }
      }

      /* Now wait for time out or new X event */
      FD_ZERO (&in_fdset);
      FD_SET (Xfd, &in_fdset);
      tm.tv_sec = clockUpdate;
      tm.tv_usec = 0;
      select (num_fds, &in_fdset, NULL, NULL, &tm);

      Draw_Window (&Clock, 0);
#ifdef ICONWIN
      Draw_Window (&Icon, 0);
#endif
   }
}

/*
 * Print an error message.
 */
static void
print_error (const char *fmt, ...)
{
   va_list arg_ptr;

   va_start (arg_ptr, fmt);
   fprintf (stderr, APL_NAME ": ");
   vfprintf (stderr, fmt, arg_ptr);
   fprintf (stderr,"\n");
   va_end (arg_ptr);
}
/*----------------------- end-of-file (C source) -----------------------*/
