/* TimeUtilities.c
 *       *
 *     Emilio            2/2/2001
 *       Added GetDaysInMonth function. Added local array nDaysPerMonthLocal.
 *
 * Functions used to make calendar format conversions, particularly to convert
 * among Julian Day, Gregorian Calendar, Solar Day, and Day of the Week.
 * Leap Years and other subtleties are taken into account.
 * Code taken from TJ Saunders' code, originally adapted in large part from
 * "Numerical Recipes in C", 2nd ed,  pp. 11-12.
 ******************************************************************************/

#include "emu.h"


/* array used locally, so it doesn't conflict or modify the global array  *
 * nDaysPerMonth from unitconv.h                                          */
static int nDaysPerMonthLocal[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/* ------------------------------------------------------------------------------ */
void GregorianToJulianDay(int nMonth, int nDay, int nYear, long *plJulianDay)
{
  /* this algorithm is based on that found in "Numerical Recipes in C", 2nd ed, */
  /* pp. 11-12. */

  /* local (as in local to this function) working variables */
  int nLocalMonth;
  int nLocalYear;

  /* working variable */
  int nAdjust;

  /* capture the input values */
  nLocalMonth = nMonth;
  nLocalYear = nYear;

  if (nYear == 0) {

    /* display an error */
    Warning("GregorianToJulianDay()", "no year 0 AD");

    return;
  }

  if (nYear < 0)

    /* add one to an AD year for the "Scalinger Year" */
    nLocalYear++;

  if (nMonth > 2) {

    /* increment the month if is March or later */
    nLocalMonth = nMonth + 1;

  } else {

    /* decrement the year into the previous Julian cycle */
    nLocalYear--;

    /* place January and February into the previous Julian cycle */
    nLocalMonth = nMonth + 13;
  }

  *plJulianDay = (long) (floor(365.25 * nLocalYear) + floor(30.6001 * nLocalMonth) +
                nDay + 1720995);

  /* test whether to adjust the day for the Gregorian calendar or not */
  if ((nDay + 31L * (nMonth + 12L * nYear)) >= GREGORIAN_START) {

    /* a small adjustment factor */
    nAdjust = (int) (0.01 * nLocalYear);

    *plJulianDay += (2 - nAdjust + (int) (0.25 * nAdjust));
  }

  /* done */
  return;
}

/* ------------------------------------------------------------------------------ */
void GregorianToSolarDay(int nMonth, int nDay, int nYear, int *pnSolarDay)
{
  /* a loop variable */
  int nMonthIndex;

  /* working variables */
  int nDays = 0;

  /* first, adjust the number of days in February, if necessary */
  nDaysPerMonthLocal[1] = (IsLeapYear(nYear)) ? 29 : 28;

  /* loop through the number of months, summing up the number of days so far */
  /* use the input month minus one as the loop limit to allow for the number of days */
  /* for the next month */

  for (nMonthIndex = 0; nMonthIndex < (nMonth - 1); nMonthIndex++)
    nDays += nDaysPerMonthLocal[nMonthIndex];

  /* add the day of the month */
  nDays += nDay;

  /* and that's your Solar day, 1-based */
  *pnSolarDay = nDays;

  /* done */
  return;
}

/* ------------------------------------------------------------------------------ */
void JulianToGregorianDay(long lJulianDay, int *pnMonth, int *pnDay, int *pnYear) {

  /* this algorithm is taken from "Numerical Recipes in C", 2nd ed, pp 14-15. */

  /* working variables */
  long lFactorA, lFactorB, lFactorC, lFactorD, lFactorE;
  long lAdjust;

  /* test whether to adjust for the Gregorian calendar crossover */
  if (lJulianDay >= GREGORIAN_CROSSOVER) {

    /* calculate a small adjustment */
    lAdjust = (long) (((float) (lJulianDay - 1867216) - 0.25) / 36524.25);

    lFactorA = lJulianDay + 1 + lAdjust - ((long) (0.25 * lAdjust));

  } else

    /* no adjustment needed */
    lFactorA = lJulianDay;

  /* what follows is a lot of numerical hocus-pocus that I can't follow...I'm just */
  /* copying it right out of "Numerical Recipes" */

  lFactorB = lFactorA + 1524;
  lFactorC = (long) (6680.0 + ((float) (lFactorB - 2439870) - 122.1) / 365.25);
  lFactorD = (long) (365 * lFactorC + (0.25 * lFactorC));
  lFactorE = (long) ((lFactorB - lFactorD) / 30.6001);

  /* now, pull out the day number */
  *pnDay = lFactorB - lFactorD - (long) (30.6001 * lFactorE);

  /* ...and the month, adjusting it if necessary */
  *pnMonth = lFactorE - 1;
  if (*pnMonth > 12)
    (*pnMonth) -= 12;

  /* ...and similarly for the year */
  *pnYear = lFactorC - 4715;
  if (*pnMonth > 2)
    (*pnYear)--;

  if (*pnYear <= 0)
    (*pnYear)--;

  /* done */
  return;
}

/* ------------------------------------------------------------------------------ */
void JulianToSolarDay(long lJulianDay, int *pnSolarDay) {

  /* convert the input Julian day to a Gregorian day, then call GregorianToSolarDay() */

  /* variables for the Gregorian day values */
  int nMonth, nDay, nYear;

  /* convert to Gregorian dates */
  JulianToGregorianDay(lJulianDay, &nMonth, &nDay, &nYear);

  /* now get the Solar day */
  GregorianToSolarDay(nMonth, nDay, nYear, pnSolarDay);

  /* done */
  return;
}

/* ------------------------------------------------------------------------------ */
int GetDayOfWeekIndex(long lJulianDay) {

  /* returns the day of the week given the input Julian day.  The output ranges */
  /* from 0 to 7, with 0 being Monday and 6 being Sunday */

  return (int) (lJulianDay % 7L);
}

/* ------------------------------------------------------------------------------ */
void GetDayOfWeekName(long lJulianDay, char *pszDayOfWeek) {

  /* return a string with the name of the day of the week for the given Julian */
  /* Day */

  switch (GetDayOfWeekIndex(lJulianDay)) {

    case 0:     /* Monday */
      sprintf(pszDayOfWeek, "Monday");
      break;

    case 1:     /* Tuesday */
      sprintf(pszDayOfWeek, "Tuesday");
      break;

    case 2:     /* Wednesday */
      sprintf(pszDayOfWeek, "Wednesday");
      break;

    case 3:     /* Thursday */
      sprintf(pszDayOfWeek, "Thursday");
      break;

    case 4:     /* Friday */
      sprintf(pszDayOfWeek, "Friday");
      break;

    case 5:     /* Saturday */
      sprintf(pszDayOfWeek, "Saturday");
      break;

    case 6:     /* Sunday */
      sprintf(pszDayOfWeek, "Sunday");
      break;

    default:
      sprintf(pszDayOfWeek, "error -- unknown day of the week");

  }

  /* done */
  return;
}

/* ------------------------------------------------------------------------------ */
/* return the number of days of the specified months (base 1), accounting for     *
 * Leap years                                                                     */

short GetDaysInMonth(short month, int nYear)
{
  /* account for leap years */
  nDaysPerMonthLocal[1] = (IsLeapYear(nYear)) ? 29 : 28;

  return((short)nDaysPerMonthLocal[month-1]);
}

/* ------------------------------------------------------------------------------ */
BOOL IsLeapYear(int nYear) {

  /* the result */
  BOOL bIsLeapYear;

  if ((nYear % 4 == 0) &&
      (nYear % 100 != 0 || nYear % 400 == 0))
    bIsLeapYear = TRUE;
  else
    bIsLeapYear = FALSE;

  /* return the result */
  return bIsLeapYear;
}

/* ------------------------------------------------------------------------------ */
void SolarToGregorianDay(int nSolarDay, int nYear, int *pnMonth, int *pnDay) {

  /* convert the given Solar day (1-based) and year to a month and day */

  /* a loop variable */
  int nMonthIndex;

  /* working variables */
  int nDaysRemaining = nSolarDay;
  int nDay = 0;

  /* determine if it's a leap year */
  nDaysPerMonthLocal[1] = (IsLeapYear(nYear)) ? 29 : 28;

  /* loop through the months, subtracting the number of days per month from the */
  /* the given Solar day */
  for (nMonthIndex = 0; nMonthIndex < YEAR2MONTH; nMonthIndex++) {

    nDaysRemaining -= nDaysPerMonthLocal[nMonthIndex];

    /* stop subtracting */
    if (nDaysRemaining <= 0)
      break;
  }

  /* the month index pointer (plus a small adjustment) should now be the month */
  *pnMonth = nMonthIndex + 1;

  /* add the number of days for the month back into the remaining days variable */
  /* to get the day of the month */
  *pnDay = nDaysRemaining + nDaysPerMonthLocal[nMonthIndex];

  /* done */
  return;

}

/* ------------------------------------------------------------------------------ */
void SolarToJulianDay(int nSolarDay, int nYear, long *plJulianDay) {

  /* convert the given Solar day and year to a Gregorian day, then call */
  /* GregorianToJulianDay() */

  /* working variables */
  int nMonth, nDay;

  SolarToGregorianDay(nSolarDay, nYear, &nMonth, &nDay);
  GregorianToJulianDay(nMonth, nDay, nYear, plJulianDay);

  /* done */
  return;
}