/* ReminderCheck checks and alerts about event generated with Reminder */

/* $Id: CheckMain.c,v 1.9 93/03/06 22:39:36 Matti_Rintala Exp $ */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <exec/types.h>
#include <dos/dosextens.h>
#include <utility/tagitem.h>
#include <exec/libraries.h>

#include <libraries/reqtools.h>

#ifdef __SASC
#include <proto/exec.h>
#include <proto/reqtools.h>
#endif

#ifdef _DCC
#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/reqtools_protos.h>
#include <dos/dos.h>
#include <clib/dos_protos.h>
#include <workbench/startup.h>

struct Library *CxBase = NULL;	/* DICE does not auto open commodities.library */
struct Library *IconBase = NULL; /* Nor icon.library */

/* DICE does not have difftime(), although its prototype is in time.h. This is
   a bug in DICE, so we'll have to declare difftime() as macro */
#define difftime(a,b) ((double)(a) - (double)(b))

#endif

#include "Constants.h"

/* Responses from makealarm */
#define QUIT 0
#define ACK 2

/* Maximum width of requester */
#define MAXWIDTH 30

char *VersionStr = "$VER: ReminderCheck 1.00";

static int makealarm(time_t *date, const char *text);
static void CloseLibraries(void);

static void gettoday(time_t *today);
static void makedate(time_t *date, time_t *today,
		     short day, short month, short year, short wday);
static void makeprevdate(time_t *date, time_t *today,
			 short day, short month, short year, short wday);
static void putack(struct EventNode *node, time_t acked);
static short weekday(short day, short month, short year);

char *filename;			/* Filename of the database file */

#define min(a,b) (((a)<(b)) ? (a) : (b))

#ifdef _DCC
static struct FileLock *oldlock = NULL;

/* DICE uses different entry point for workbench startup */
int wbmain(struct WBStartup *msg) {

  /* We have to change to correct directory */
  oldlock = CurrentDir(msg->sm_ArgList[0].wa_Lock);
  return main(0, (char **)msg);	/* Simply call main() */
}
#endif

/* ReqTools library is only opened, if alerts are needed */
struct ReqToolsBase *ReqToolsBase = NULL;

int main(int argc, char **argv) {

  UBYTE **ttypes;
  FILE *infile;
  long fpos;
  struct EventNode *node;
  time_t today, tim, alarm, acked, stamp;
  double diff;
  int response, interval;
  size_t size;
  
  atexit(CloseLibraries);	/* Close libraries on exit */

#ifdef _DCC
  /* With DICE we have to open commodities.library and icon.library, too */
  if (!(CxBase = OpenLibrary("commodities.library", 0))) {
    printf("Can't open commodities.library!\n");
    exit(-1);
  }
  if (!(IconBase = OpenLibrary("icon.library", 0))) {
    printf("Can't open icon.library!\n");
    exit(-1);
  }
#endif

  /* Get today's date */
  gettoday(&today);

  /* Get memory for one node */
  if ((node = malloc(sizeof(struct EventNode))) == NULL)
    exit(-1);

  /* Parse arguments */
  ttypes = ArgArrayInit(argc, argv);
  /* Try to find database filename */
  filename = ArgString(ttypes, FILETYPE, DEFAULTFILE);
  /* And checking interval */
  interval = ArgInt(ttypes, INTRVLTYPE, DEFAULTINTRVL);

  /* Try to open database */
  infile = fopen(filename, "r+b");

  /* Clear up argument parsing */
  ArgArrayDone();

  /* Exit if database open failed */
  if (infile == NULL)
    exit(0);

  /* Check the stamp */
  if (fread(&stamp, 1, sizeof(time_t), infile) == sizeof(time_t)) {
    tim = time(NULL);
    diff = difftime(tim, stamp); /* Current interval in seconds */
    if (diff >= (double)interval * 3600.0) {
      /* Write new stamp */
      fseek(infile, 0, SEEK_SET);
      fwrite(&tim, 1, sizeof(time_t), infile);
      fflush(infile);

      /* Go through all events in file */
      while (TRUE) {
	/* Read event to node */
	clearerr(infile);
	while (TRUE) {
	  fpos = ftell(infile);	/* Remember position */
	  size = fread(&node->entry, 1, sizeof(struct EventNode)-sizeof(struct Node),
		       infile);
	  /* Stop looping if error or wrong number of bytes read */
	  if (ferror(infile) || size < sizeof(struct EventNode)-sizeof(struct Node))
	    break;
	  /* Also if event is not deleted */
	  if (strcmp(node->entry, DELETEDSTR))
	    break;
	}
	/* Stop looping if end-of-file */
	if (ferror(infile) || size < sizeof(struct EventNode)-sizeof(struct Node))
	  break;
	
	/* Get event's next alarm date */
	makedate(&alarm, &today, node->day, node->month, node->year, node->wday);
	/* And when it has been acknowledged last time */
	makedate(&acked, &today, node->aday, node->amonth, node->ayear, 0);
	
	/* If acked time is the as alarm time, this event has already been acked */
	if (acked == alarm)
	  continue;
	
	/* If difference between now and alarm time is less than 'before'
	   value, alarm is made */
	diff = difftime(alarm, today);
	if (diff < 0.0 || diff > ((double)node->before)*((double)3600*24)) {
	  /* If next alarm is too far away or in past, try previous alarm */
	  makeprevdate(&alarm, &today, node->day, node->month,
		       node->year, node->wday);
	  
	  /* If already acked, give up */
	  if (acked == alarm)
	    continue;
	  
	  /* If difference between alarm and now is more than 'after' value,
	     no alarm is made */
	  diff = difftime(today, alarm);
	  if (diff < 0.0 || diff > (double)node->after*(3600.0*24.0))
	    continue;
	}
	response = makealarm(&alarm, node->text);
	/* If response was 'quit', stop making alarms */
	if (response == QUIT)
	  break;
	/* If it was 'acknowledged', write the ack information to file */
	if (response == ACK) {
	  /* If autodelete flag is set, whole event can be deleted */
	  if (node->autodelete)
	    strcpy(node->entry, DELETEDSTR);
	  else
	    putack(node, alarm);
	  
	  fseek(infile, fpos, SEEK_SET);
	  size = fwrite(&node->entry, 1, sizeof(struct EventNode)-sizeof(struct Node),
			infile);
	  /* Stop if error */
	  if (ferror(infile) || size < sizeof(struct EventNode)-sizeof(struct Node))
	    break;
	  fflush(infile);
	}
      }
    }
  }

  fclose(infile);
  free(node);
  
  exit(0);
}
  

/* gettoday gets today's date (without time of day) */
static void gettoday(time_t *today) {

  struct tm *tmptr;

  time(today);			/* Get date */
  
  tmptr = localtime(today);	/* Change to struct */
  /* Now zero all time of day fields */
  tmptr->tm_sec = tmptr->tm_min = tmptr->tm_hour = 0;
  *today = mktime(tmptr);	/* Convert back to time_t */
}

/* putack puts the acknowledgement date to EventNode */
static void putack(struct EventNode *node, time_t acked) {

  struct tm *tmptr;

  tmptr = localtime(&acked);	/* Change to struct */
  node->aday = tmptr->tm_mday;
  node->amonth = tmptr->tm_mon + 1;
  node->ayear = tmptr->tm_year + 1900;
}

static monthlen[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/* makedate calculates next date that satisfies given conditions */
static void makedate(time_t *date, time_t *today,
		     short dday, short dmonth, short dyear, short dwday) {

  struct tm d;
  int found;
  short year, month, day;
  short year_max, month_max, day_max;
  short year_now, month_now, day_now;

  d = *localtime(today);	/* Current date */
  day_now = d.tm_mday;
  month_now = d.tm_mon + 1;
  year_now = d.tm_year + 1900;

  found = FALSE;
  year = dyear;
  if (year != 0)
    year_max = year;		/* Year is not wildcard */
  else {
    year = year_now;		/* Otherwise start form now */
    year_max = MAXYEAR;
  }

  /* Go through all possible years */
  for (; year <= year_max; year++) {
    /* If we are in the past, try another */
    if (year < year_now)
      continue;

    month = dmonth;
    if (month != 0)
      month_max = month;	/* Month is not wildcard */
    else {
      month = (year == year_now) ? month_now : 1; /* Start from now */
      month_max = 12;
    }

    /* If we are in the past, try another year */
    if (year == year_now && month_max < month_now)
      continue;

    /* Go through all possible months */
    for (; month <= month_max; month++) {
      day = dday;
      day_max = monthlen[month-1] + ((year%4 == 0) ?
				     ((year%400 == 0) ? 0 : 1) : 0);
      if (day != 0) {
	if (dwday == 0)
	  day_max = day;	/* No wildcards in day */
	else {
	  day = min(day, day_max);
	  day_max = min(day+6, day_max); /* Scope is one week */
	}
      }
      else {			/* Day is a wildcard */
	day = (year == year_now && month == month_now) ? day_now : 1;
      }

      /* If we are in the past, try another month */
      if (year == year_now && month == month_now && day_max < day_now)
	continue;

      /* We should not start earlier than present */
      if (year == year_now && month == month_now && day < day_now)
	day = day_now;

      /* We have to find the day */
      for (; day <= day_max; day++) {
	if (dwday == 0 || dwday == weekday(day, month, year)) {
	  found =TRUE;
	  break;
	}
      }
      if (found) break;
    }
    if (found) break;
  }

  /* If found, use the date */
  if (found) {
    /* Now calculate the date */
    d.tm_sec = d.tm_min = d.tm_hour = 0;
    d.tm_mday = day;
    d.tm_mon = month - 1;
    d.tm_year = year - 1900;
    
    *date = mktime(&d);		/* Make the date */
  }
  else {
    /* Else try previous one */
    makeprevdate(date, today, dday, dmonth, dyear, dwday);
  }
}

/* makeprevdate calculates previous date that satisfies given conditions */
static void makeprevdate(time_t *date, time_t *today,
			 short dday, short dmonth, short dyear, short dwday) {

  struct tm d;
  int found;
  short year, month, day;
  short year_min, month_min, day_min;
  short year_now, month_now, day_now;

  d = *localtime(today);	/* Current date */
  day_now = d.tm_mday;
  month_now = d.tm_mon + 1;
  year_now = d.tm_year + 1900;

  found = FALSE;
  year = dyear;
  if (year != 0)
    year_min = year;		/* Year is not wildcard */
  else {
    year = year_now;		/* Otherwise start form now */
    year_min = MINYEAR;
  }

  /* Go through all possible years */
  for (; year >= year_min; year--) {
    /* If we are in the future, try next */
    if (year > year_now)
      continue;

    month = dmonth;
    if (month != 0)
      month_min = month;	/* Month is not wildcard */
    else {
      month = (year == year_now) ? month_now : 12; /* Start from now */
      month_min = 1;
    }

    /* If we are in the future, try another year */
    if (year == year_now && month_min > month_now)
      continue;

    /* Go through all possible months */
    for (; month >= month_min; month--) {
      day_min = dday;
      day = monthlen[month-1] + ((year%4 == 0) ?
				 ((year%400 == 0) ? 0 : 1) : 0);
      if (day_min != 0) {
	if (dwday == 0)
	  day = day_min;	/* No wildcards in day */
	else {
	  day = min(day_min+6, day); /* Scope is one week */
	}
      }
      else {			/* Day is a wildcard */
	day = (year == year_now && month == month_now) ? day_now : day;
	day_min = 1;
      }

      /* If we are in the future, try another month */
      if (year == year_now && month == month_now && day_min > day_now)
	continue;

      /* We should not start later than present */
      if (year == year_now && month == month_now && day > day_now)
	day = day_now;

      /* We have to find the day */
      for (; day >= day_min; day--) {
	if (dwday == 0 || dwday == weekday(day, month, year)) {
	  found =TRUE;
	  break;
	}
      }
      if (found) break;
    }
    if (found) break;
  }

  d.tm_sec = d.tm_min = d.tm_hour = 0;

  /* If date found */
  if (found) {
    /* Now calculate the date */
    d.tm_mday = day;
    d.tm_mon = month - 1;
    d.tm_year = year - 1900;
  }
  else {
    /* Else use 1.1.1990 */
    d.tm_mday = 1;
    d.tm_mon = 0;
    d.tm_year = 90;
  }

  *date = mktime(&d);		/* Make the date */
}

/* weekday gives the day of the week */
static short weekday(short day, short month, short year) {

  struct tm date;
  time_t time;

  date.tm_sec = date.tm_min = date.tm_hour = 0;
  date.tm_mday = day;
  date.tm_mon = month - 1;
  date.tm_year = year - 1900;
  time = mktime(&date);		/* Make a time_t */
  date = *localtime(&time);	/* Calculate back to struct */

  /* Day of the week is now in date */
  return (short)((date.tm_wday == 0) ? 7 : date.tm_wday);
}

static char buffer[TEXTLEN+19];	/* Place for requester text */

/* makealarm puts up an alarm requester */
static int makealarm(time_t *date, const char *text) {

  char *str = buffer;
  struct tm *d;
  int i;
  
  /* Calculate date string */
  d = localtime(date);
  strftime(str, MAXWIDTH, "%a %d-%b-%Y :\n", d); /* Print date */
  str += strlen(str);

  /* divide text into lines */
  while (strlen(text) > MAXWIDTH) {
    /* Find first space in the end of line */
    for (i = MAXWIDTH; i > 0 && text[i] != ' '; i--);
    if (i < 2)
      i = MAXWIDTH;		/* If no space found */

    strncpy(str, text, i);	/* Copy the line into buffer */
    str += i;
    text += i;
    *str++ = '\n';		/* Add linefeed to end of line */

    while (*text != '\0' && *text == ' ')
      text++;			/* Skip spaces in the beginning of new line */
  }

  if (*text != '\0')
    strcpy(str, text);		/* Copy the last partial line */

  /* Open ReqTools library if not already open */
  if (ReqToolsBase == NULL) {
    if ((ReqToolsBase = (struct ReqToolsBase *)
	 OpenLibrary(REQTOOLSNAME, REQTOOLSVERSION)) == NULL)
      return QUIT;		/* If cannot open, quit program */
  }

  /* Make the alarm requester */
  return (int)rtEZRequestTags(buffer, "_Go away!|_Ackn|_Quit!", NULL, NULL,
			      RT_PubScrName, (Tag)"Workbench",
			      RT_ReqPos, REQPOS_CENTERSCR,
			      RT_Underscore, (Tag)'_', 
			      RTEZ_Flags, EZREQF_CENTERTEXT, TAG_END);
}

/* CloseLibraries simply closes the opened libraries */
static void CloseLibraries(void) {

  if (ReqToolsBase != NULL)
    CloseLibrary((struct Library *)ReqToolsBase);

#ifdef _DCC
  /* With DICE we have to close commodities.library and icon.library, too */
  if (CxBase != NULL)
    CloseLibrary(CxBase);

  if (IconBase != NULL)
    CloseLibrary(IconBase);

  /* Let's also return us to correct directory */
  if (oldlock != NULL)
    CurrentDir(oldlock);

#endif

}
