/* Event handling routines for Reminder */

/* $Id: Events.c,v 1.13 1993/04/08 20:12:18 Matti_Rintala Exp $ */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>

#include <exec/types.h>
#include <exec/lists.h>

#include <exec/exec.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/reqtools_protos.h>
#endif

#include "Globals.h"
#include "CalcDate.h"
#include "Events.h"

static int checkdate(void);
static void makeentry(char *entry, short day, short month, short year,
		      short wday, const char *text);

static int AddEventlist(struct EventNode *newevent);

/* Eventlist holds the event database, initially empty */
static struct List Eventlist = {
  (struct Node *)&Eventlist.lh_Tail,
  NULL,
  (struct Node *)&Eventlist.lh_Head
  };

static time_t today;		/* Date of current day (initialized in LoadEvents) */

static void DeleteEventlist(void);

/* InitEvents initializes the events database */
struct List *InitEvents(void) {

  /* Only ensure that Eventlist is deleted on exit */
  atexit(DeleteEventlist);

  /* Return pointer to Eventlist */
  return &Eventlist;
}

static void DeleteEventlist(void) {

  struct EventNode *node;

  while ((node = (struct EventNode *)RemTail(&Eventlist)) != NULL)
    free(node);

  eventno = 0;			/* There are no events now */

}

/* NewEvent makes a new, clear event */
void NewEvent(void) {

  /* Ask for confirmation if event in gadgets has been edited */
  if (edited) {
    if (!rtEZRequestTags("You have edited\nthe current event.\nAbandon changes?",
			 "_Yes|Oh, _no!", NULL, NULL, RT_LockWindow, (Tag)TRUE,
			 RT_Underscore, (Tag)'_', RTEZ_DefaultResponse, (Tag)0,
			 TAG_END)) {
      /* If user does not want to abandon changes */
      return;
    }
  }

  /* Initialize all variables */
  day = month = year = wday = before = after = 0;
  autodelete = FALSE;
  swdaytxt = NULL;
  mode = AREXXNMODE;
  text[0] = arexxport[0] = arexxcom[0] = '\0';
  event = ~0;			/* No event selected */

  edited = FALSE;		/* Nothing edited now */
}

/* AddEvent adds a new event to list */
struct List *AddEvent(void) {

  struct EventNode *newevent;

  /* Check that the date is correct */
  if (!checkdate()) {
    /* If not, tell the user that */
    rtEZRequestTags("Cannot add event,\ninvalid date.", "OK", NULL, NULL,
		    RT_LockWindow, (Tag)TRUE, TAG_END);
    /* Return with unchanged list */
    return &Eventlist;
  }

  /* Make a new list node */
  if ((newevent = malloc(sizeof(struct EventNode))) == NULL) {
    rtEZRequestTags("Cannot get memory\nfor new event!", "OK", NULL, NULL,
		    RT_LockWindow, (Tag)TRUE, TAG_END);
    return &Eventlist;
  }

  /* Generate entry text */
  makeentry(newevent->entry, day, month, year, wday, text);
  newevent->node.ln_Name = newevent->entry;

  /* Set Acknowledgement date to 1-Jan-1990 */
  newevent->aday = 1; newevent->amonth = 1; newevent->ayear = 1990;

  /* Set other fields from globals */
  newevent->day = day; newevent->month = month; newevent->year = year;
  newevent->wday = wday;
  newevent->before = before; newevent->after = after;
  newevent->autodelete = autodelete;
  strncpy(newevent->text, text, TEXTLEN);
  strncpy(newevent->arexxcom, arexxcom, AREXXCOMLEN);
  strncpy(newevent->arexxport, arexxport, AREXXPORTLEN);
  newevent->mode = mode;

  /* Add new entry */
  event = AddEventlist(newevent);

  changed = 1;		/* Database has changed */
  eventno++;			/* One more event now exists */

  edited = FALSE;		/* Nothing has been edited now */

  /* Return pointer to list */
  return &Eventlist;
}

/* Getevent gets data from selected event */
int GetEvent(void) {

  struct EventNode *node;
  int i;

  /* Ask for confirmation if event in gadgets has been edited */
  if (edited) {
    if (!rtEZRequestTags("You have edited\nthe current event.\nAbandon changes?",
			 "_Yes|Oh, _no!", NULL, NULL, RT_LockWindow, (Tag)TRUE,
			 RT_Underscore, (Tag)'_', RTEZ_DefaultResponse, (Tag)0,
			 TAG_END)) {
      /* If user does not want to abandon changes */
      return FALSE;
    }
  }

  /* First find the appropriate node */
  node = (struct EventNode *)Eventlist.lh_Head;
  for (i = 0 ; i < event; i++)
    node = (struct EventNode *)node->node.ln_Succ;

  /* Then get the appropriate data */
  wday = node->wday; day = node->day; month = node->month; year = node->year;
  before = node->before; after = node->after;
  autodelete = node->autodelete;
  strncpy(text, node->text, TEXTLEN);
  strncpy(arexxcom, node->arexxcom, AREXXCOMLEN);
  strncpy(arexxport, node->arexxport, AREXXPORTLEN);
  mode = node->mode;

  edited = FALSE;		/* Nothing has been edited */

  return TRUE;
}

/* UpdateEvent updates an existing event */
struct List *UpdateEvent(void) {

  struct EventNode *node;
  int i;

  /* First find the appropriate node */
  node = (struct EventNode *)Eventlist.lh_Head;
  for (i = 0 ; i < event; i++)
    node = (struct EventNode *)node->node.ln_Succ;

  /* Remove the node from EventList */
  Remove((struct Node *)node);
  eventno--;

  /* Generate entry text */
  makeentry(node->entry, day, month, year, wday, text);
  node->node.ln_Name = node->entry;

  /* Set Acknowledgement date to 1-Jan-1990 */
  node->aday = 1; node->amonth = 1; node->ayear = 1990;

  /* Set other fields from globals */
  node->day = day; node->month = month; node->year = year;
  node->wday = wday;
  node->before = before; node->after = after;
  node->autodelete = autodelete;
  strncpy(node->text, text, TEXTLEN);
  strncpy(node->arexxcom, arexxcom, AREXXCOMLEN);
  strncpy(node->arexxport, arexxport, AREXXPORTLEN);
  node->mode = mode;

  /* Add event back to list */
  event = AddEventlist(node);
  eventno++;

  changed = 1;		/* Database has changed */

  edited = FALSE;		/* Nothing has been edited */

  return &Eventlist;
}

/* RemoveEvent removes an existing event */
struct List *RemoveEvent(void) {

  struct EventNode *node;
  int i;

  /* First find the appropriate node */
  node = (struct EventNode *)Eventlist.lh_Head;
  for (i = 0 ; i < event; i++)
    node = (struct EventNode *)node->node.ln_Succ;

  /* Remove the node */
  Remove((struct Node *)node);

  /* Free the memory */
  free(node);

  changed = 1;			/* Database has changed */
  eventno--;			/* Now there are one less events */

  return &Eventlist;
}

/* LoadEvents loads the event database from file. It returns TRUE if it fails. */
int LoadEvents(const char *filename) {

  FILE *infile;
  struct EventNode *node;

  /* Get todays date */
  gettoday(&today);

  /* Try to open the file */
  if ((infile = fopen(filename, "rb")) == NULL) 
    return TRUE;

  /* Skip the timestamp */
  fseek(infile, sizeof(time_t), SEEK_SET);

  /* Read events from file */
  while (TRUE) {
    /* Get memory for event */
    if ((node = malloc(sizeof(struct EventNode))) == NULL) {
      rtEZRequestTags("Insufficient memory!", "OK", NULL, NULL,
		      RT_LockWindow, (Tag)TRUE, TAG_END);
      break;
    }

    /* Read event part of it from file */
    while (TRUE) {
      fread(SAVEADDR(node), 1, SAVELEN(node), infile);
      /* If end-of-file occurred, stop looping */
      if (feof(infile))
	break;
      /* Stop looping also, if event is not deleted */
      if (node->mode != DELETEDMODE)
	break;
      else
	changed = 2;		/* Deleted entrys should be swept away */
    }
    /* If end-of-file occurred, stop looping */
    if (feof(infile))
      break;

    /* Check mode (for database files generated with Reminder V1.00) */
    if (node->mode != AREXXCMODE && node->mode != AREXXSMODE &&
	node->mode != AREXXNMODE) {
      /* Invalid mode, use No ARexx and empty Port and Com fields */
      node->mode = AREXXNMODE;
      node->arexxcom[0] = node->arexxport[0] = '\0';
    }

    /* Calculate entry text */
    makeentry(node->entry, node->day, node->month, node->year, node->wday,
	      node->text);

    /* Set name of event and add it to Eventlist */
    node->node.ln_Name = node->entry;
    AddEventlist(node);
    eventno++;			/* One more event */
  }

  /* Free the extra node left (freeing NULL is allowed) */
  free(node);

  fclose(infile);

  return FALSE;
}

/* SaveEvents saves event database to file. It returns TRUE if it fails. */
int SaveEvents(const char *filename) {

  FILE *outfile;
  struct EventNode *node;
  BOOL success = TRUE;
  struct tm tm_stamp = {0, 0, 0, 1, 0, 90, 0, 0, 0};
  time_t stamp;
  
  /* First try to open the file */
  if ((outfile = fopen(filename, "wb")) == NULL)
    return 1;

  /* Write timestamp for 1.1.1990 */
  stamp = mktime(&tm_stamp);
  if (fwrite(&stamp, 1, sizeof(time_t), outfile) != sizeof(time_t)) {
    success = FALSE;
  }
  else {
    node = (struct EventNode *)Eventlist.lh_Head;
    for (; node->node.ln_Succ != NULL;
	 node = (struct EventNode *)node->node.ln_Succ) {
      /* Write the node information to file */
      if (fwrite(SAVEADDR(node), 1, SAVELEN(node), outfile) != SAVELEN(node)) {
	/* If failed, abort */
	success = FALSE;
	break;
      }
    }
  }

  /* Close the file and exit */
  fclose(outfile);

  return !success;
}

  

static short monthlist[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/* checkdate checks that the date is valid */
static int checkdate(void) {

  short testyear, testmonth;
  /* Check month */
  if (month > 12)
    return 0;

  /* If day is ANY, date is ok */
  if (day == 0)
    return 1;

  /* Get appropiate values for test variables */
  if (year == 0) testyear = 1996; /* Leap year */
  else testyear = year;

  if (month == 0) testmonth = 1; /* Month with 31 days */
  else testmonth = month;
    
  /* Check day */
  if (day > (monthlist[testmonth-1] + ((testmonth == 2 && testyear%4 == 0) ?
				     ((testyear%400 == 0) ? 0 : 1) :
				     0)))
    return 0;

  return 1;
}

/* makeentry makes an appropriate entry line for Eventlist gadget */
static void makeentry(char *entrystart, short day, short month, short year,
		      short wday, const char *text) {

  char *entry = entrystart;

  /* Start with day of the week */
  /* If we have a non-repeating date with ANY weekday, show the weekday + colon */
  if (day != 0 && month != 0 && year != 0 && wday == 0) {
    strncpy(entry, wdayabbrv+4*weekday(day, month, year), 3);
    entry += 3;
    *entry++ = ':';
  }
  else {
    strncpy(entry, wdayabbrv+4*wday, 3);
    entry += 3;			/* Skip the day */
    *entry++ = ' ';
  }

  /* If day is ANY, use **, otherwise the day number */
  if (day == 0) {
    *entry++ = '*';
    *entry++ = '*';
  }
  else {
    sprintf(entry, "%02d", day);
    entry += 2;			/* Skip over the number */
  }

  *entry++ = '-';
  /* Then month name */
  strncpy(entry, monthabbrv+4*month, 3);
  entry += 3;			/* Skip over month */
  *entry++ = '-';

  /* Then year */
  if (year == 0) {
    *entry++ = '*';
    *entry++ = '*';
    *entry++ = '*';
    *entry++ = '*';
  }
  else {
    sprintf(entry, "%4d", year);
    entry += 4;			/* Skip over year */
  }

  *entry++ = ' ';

  /* Then copy rest with text */
  strncpy(entry, text, ENTRYLEN-(entry-entrystart));
}

/* AddEventlist finds the correct place for event and adds it to list.
   It returns the place of the event in the list */
static int AddEventlist(struct EventNode *newevent) {

  int i;
  struct EventNode *node;

  /* Calculate next occurrence of event */
  makedate(&newevent->next_date, &today, newevent->day, newevent->month,
	   newevent->year, newevent->wday);

  /* Find place for event, starting form the head of list */
  node = (struct EventNode *)Eventlist.lh_Head;
  for (i = 0; i < eventno; i++, node = (struct EventNode *)node->node.ln_Succ) {
    /* If event is earlier than this, add new event before it */
    if (newevent->next_date < node->next_date) {
      Insert(&Eventlist, (struct Node *)newevent, node->node.ln_Pred);
      return i;			/* Return place of event */
    }
  }

  /* If we got this far, event must be added to the end of the list */
  AddTail(&Eventlist, (struct Node *)newevent);
  return eventno;		/* Event is the last one */
}
