/* Date calculation routines for Reminder and ReminderCheck */

/* $Id: CalcDate.c,v 1.1 1993/04/04 13:37:45 Matti_Rintala Exp $ */

#include "Constants.h"
#include "CalcDate.h"

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

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

/* makedate calculates next date that satisfies given conditions */
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 */
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 */
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);
}

/* gettoday gets today's date (without time of day) */
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 */
}

