#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "structs.h"

/* #define STAND */

/* for now */
#ifdef STAND
#define XtMalloc malloc
#define XtFree free
#define filename "structs.dat"
#endif

int ampm = 1;
int assume_day = 1;

/*
 * The purpose of matchbits is to return a boolean (though it
 * really doesn't) indicating whether bit num is set in bits.
 * This is used to help check if a simple date is included in
 * a complicated one.
 */
int matchBits( num, bits )
int num, bits;
{
  return (bits & (1<<(num-1)));
}

/*
 * Turn a string expression into a set of bits (in an int)
 * The bits would probably be appropriately be abstracted into
 * a type set.
 * 
 * expression:                      bits:
 *
 *      *                           all
 *     a-b                          bits a to b set
 *                                  if b is less than a, then
 *                                  bits from b to max, and
 *                                  0 to a are set.
 *      a                           bit a is set
 *
 * All expressions but * must be enclosed in {}, and multiple
 * expressions may be separated by commas. Thus
 * {a-b, c-d, e, f, g-h} is a valid expression, as is *.
 */
int expToBits( e, max )
char *e;
int max;
{
  int i, j, n, first = 0, range; /* shutup! */
  int rtn = 0;

  if ( e[0] == '\0' )
    return (int) 0;
  if ( e[0] == '*' )
    return (int)~0;
  if ( e[0] != '{' )
    return ( 1<<(atoi(e)-1) );

  i = 1; n = 0; range = 0;
  while ( i < strlen( e ) )
    {
      while ( e[i] == ' ' || e[i] == '-' || e[i] == ',' || e[i] == '}' )
	i++;

      while ( e[i] != ' ' && e[i] != '-' && e[i] != ',' && e[i] != '}' )
	n = 10 * n + e[i++] - '0';

      switch( e[i] )
	{
	case ' ':
	  break;
	case '-':
	  range = 1;
	  first = n;
	  n = 0;
	  break;
	case ',':
	case '}':
	  if ( range )
	    {
	      range = 0;
	      if ( n < first )
		n += max; /* this with mod to wrap */
	      for ( j = first; j <= n; j++ )
		rtn |= (1<<((j-1)%max));
	    }
	  else
	    rtn |= (1<<(n-1));
	  n = 0;
	  if ( e[i] == '}' )
	    i = strlen( e );
	  break;
	default: /* buggy stuff */
	  fprintf( stderr, "expToBits: %c\n", e[i] );
	  break;
	}
    }
  return rtn;
}

/*
 * Converts a day string expression to its bits
 */
int dayToRep( w )
char *w;
{
  return expToBits( w, 7 ) & wildDays;
}

/*
 * Converts a month string expression to its bits
 */
int monthToRep( m )
char *m;
{
  return expToBits( m, 12 ) & wildMonths;
}

/*
 * Converts a date string expression to its bits
 */
int dateToRep( d )
char *d;
{
  return expToBits( d, 31 ) & wildDates;
}

/*
 * Converts a year string expression (limited) to an int.
 * No ranges are allowed in its representation, except for wild.
 */
int yearToRep( y )
char *y;
{
  if ( y[0] == '*' )
    return wildYear;
  else
    return atoi( y );
}

/*
 * Takes a file and adds it to an events list.
 * An events list is a fully general list of all events the program
 * knows about.
 *
 * file format:
 *
 *    w m/d/y h:m:s h:m:s
 *    # Header text line (for single line display)
 *    # description text line 1...
 *    # description text line n...
 * 
 * any of {w,m,d} may be formed {n1,n2-n3,n5}; y may be * or n1.
 */
int addFileToEvents( file, events )
char *file;
event **events;
{
  FILE *f;
  event *new;
  date *w, **wm;
  int l;
  char t[DESCMAX];
  int used;
  int badlines=0, linenum=1;
  int version = 0;
  char error[10000];
  char error2[10000];

  error[0] = '\0';
  strcat(error, "Found %d error%s while reading `%s'.  Error%s on line%s:\n");

  f = fopen( file, "r" );

  if ( f == NULL ) /* needs better stuff here */
    {
      *events = NULL;
      return 0;
    }

  while ( !feof( f ) )
    {
      new = (event *) XtMalloc( sizeof( event ) );

      new->next = *events;
      new->info = (desc *) XtMalloc( sizeof( desc ) );
      new->info->desc = NULL;
      *events = new;

      wm = &new->when;

      l = getc( f ); /* i think this dies on null files *bug* */
      ungetc( l, f );

      if ( l == '_' )
	{
	  fscanf( f, "_version %d\n", &version );
	  l = getc( f );
	  ungetc( l, f );
	}

      while ( l != EOF && l != '#' )
	{
	  linenum++;
	  w = (date *) XtMalloc( sizeof( date ) );
	  *wm = w;
	  w->more = NULL;
	  wm = &w->more;

	  if ( version == 0 )
	    {
	      char wk[10], m[10], d[100], y[10];
	      int hs, ms, ss, he, me, se;

	      fscanf( f, "%[^ ] %[^/]/%[^/]/%[^ ] %d:%d:%d %d:%d:%d\n",
		      wk, m, d, y, &hs, &ms, &ss, &he, &me, &se );
	      new->info->activity = 0;
	      new->info->scheduled = 1;
	      new->info->daylike = 0;
	      w->days = dayToRep( wk );
	      w->months = monthToRep( m );
	      w->dates = dateToRep( d );
	      w->year = yearToRep( y ); /* must restructure */
	      w->starttime.hour = hs; /* bogus */
	      w->starttime.minute = ms;
	      w->starttime.second = ss;
	      w->endtime.hour = he;
	      w->endtime.minute = me;
	      w->endtime.second = se;
	    }
	  else
	    {
	      char *wk, *m, *d, *y;
	      char *hs, *ms, *ss, *he, *me, *se;
	      char *activity, *scheduled, *daylike;
	      char *ptr;
	      char *no_spec = "  %d: no %s specified.\n";
	      char linebuf[DESCMAX];

#define NO_SPEC(spec)  { \
			 badlines++; \
			 sprintf(error2, no_spec, linenum, (spec)); \
			 strcat(error, error2); \
		       }

	      ptr = fgets(linebuf, DESCMAX, f);

	      wk = ptr; ptr = strchr(ptr, ' ');  *ptr++ = '\0';

	      m = ptr; ptr = strchr(ptr, '/');  *ptr++ = '\0';
	      if (*m == '\0')   NO_SPEC("month");

	      d = ptr; ptr = strchr(ptr, '/');  *ptr++ = '\0';
	      if (*d == '\0')   NO_SPEC("days");

	      y = ptr; ptr = strchr(ptr, ' ');  *ptr++ = '\0';
	      if (*y == '\0')   NO_SPEC("years");

	      hs = ptr; ptr = strchr(ptr, ':');  *ptr++ = '\0';
	      if (*hs == '\0')   NO_SPEC("start hour");

	      ms = ptr; ptr = strchr(ptr, ':');  *ptr++ = '\0';
	      if (*ms == '\0')   NO_SPEC("start minute");
	     
	      ss = ptr; ptr = strchr(ptr, ' ');  *ptr++ = '\0';
	      if (*ss == '\0')   NO_SPEC("start seconds");
	     
	      he = ptr; ptr = strchr(ptr, ':');  *ptr++ = '\0';
	      if (*he == '\0')   NO_SPEC("end hour");
	     
	      me = ptr; ptr = strchr(ptr, ':');  *ptr++ = '\0';
	      if (*me == '\0')   NO_SPEC("end minute");
	     
	      se = ptr; ptr = strchr(ptr, ' ');  *ptr++ = '\0';
	      if (*se == '\0')   NO_SPEC("end seconds");
	     
	      activity = ptr; ptr = strchr(ptr, ' ');  *ptr++ = '\0';
	      if (*activity == '\0')   NO_SPEC("activity flag");
	     
	      scheduled = ptr; ptr = strchr(ptr, ' ');  *ptr++ = '\0';
	      if (*scheduled == '\0')   NO_SPEC("scheduled flag");
	     
	      daylike = ptr; ptr = strchr(ptr, '\n');  *ptr++ = '\0';
	      if (*daylike == '\0')   NO_SPEC("day-like flag");
	     
	      w->days = dayToRep( wk );
	      w->months = monthToRep( m );
	      w->dates = dateToRep( d );
	      w->year = yearToRep( y ); /* must restructure */
	      w->starttime.hour = atoi(hs); /* bogus */
	      w->starttime.minute = atoi(ms);
	      w->starttime.second = atoi(ss);
	      w->endtime.hour = atoi(he);
	      w->endtime.minute = atoi(me);
	      w->endtime.second = atoi(se);
	      new->info->activity = atoi(activity);
	      new->info->scheduled = atoi(scheduled);
	      new->info->daylike = atoi(daylike);
	    }

	  l = getc( f );
	  ungetc( l, f );
	}

      used = 0;

      while ( l != EOF && l == '#' && used != DESCMAX-1 )
	{
	  linenum++;
	  l = getc( f );
	  fgets( &t[used], DESCMAX-used, f ); /* includes cr */
	  used = strlen( t );

	  l = getc( f );
	  ungetc( l, f );
	}

      /* bug; put in code to skip characters over DESCMAX */

      if ( used != 0 )
	{
	  new->info->desc = (char *) XtMalloc( used+1 );
	  memmove( new->info->desc, t, used+1 );
	}
    }

  if ( badlines )
    {
      strcat(error, "\nPlease use an editor to remove or fix.");
      sprintf(error2, error,
	      badlines,
	      (badlines > 1 ? "s" : ""),
	      file,
	      (badlines > 1 ? "s are" : " is"),
	      (badlines > 1 ? "s" : ""));
      MyError(1, error2);
    }

  fclose( f );
  return 1;
}

void all( bits, str )
int bits;
char *str;
{
  char work[10];
  char bld[100];
  int i, first;
  int multi = 0, range = 0, started = 0;

  sprintf( bld, "" );

  for ( i = 1; i < 33; i++ )
    {
      if ( (!range) && (bits & 1) )
	{
	  range = 1;
	  first = i;
	}
      else
	if ( range && !(bits & 1) )
	  {
	    if ( !started )
	      started = 1;
	    else
	      strcat( bld, "," );
	    
	    range = 0;
	    if ( i == first+1 )
	      sprintf( work, "%d", first );
	    else
	      {
		multi = 1;
		sprintf( work, "%d-%d", first, i-1 );
	      }
	    strcat( bld, work );
	  }

      if ( i == 32 && range )
	{
	  if ( i == first+1 )
	    sprintf( work, "%d", first );
	  else
	    {
	      multi = 1;
	      sprintf( work, "%d-%d", first, i-1 );
	    }
	  strcat( bld, work );	  
	}
      bits = bits >> 1;
    }
  if ( multi || strchr( bld, ',' ) )
    sprintf( str, "{%s}", bld );
  else
    strcpy( str, bld );
}

int first( bits )
int bits;
{
  int i;
  for ( i = 0; i < 31; i++ )
    {
      if ( bits & 1 )
	return i + 1;
      bits = bits >> 1;
    }
  return i+1;			/* ??????? can fall thru bottom -- */
				/* return what ????? */
}

int dumpEventsToFile( events, file )
char *file;
event *events;
{
  FILE *f;
  char *s, *e, ms[100], ds[100];
  date *w;

  f = fopen( file, "w" );
  if ( f == NULL )
    return 0;

  fprintf( f, "_version 1\n" );
  while (events != NULL)
    {
      w = events->when;
      while ( w != NULL )
	{
	  if (w->months != 0 && w->dates != 0)
	    {
	      all( w->months, ms );
	      all( w->dates, ds );
	      fprintf( f, "* %s/%s/%d %d:%d:%d %d:%d:%d %d %d %d\n",
		       ms,
		       ds,
		       w->year,
		       w->starttime.hour,
		       w->starttime.minute,
		       w->starttime.second,
		       w->endtime.hour,
		       w->endtime.minute,
		       w->endtime.second,
		       events->info->activity,
		       events->info->scheduled,
		       events->info->daylike );
	    }
	  w = w->more;
	}

      s = events->info->desc;
      while ( s != NULL )
	{
	  e = strchr( s, '\n' );
	  if ( e == 0 )
	    e = strchr( s, '\0' );
	  fprintf( f, "#%.*s\n", e-s, s );
	  s = e + ( (*e == '\0') ? 0 : 1);
	  if (*s == '\0')
	    s = NULL;
	}
      events = events->next;
    }
  fclose( f );
  return 1;
}

deleteFromEvents( e, what )
event **e;
desc *what;
{
  event *last = NULL, *current;
  date *w1, *w2;

  current = *e;

  while ( current != NULL )
    {
      if ( current->info == what )
	{
	  w1 = current->when;
	  while ( w1 != NULL )
	    {
	      w2 = w1;
	      w1 = w1->more;
	      XtFree( (char *)w2 );
	    }
	  XtFree( current->info->desc );
	  XtFree( (char *)current->info );
	  if ( last == NULL )
	    *e = current->next;
	  else
	    last->next = current->next;
	  XtFree( (char *)current );
	  current = NULL;
	}
      else
	{
	  last = current;
	  current = current->next;
	}
    }
}

void addToEvents( e, sels, numsels,
		  start, end,
		  ofni )
event **e;
CalendarSelection *(sels[]);
int numsels;
CalTime start, end;
desc *ofni;
{
  int i, j;
  event *new;
  date *ptr;

  new = (event *) XtMalloc( sizeof( event ) );

  new->next = *e;
  new->info = (desc *) XtMalloc( sizeof( desc ) );
  new->info->desc = NULL;
  ptr = (date *) XtMalloc( sizeof( date ) );
  new->when = ptr; /* bugs for null selections... */
  *e = new;

  for ( i = 0; i < numsels; i++ )
    {
      if ( i > 0 )
	{
	  ptr->more = (date *) XtMalloc( sizeof( date ) );
	  ptr = ptr->more;
	}

      ptr->more = NULL;
      ptr->starttime = start;
      ptr->endtime = end;
      ptr->days = wildDays;

      ptr->months = 1 << ( (sels[i])[0].month - 1);
      ptr->year = (sels[i])[0].year;
      ptr->dates = 0;

      for ( j = 0; (sels[i])[j].day != 0; j++ )
	ptr->dates |= 1 << ( (sels[i])[j].day - 1);
    }

  new->info->desc = (char *) XtMalloc( strlen( ofni->desc ) + 1 ); /* bug */
  memmove( new->info->desc, ofni->desc, strlen( ofni->desc ) + 1 );

  new->info->activity = ofni->activity;
  new->info->scheduled = ofni->scheduled;
  new->info->daylike = ofni->daylike;
}

/*
 * Used by cvtEventToSimple to copy a simple event into
 * a list it is building. Enters it sorted.
 */
void addSimpToList( simp, list )
simpleEvent *simp, **list;
{
simpleEvent *temp;
simpleEvent *ptr;
simpleEvent **lastPtr;

temp = (simpleEvent *) XtMalloc( sizeof( simpleEvent ) );
temp->starttime = simp->starttime;
temp->endtime = simp->endtime;
temp->info = simp->info;

if ( *list == NULL )
  {
    *list = temp;
    temp->next = NULL;
    return;
  }

ptr = *list;
lastPtr = list;

while ( ptr != NULL )
  if ( (((ptr->starttime.hour * 60) + ptr->starttime.minute) >
       ((temp->starttime.hour * 60) + temp->starttime.minute )) ||
       ptr->info->daylike < temp->info->daylike )
    {
      temp->next = ptr;
      *lastPtr = temp;
      return;
    }
  else
    {
      lastPtr = &(ptr->next);
      ptr = ptr->next;
    }

if ( ptr == NULL )
  {
    temp->next = NULL;
    *lastPtr = temp;
  }
}

static int py=0, pm=0, p[42];

getp( year, month )
int year, month;
{
  if ( year != py || month != pm )
    {
      py = year;
      pm = month;
      cal( pm, py, p );
    }
}

/*
 * return day of week (1 - 7) (Sun - Sat)
 */
int dayof( year, month, day )
{
  int i;

  getp( year, month );

  for ( i = 0; i < 42; i++ )
    if ( p[i] == day )
      return ( i%7 + 1 );

  return 0;			/* have to return something! ?????? */
}

/*
 * return a calendarselection array containing the days that d occurs
 * in a given month
 */
getalldates( e, d, sels, numsels )
event *e;
desc *d;
CalendarSelection *(sels[]);
int *numsels;
{
  int day, dw;
  date *when;
  int i = 0, j;
  int month, year;

  while ( e != NULL )
    {
      if ( d == e->info )
	{
	  when = e->when;
	  while ( when != NULL )
	    {
	      j = 0;
	      month = first( when->months );
	      year = when->year;

	      dw = dayof( year, month, 1 );
	      /* this is scrod in 1752 */
	      for ( day = 1; day < 32; day++ )
		{

		  /* shit... structure being blown away */
		  if ( sels[i] == NULL )
		    sels[i] = (CalendarSelection *)
		      XtMalloc( 32 * sizeof( CalendarSelection ) );

		  if ( matchBits( day, when->dates ) &&
		      matchBits( dw, when->days ))
		    {
		      (sels[i])[j].month = month;
		      (sels[i])[j].year = year;
		      (sels[i])[j].day = day;
		      j++;
		    }
		  dw = dw%7 + 1;
		}
	      (sels[i])[j].month = month;
	      (sels[i])[j].year = year;
	      (sels[i])[j].day = 0;
	      when = when->more;
	      i++;
	    }
	  e = NULL;
	}
      else
	e = e->next;
    }
  *numsels = i;
}

/*
 * return a calendarselection array containing the days that d occurs
 * in a given month
 */
getdates( e, d, year, month, s )
event *e;
desc *d;
int month, year;
CalendarSelection s[];
{
  int day, dw;
  date *when;
  int i = 0;

  while ( e != NULL )
    {
      if ( d == e->info )
	{
	  when = e->when;
	  while ( when != NULL )
	    {
	      if ( when->year == year || when->year == 0 )
		if ( matchBits( month, when->months ) )
		  {
		    dw = dayof( year, month, 1 );
		    /* this is scrod in 1752 */
		    for ( day = 1; day < 32; day++ )
		      {
			if ( matchBits( day, when->dates ) &&
			    matchBits( dw, when->days ))
			  {
			    s[i].year = year;
			    s[i].month = month;
			    s[i].day = day;
			    i++;
			  }
			dw = dw%7 + 1;
		      }
		  }
	      when = when->more;
	    }
	  e = NULL;
	}
      else
	e = e->next;
    }
  s[i].month = month;
  s[i].day = 0;
  s[i].year = year;
}

/*
 * Return a list of simpleEvents containing all entries in the
 * event list e that match to month/day/year.
 * SimpleEvents are useful structures to higher level routines
 * that want to print the events for a day; events are in a
 * slightly difficult format to use, which is why this conversion
 * abstraction exists. The event structure format is also likely
 * to change.
 */
simpleEvent *cvtEventToSimple( e, year, month, day )
event *e;
int year, month, day;
{
simpleEvent *list, s;
date *when;

list = NULL;

while ( e != NULL )
  {
    when = e->when;
    while ( when != NULL )
      {
	if ( when->year == year || when->year == 0 )
	  if ( matchBits( month, when->months ) )
	    if ( matchBits( day, when->dates ) )
	      if ( matchBits( dayof( year, month, day ), when->days ) )
		{
		  s.starttime = when->starttime;
		  s.endtime = when->endtime;
		  s.info = e->info;
		  addSimpToList( &s, &list );
		}
	when = when->more;
      }
    e = e->next;
  }
return list;
}

/*
 * Free a list of simpleEvents that is not needed anymore.
 */
freeSimple( s )
simpleEvent *s;
{
simpleEvent *p;

p = s;
while ( p != NULL )
  {
    /* don't free description - something else points to it */
    XtFree( (char *)p );
    p = p->next; /* "contents undisturbed" */
  }
}

/*
 * Free a list of events
 */
void freeEvents( e )
event **e;
{
  event *p, *q;
  date *d, *f;

  p = *e;

  while ( p != NULL )
    {
      XtFree( p->info->desc ); /* free description text */
      XtFree( (char *)p->info ); /* free description */

      d = p->when;
      while ( d != NULL ) /* free date list */
	{
	  f = d;
	  d = d->more;
	  XtFree( (char *)f );
	}
      q = p;
      p = p->next;
      XtFree( (char *)q );
    }
  *e = NULL;
}

/*
 * Debugging routine
 */
void printSimp( list )
simpleEvent *list;
{
simpleEvent *here;

here = list;
while ( here != NULL )
  {
    fprintf( stdout, "%d %d %d\n",
	     here->starttime.hour,
	     here->starttime.minute,
	     here->starttime.second );
    if ( here->info->desc != NULL )
      fprintf( stdout, "%s\n", here->info->desc );
    here = here->next;
  }
}

void timeToStr( t, str )
CalTime t;
char *str;
{
  char ap[3];
  int h;

  ap[1] = 'm';  ap[2] = '\0';

  if ( !ampm )
    sprintf( str, "%02d:%02d",
	     t.hour, t.minute );
  else
    {
      h = t.hour;
      if ( h < 12 )
	{
	  if ( h == 0 )
	    h = 12;
	  ap[0] = 'a';
	}
      else
	{
	  ap[0] = 'p';
	  if ( h > 12 )
	    h -= 12;
	}
      sprintf( str, "%2d:%02d %s",
	       h, t.minute, ap );
    }
}

char *suffix( d )
int d;
{
  static char *suff[] = { "st", "nd", "rd", "th",};

  if ( d > 9 && d < 21 )
    return suff[3];
  if ( ((d-1)%10) > 2 )
    return suff[3];
  return suff[(d%10) - 1];
}

char    *smon1[]= {
        "January", "February", "March", "April",
        "May", "June", "July", "August",
        "September", "October", "November", "December",
};

char *month( i )
int i;
{
  return smon1[i - 1];
}

void getTimeNum( s, n )
char **s;
int *n;
{
  char *e;
  int q;

  while ( isspace( **s ) ) (*s)++;
  e = *s;

  q = 0;
  while ( isdigit( *e ) ) { q = 10*q + ( *e - '0' ); e++; }
  if ( e == *s )
    q = -1;
  *n = q;
  *s = e;

  while ( isspace( **s ) ) (*s)++;
  if ( **s == ':' )
    (*s)++;
}

void strToTime( s, t )
char *s;
CalTime *t;
{
  int spec = 0;

  getTimeNum( &s, &t->hour );
  getTimeNum( &s, &t->minute );
  getTimeNum( &s, &t->second );

  if ( t->minute == -1 )
    t->minute = 0;
  if ( t->second == -1 )
    t->second = 0;

  if ( (islower( *s ) ? toupper( *s ) : *s) == 'P' )
    {
      if ( t->hour < 12 )
	t->hour += 12;
      spec = 1;
    }
  if ( (islower( *s ) ? toupper( *s ) : *s) == 'A' )
    {
      if ( t->hour > 11 )
	t->hour -= 12;
      spec = 1; /* if they said 15:00AM, tough */
    }

  if ( !spec && assume_day )
    if ( t->hour < 8 )
      t->hour += 12;
}

/*
#ifdef STAND
main()
{
  event *events;
  simpleEvent *simp;

  events = NULL;

  addFileToEvents( filename, &events );
  simp = cvtEventToSimple( events, 1573, 9, 2 );
  printSimp( simp );
}
#endif
*/
