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

/* $Id: CheckMain.c,v 1.17 1993/04/19 15:20:43 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 <intuition/intuition.h>

#include <libraries/reqtools.h>

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

#ifdef _DCC
#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/intuition_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"
#include "CalcDate.h"
#include "ARexx.h"

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

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

static int makealarm(time_t *date, const char *text);
static int groupalarm(time_t *date, struct EventNode *node, long int fpos);
static void CloseLibraries(void);

static void putack(struct EventNode *node, time_t acked);
static char *divtext(char *str, const char *text);
static int makerequest(char *buffer);

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

struct EventNode eventnode, groupnode, *node = NULL;

BOOL noreqtools = FALSE;	/* TRUE if NOREQTOOLS tooltype is present */
BOOL noarexx = FALSE;		/* TRUE if NOAREXX tooltype is present */
short maxgroup = DEFAULTMAXGROUP; /* Maximum number of grouped events in one req */
short groupno = 0;		/* Number of events currently grouped */

long int *groupfpos = NULL;	/* Table for record positions in grouped req */
char *grouptext = NULL;		/* Text of group requester */
char *groupnext = NULL;	/* Place to put next grouped event text */

short maxwidth = DEFAULTMAXWIDTH; /* Maximum width of requester line */

#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;
  long fpos;
  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);

  /* Set node to point to eventnode */
  node = &eventnode;

  /* Parse arguments */
  ttypes = ArgArrayInit(argc, argv);
  /* Try to find database filename */
  filename = ArgString(ttypes, FILETYPE, DEFAULTFILE);
  /* And checking interval */
  interval = ArgInt(ttypes, INTRVLTYPE, DEFAULTINTRVL);
  /* And whether we use ReqTools.library or not */
  noreqtools = (ArgString(ttypes, NOREQTOOLSTYPE, NULL) != NULL);
  /* And whether we use ARexx or not */
  noarexx = (ArgString(ttypes, NOAREXXTYPE, NULL) != NULL);
  /* And maximum number of events in one grouped requester */
  maxgroup = ArgInt(ttypes, MAXGROUPTYPE, DEFAULTMAXGROUP);
  /* And maximum width of requester line */
  maxwidth = ArgInt(ttypes, MAXWIDTHTYPE, DEFAULTMAXWIDTH);
  if (maxwidth < 19)
    maxwidth = 19;		/* Minimum is  */

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

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

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

  /* Initialize ARexx, if permitted */
  if (!noarexx)
    initarexx();

  /* Allocate space for grouped requester data, if needed */
  if (maxgroup != 0) {
    if ((grouptext = malloc((maxgroup*(TEXTLEN+1)+19)*sizeof(char))) != NULL) {
      if ((groupfpos = (long int *)calloc(maxgroup, sizeof(long int))) == NULL) {
	/* If memory allocation failed, free also grouptext */
	free(grouptext);
	grouptext = NULL;
      }
    }
  }

  /* 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(SAVEADDR(node), 1, SAVELEN(node), infile);
	  /* Stop looping if error or wrong number of bytes read */
	  if (ferror(infile) || size < SAVELEN(node))
	    break;
	  /* Also if event is not deleted */
	  if (node->mode != DELETEDMODE)
	    break;
	}
	/* Stop looping if end-of-file */
	if (ferror(infile) || size < SAVELEN(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 grouping is enabled, alarm is today and event has grouping flag
	   set, use grouped requester, otherwise normal */
	if (grouptext != NULL && alarm == today && (node->mode & GROUPEDMASK)) {
	  if (groupalarm(&today, node, fpos) == QUIT)
	    break;
	}
	else {
	  /* 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;
	  }
	  /* Use ARexx, if requested */
	  makearexx(node, &alarm);
	  /* Make the alarm requester */
	  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)
	      node->mode = DELETEDMODE;
	    else
	      putack(node, alarm);
	  
	    fseek(infile, fpos, SEEK_SET);
	    size = fwrite(SAVEADDR(node), 1, SAVELEN(node), infile);
	    /* Stop if error */
	    if (ferror(infile) || size < SAVELEN(node))
	      break;
	    fflush(infile);
	  }
	}
      }
      /* If grouped alarms are enabled, flush them */
      if (grouptext != NULL) {
	groupalarm(&today, NULL, 0);
      }
    }
  }

  fclose(infile);
  
  exit(0);
}
  

/* 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 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;
  
  /* Calculate date string */
  d = localtime(date);
  str += strftime(str, maxwidth, "%a %d-%b-%Y :\n", d); /* Print date */

  divtext(str, text);		/* Divide text into lines */

  return makerequest(buffer);	/* And present the requester to user */
}

/* groupalarm handles grouped alarms */
static int groupalarm(time_t *date, struct EventNode *node, long int fpos) {

  struct tm *d;
  int i, response;
  long int currfpos, size;
  struct EventNode *gnode = &groupnode;

  /* If grouping not enabled, return */
  if (grouptext == NULL)
    return ACK;			/* Dummy return value */

  /* If node is not NULL (which means flush), add event */
  if (node != NULL) {
    /* If we have now the first event, calculate the date text */
    if (groupno == 0) {
      /* Calculate date string */
      d = localtime(date);
      groupnext = grouptext +
	strftime(grouptext, maxwidth, "%a %d-%b-%Y :", d); /* Print date */
      /* Remember the node for ARexx information */
      groupnode = *node;
    }
    else {
      /* If groupnode does not have ARexx, but new does, update groupnode */
      if ((groupnode.mode & ~GROUPEDMASK) == AREXXNMODE &&
	  (node->mode & ~GROUPEDMASK) != AREXXNMODE)
	groupnode = *node;
    }
    /* Add two newlines and the event text */
    *groupnext++ = '\n';
    *groupnext++ = '\n';
    groupnext = divtext(groupnext, node->text);
    /* Remember the position of the event in file */
    groupfpos[groupno] = fpos;
    /* There is one more event in group */
    groupno++;
  }

  /* If maximum number of events is reached or node is NULL, make the requester */
  if (groupno == maxgroup || (node == NULL && groupno != 0)) {
    /* Do the ARexx */
    makearexx(&groupnode, date);

    /* Present the requester to user and get the response */
    response = makerequest(grouptext);

    /* If response was 'ACK', update the acknowledgement dates of events */
    if (response == ACK) {
      currfpos = ftell(infile);	/* Remember current file position */

      for (i = 0; i < groupno; i++) {
	clearerr(infile);
	fseek(infile, groupfpos[i], SEEK_SET); /* Go to correct position */
	size = fread(SAVEADDR(gnode), 1, SAVELEN(gnode), infile);
	/* Stop, if error or wrong number of bytes read */
	if (ferror(infile) || size < SAVELEN(gnode))
	  break;

	/* Delete or acknowledge event depending on mode */
	if (gnode->autodelete)
	  gnode->mode = DELETEDMODE;
	else
	  putack(gnode, *date);

	/* Seek back, write the new node information and flush buffers */
	fseek(infile, groupfpos[i], SEEK_SET);
	size = fwrite(SAVEADDR(gnode), 1, SAVELEN(gnode), infile);
	/* Stop if errors */
	if (ferror(infile) || size < SAVELEN(gnode))
	  break;
	fflush(infile);
      }
      /* Return to old file position */
      fseek(infile, currfpos, SEEK_SET);
    }
    groupno = 0;		/* No events in group any more */
    groupnext = grouptext;	/* Same goes for group text */
  }
  else {
    response = ACK;		/* If no requester was put up */
  }

  return response;
}

/* divtext divides text into lines and copies it */
static char *divtext(char *str, const char *text) {

  int i;
  
  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 */
    str += strlen(text);	/* Update str */
  }

  return str;			/* Return pointer to end of added text */
}

/* makerequest puts up a given requester and returns the button pressed */
static int makerequest(char *buffer) {
  
  /* Use ReqTools or EasyRequest depending on toolstype/cmdarg */
  if (noreqtools) {
    /* Structure for Intuition EasyRequest */
    struct EasyStruct es = {sizeof(struct EasyStruct), 0, "Reminder",
			      NULL, "Go away!|Ackn|Quit!"};
    es.es_TextFormat = buffer;
    
    return (int)EasyRequest(NULL, &es, NULL, NULL);
  }
  else {
    /* 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);

  /* Release ARexx */
  releasearexx();

  /* Free grouped requester data */
  free(grouptext);
  free(groupfpos);

#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

}
