import tauzaman.field.*;
import tauzaman.timestamp.*;
import tauzaman.calendar.*;
import tauzaman.calendar.Calendar;
import tauzaman.calendricsystem.*;
import tauzaman.TauZamanException;



/*
 * This class implements all the methods necessary for a simple U of A academic
 * calendar that will work with tauZaman.  Here are the following assumptions
 * I made while writing this calendar:
 *   # This academic calendar starts at the 2000-2001 academic year for the
 *     U of A and ends at the 2003-2004 academic calendar for the U of A.
 *   # This calendar lumps all three summer semesters together (i.e. there are
 *     four terms a year instead of six).
 *   # There is no support for holidays/non-class days in this calendar.
 *   # A cast from week -> term for a week that crosses 2 terms will return the
 *     first term.
 *   # Each granule starts at 0 (not 1), therefore, I am doing 0-based counting
 *     of the granules.
 *   # It does take care of the last week of the Winter term and the first week
 *     of the Spring term crossing each other.
 *   # It does take care of the fact that there are a couple of weeks each year
 *     which only have 2/3 days (e.g. the last week of the Summer term).
 *
 * Last Updated:  2003/3/23 by Jessica Miller
 */

public class UofACalendar
{
  /******************************************************************************/
  /* Constants                                                                  */
  /******************************************************************************/
  public static final int NUM_HOURS_IN_DAY        = 14;
  public static final int NUM_DAYS_IN_WEEK        = 5;
  public static final int NUM_WEEKS_IN_CALENDAR   = 204;
  public static final int NUM_TERMS_IN_YEAR       = 4;
  public static final int NUM_YEARS_IN_CALENDAR   = 4; 

  private static final int ACADEMIC_YEAR_2000_2001 = 0;
  private static final int ACADEMIC_YEAR_2001_2002 = 1;
  private static final int ACADEMIC_YEAR_2002_2003 = 2;
  private static final int ACADEMIC_YEAR_2003_2004 = 3;

  private static final int FALL                    = 0;
  private static final int WINTER                  = 1;
  private static final int SPRING                  = 2;
  private static final int SUMMER                  = 3;

  private static final int INVALID_ARG             = -1;

  public static final double[][] EXACT_WEEKS_IN_TERM = {
    {17, 3.4, 17.6, 12.6},
    {17, 3.4, 17.6, 12.8},
    {17, 3.8, 17.6, 12.6},
    {17, 3.4, 17.6, 12.6}};

  private static final long[][] FIRST_WEEK_OF_TERM = {
    {0, 17, 20, 38},
    {51, 68, 71, 89},
    {102, 119, 122, 140},
    {153, 170, 173, 191}};

  /* 0-based counting */
  public static final long[][] LAST_WEEK_OF_TERM = {
    {16,  20,  37,  50},
    {67,  71,  88,  101},
    {118, 122, 139, 152},
    {169, 173, 190, 203}};
    
  // mapping between week number that does not have 5 days and the number of
  //   days it is missing (e.g. week 50 only has 3 days)
  private static final long[][] NOT_FULL_WEEKS = {
    {50,  101,  119,  152, 203},
    {2 ,  1  ,  3  ,  2  , 2  }}; 
  
  // mapping between week number that crosses more than one term and first day
  //   of second term in week 
  private static final long[][] SHARED_WEEKS = {
    {20,  71,  122,  173},
    {2 ,  2 ,  2  ,  2  }};     

  // for use with arrays, since 1-based counting is used in this calendar
  private static final int TERM_OFFSET = 1;

  // Constants for indices of array that is given back in the split
  public static final int YEAR_INDEX        = 0;
  public static final int TERM_INDEX        = 1;
  public static final int WEEK_INDEX        = 2;
  public static final int DAY_INDEX         = 3;
  public static final int HOUR_INDEX        = 4;
  public static final int SPLIT_ARRAY_SIZE  = 5;
  
  
  /***************************************************************************/
  /* Helper methods                                                          */
  /***************************************************************************/


  /*
   * Splits academic years into academic years by simply returning the given
   *   argument.
   *
   *   @param years  the academic year that we want to split
   *   @return       array whose first value is a year
   */
  public static long[] splitAcademicYears(long years)  
  {
    long[] result      = new long[SPLIT_ARRAY_SIZE];
    result[YEAR_INDEX] = years;
    return result;
  }


  /*
   * Splits given days into days, weeks, terms, and years.
   *
   *   @param days  the days to split into days, weeks, terms, and years
   *   @return      array of split values (years, terms, weeks, days)
   */
  public static long[] splitDay(long days)
  {
    long[] result;
    
    // get an estimate about what week this might be
    long weekEst       = days / NUM_DAYS_IN_WEEK;
    long dayRemEst     = days % NUM_DAYS_IN_WEEK;
    
    // now adjust the estimate to the correct week, given remaining days and
    //   number of days accounted for that are not really in the calendar
    long errDays = 0;
    int ii = 0;
    
    while (NOT_FULL_WEEKS[0][ii] < weekEst  &&  ii < NOT_FULL_WEEKS[0].length)
      errDays += NOT_FULL_WEEKS[1][ii++];  
      
    long adjDays  = errDays + dayRemEst;
    long adjWeeks = adjDays / NUM_DAYS_IN_WEEK;
    
    long week = weekEst + adjWeeks;
    long day  = adjDays % NUM_DAYS_IN_WEEK;    
    
    // need to adjust week by one because need week that remaining day is in 
    //   (i.e. not the week before the week that remainding day is in)
    if (week != 0) 
      week = week + 1;    
    
    // if this week is a week that has less than the normal amount of days, 
    //   need to be sure that the number of remainding days is within the range
    //   of this week and if not, need to add a week and adjust the days
    for (ii = 0; ii < NOT_FULL_WEEKS[0].length; ii++)
    {
      if (NOT_FULL_WEEKS[0][ii] == week  
          &&  day >= (NUM_DAYS_IN_WEEK - NOT_FULL_WEEKS[1][ii]))
      {
        week++;
        day = day - (NUM_DAYS_IN_WEEK - NOT_FULL_WEEKS[1][ii]);
      }
    }
      
    result = splitWeek(week);   
    
    // if this week is a shared week and the number of days crosses to the 
    //   second term in the week, adjust the results to reflect this
    for (ii = 0; ii < SHARED_WEEKS[0].length; ii++)
    {
      if ((SHARED_WEEKS[0][ii] == week)
          &&  (day >= (SHARED_WEEKS[1][ii])))
      {    
        result[TERM_INDEX]++;
        result[WEEK_INDEX] = 0;
        day = day - SHARED_WEEKS[1][ii];
      }
    }    
    
    result[DAY_INDEX] = day;
    
    return result;    
  }
  

  /*
   * Splits given hours into hours, days, weeks, terms, and years.
   *
   *   @param hours  the hours to split into hours, days, weeks, terms, and 
   *                 years
   *   @return       array of split values (years, terms, weeks, days, hours)
   */  
  public static long[] splitHour(long hours)
  {
    long[] result;
    
    long day = hours / NUM_HOURS_IN_DAY;
    
    if (day != 0) 
      day = day + 1;      
    
    result = splitDay(day);
    result[HOUR_INDEX] = hours % NUM_HOURS_IN_DAY;
    
    return result;      
  }
  
  
  /*
   * Splits given terms into terms and years.
   *
   *   @param terms  the terms to split into terms and years
   *   @return       array of split values (years, terms)
   */  
  public static long[] splitTerm(long terms)
  {
    long[] result      = new long[SPLIT_ARRAY_SIZE];
    
    result[YEAR_INDEX] = terms / NUM_TERMS_IN_YEAR;
    result[TERM_INDEX] = terms % NUM_TERMS_IN_YEAR;
    return result;   
  }
  

  /*
   * Splits given weeks into weeks, terms and years.
   * <p>
   * Note: Since this method can only return a single value, for weeks that 
   *       cross more than one term, the result given is for that of the first
   *       term in the week.
   * </p>
   *   @param weeks  the weeks to split into weeks, terms and years
   *   @return       array of split values (years, terms, weeks)
   */    
  public static long[] splitWeek(long weeks)
  {
    long[] result      = new long[SPLIT_ARRAY_SIZE];
    int year = 0, term = 0;
    
    // while we have not exhausted all the years in the calendar and the given
    //   weeks is greater than the weeks passed in during the current year and
    //   term, increment the term if it is less than the number of terms in an
    //   academic year (or set it to 0 otherwise) and if the term gets set to 
    //   the first term, increment to the next academic year
    while (year < NUM_YEARS_IN_CALENDAR  &&  weeks > LAST_WEEK_OF_TERM[year][term])
    {
      if (term + 1 < NUM_TERMS_IN_YEAR)
        term++;
      else
        term = 0;
        
      if (term == 0)
        year++;
    }
    
    result[YEAR_INDEX] = year;
    result[TERM_INDEX] = term;
    
    // figure out the number of weeks to subtract from given weeks to get the 
    //   remainder of weeks
    long weeksToSubtract;
    
    if (year == 0 && term == 0) 
      weeksToSubtract = 0;
    else if (term == 0) 
      weeksToSubtract = LAST_WEEK_OF_TERM[year - 1][NUM_TERMS_IN_YEAR - 1] + 1;
    else if (term == 2)
      weeksToSubtract = LAST_WEEK_OF_TERM[year][term - 1];
    else
      weeksToSubtract = LAST_WEEK_OF_TERM[year][term - 1] + 1;    
      
     
    result[WEEK_INDEX] = weeks - weeksToSubtract;
    return result;      
  }
  
  
  /*
   * Return the total number of days that have occurred up to and including 
   *   this academic year, term, week and day (0-based counting).
   *
   *   @param academicYear  the year to turn into days
   *   @param term          the term to turn into days
   *   @param week          the week to turn into days   
   *   @param day           the day to turn into days     
   *   @return              total days in the four granularities combined, 
   *                        -1 if parameters passed are out of bounds
   */
  public static long sumDays(long academicYear, long term, long week, long day)
  {
    long weeks, errDays = 0;
    int ii = 0;

    if (day < 0 || day > NUM_DAYS_IN_WEEK - 1)
      return -1;

    weeks = sumWeeks(academicYear, term, week);
    
    if (weeks < 0) // error has occurred in summing weeks
      return -1;
      
    weeks = (weeks == 0) ? weeks : weeks - 1;
    
    while (NOT_FULL_WEEKS[0][ii] < weeks  &&  ii < NOT_FULL_WEEKS[0].length)
      errDays += NOT_FULL_WEEKS[1][ii++];

    return weeks * NUM_DAYS_IN_WEEK + day - errDays; 
  }


  /*
   * Return the total number of hours that have occurred up to and including 
   *   this academic year, term, week, day and hour (0-based counting).
   *
   *   @param academicYear  the year to turn into hours
   *   @param term          the term to turn into hours
   *   @param week          the week to turn into hours   
   *   @param day           the day to turn into hours     
   *   @param hour          the hour to turn into hours     
   *   @return              total hours in the four granularities combined, 
   *                        -1 if parameters passed are out of bounds
   */
  public static long sumHours(long academicYear, long term, long week, long day, long hour)
  {
    // check to be sure that given date is within the range of calendar
    if (hour < 0 || hour > NUM_HOURS_IN_DAY - 1)
      return -1;

    long days = sumDays(academicYear, term, week, day);

    return (days < 0) ? -1 : 
             (days == 0) ? days :
               (days - 1) * NUM_HOURS_IN_DAY + hour;
  }


  /*
   * Return the total number of terms that have occurred up to and including 
   *   this academic year and term (0-based counting).
   *
   *   @param academicYear  the year to turn into terms
   *   @param term          the term to turn into terms
   *   @return              total terms in the two granularities combined, 
   *                        -1 if parameters passed are out of bounds
   */
  public static long sumTerms(long academicYear, long term)
  {
    if (academicYear < 0 || academicYear > 3)
      return -1;
      
    if (term < 0 || term > NUM_TERMS_IN_YEAR - 1)
      return -1;      

    return NUM_TERMS_IN_YEAR * academicYear + term;
  }


  /*
   * Return the total number of weeks that have occurred up to and including 
   *   this academic year, term and week (0-based counting).
   *
   *   @param academicYear  the year to turn into weeks
   *   @param term          the term to turn into weeks
   *   @param week          the week to turn into weeks   
   *   @return              total weeks in the three granularities combined, 
   *                        -1 if parameters passed are out of bounds
   */
  public static long sumWeeks(long academicYear, long term, long week)
  {
    long weeks;

    if (academicYear < 0 || academicYear > 3)
      return -1;

    if (term < 0 || term > NUM_TERMS_IN_YEAR - 1)
      return -1;

    if (week < 0 || week > ((int)Math.ceil(EXACT_WEEKS_IN_TERM[new Long(academicYear).intValue()][new Long(term).intValue()]) - 1))
      return -1; 

    weeks = (academicYear == 0 && term == 0) ? 0
             : FIRST_WEEK_OF_TERM[new Long(academicYear).intValue()][new Long(term).intValue()];

    return weeks + week;
  }


  /***************************************************************************/
  /* Methods that will be used for the irregular mappings in this calendar   */
  /***************************************************************************/

  public static long castWeekToTerm(Long weekL){
      return castWeekToTerm(weekL.longValue());
  }

  /*
   * Covert the given amount of weeks to terms using the cast operation.
   * <p>
   * Algorithm:
   *   for each year that is in the calendar
   *     for each term in the year
   *       if the last week that has passed by this term in the calendar
   *         is greater than or equal to given week, return this as the term
   *       else
   *         increment term and keep on looking for a last week of a term
   *         that is greater than or equal to the week given
   *
   *   @param second number of week granules to cast into term granularity
   *   @return       number of term granules
   */
  public static long castWeekToTerm(long week)
  {
    int terms = 0;

    for (int year = 0; year < NUM_YEARS_IN_CALENDAR; year++)
      for (int term = 0; term < NUM_TERMS_IN_YEAR; term++)
      {
        if (week <= LAST_WEEK_OF_TERM[year][term])
          return terms;
        else
          terms++;
      }

    return INVALID_ARG;
  }

  public static long castTermToWeek(Long termL){
      return castTermToWeek(termL.longValue());
  }
  /*
   * Covert the given amount of terms to weeks using the cast operation.
   * <p>
   * Formula:
   *   numWeeks = the number of weeks completed by the year of the term and
   *              the term itself
   *
   *   @param term number of term granules to cast into week granularity
   *   @return     number of week granules
   */
  public static long castTermToWeek(long term)
  {


    int years = ((int)term) / NUM_TERMS_IN_YEAR;
    int terms = ((int)term) % NUM_TERMS_IN_YEAR;

    if (years >= NUM_YEARS_IN_CALENDAR)
      return INVALID_ARG;

    return FIRST_WEEK_OF_TERM[years][terms];
  }

  /**
  * Fills each <code>Field</code> of <code>Fields</code>, which can only be produced
  * by either InstantOutputFormat or IntervalOutputFormat properties.
  *
  * @param granule <code>Granule</code>, which contains timestamp information
  * @param fields <code>Fields</code>, which represents unparsed temporal constant
  *
  * @return a boolean value true if conversion is successfull, false otherwise
  *
  * @throws CalendarServiceException if any abnormal condition occurs when converting
  * timestamp to unparsed temporal constant. Unsupported <code>Granularities</code> or
  * <code>Properties</code>, which <code>Fields</code> is produced, may cause
  * this exception.
  */
  static public Boolean granuleToFields(Granule granule, Fields fields) throws CalendarServiceException{
      
      long splitResults[];

      String granularityName = granule.getGranularity().getLocalName();

      /* according to granularity of timestamp flattened it to upper granularities */
      if(granularityName.equals("year"))
          splitResults = splitAcademicYears(granule.getGranule().getValue());
      else if(granularityName.equals("term"))
          splitResults = splitTerm(granule.getGranule().getValue());
      else if(granularityName.equals("week"))   
          splitResults = splitWeek(granule.getGranule().getValue());
      else if(granularityName.equals("day"))
          splitResults = splitDay(granule.getGranule().getValue());
      else if(granularityName.equals("hour")){
          splitResults = splitHour(granule.getGranule().getValue());
          System.out.println("splitted granule: " + granule.image() +
                             "/" + splitResults[HOUR_INDEX] + "/" + splitResults[DAY_INDEX] +
                             "/" + splitResults[WEEK_INDEX] +
                             "/" + splitResults[TERM_INDEX] + "/" + splitResults[YEAR_INDEX]);
      }
      else
          throw new CalendarServiceException("Exception when converting granule to fields: " +
                                       " undefined granularity: " + granularityName + " in UofA Calendar.");

      /* all the fields corresponding to all the granularities and additional
         field names of UofA calendar */
      Field yearField = fields.getFieldByName("year"),
            termField = fields.getFieldByName("term"),
            weekField = fields.getFieldByName("week"),
            dayField = fields.getFieldByName("day"),
            hourField = fields.getFieldByName("hour"),
            termOfYearField = fields.getFieldByName("termOfYear"),
            weekOfTermField = fields.getFieldByName("weekOfTerm"),
            dayOfWeekField = fields.getFieldByName("dayOfWeek"),
            hourOfDayField = fields.getFieldByName("hourOfDay");

      /* multiplexing of output formats, compatibility checks between fields
         are not handled yet  */
      if(fields.getName().equals("InstantOutputFormat")){
          if(yearField != null) yearField.setValue(splitResults[YEAR_INDEX]);
          if(termField != null) termField.setValue(splitResults[TERM_INDEX]);
          if(weekField != null) weekField.setValue(splitResults[WEEK_INDEX]);
          if(dayField != null) dayField.setValue(splitResults[DAY_INDEX]);
          if(hourField != null) hourField.setValue(splitResults[HOUR_INDEX]);
          if(termOfYearField != null) termOfYearField.setValue(splitResults[TERM_INDEX]);
          if(weekOfTermField != null) weekOfTermField.setValue(splitResults[WEEK_INDEX]);
          if(dayOfWeekField != null) dayOfWeekField.setValue(splitResults[DAY_INDEX]);
          if(hourOfDayField != null) hourOfDayField.setValue(splitResults[HOUR_INDEX]);
      }
      else if(fields.getName().equals("IntervalOutputFormat")){
          if(yearField != null) yearField.setValue(splitResults[YEAR_INDEX]);
          if(termField != null) termField.setValue(splitResults[TERM_INDEX]);
          if(weekField != null) weekField.setValue(splitResults[WEEK_INDEX]);
          if(dayField != null) dayField.setValue(splitResults[DAY_INDEX]);
          if(hourField != null) hourField.setValue(splitResults[HOUR_INDEX]);
      }
      else
          throw new CalendarServiceException("Exception when converting Granule to fill Fields: undefined temporal data type " +
                                                                                                            fields.getName());

      return new Boolean(true);
  }


  /**
  * Converts unparsed temporal constant, <code>Fields</code>, into timestamp, <code>Granule</code>
  * <code>Fields</code>, which can only be produced by either InstantOutputFormat or
  * IntervalOutputFormat properties.
  *
  * @param fields <code>Fields</code>, which represents unparsed temporal constant
  *
  * @return a <code>Granule</code>, which is the corresponding timestamp  
  *
  * @throws CalendarServiceException if any abnormal condition occurs when converting
  * unparsed temporal constant to timestamp. Unsupported <code>Granularities</code> or
  * <code>Properties</code>, which <code>Fields</code> is produced, may cause
  * this exception.
  */
  static public Granule fieldsToGranule(Fields fields) throws CalendarServiceException{

      /* all the fields corresponding to all the granularities and additional
         field names of UofA calendar */
      Field yearField = fields.getFieldByName("year"),
            termField = fields.getFieldByName("term"),
            weekField = fields.getFieldByName("week"),
            dayField = fields.getFieldByName("day"),
            hourField = fields.getFieldByName("hour"),
            termOfYearField = fields.getFieldByName("termOfYear"),
            weekOfTermField = fields.getFieldByName("weekOfTerm"),
            dayOfWeekField = fields.getFieldByName("dayOfWeek"),
            hourOfDayField = fields.getFieldByName("hourOfDay");

       /* declare variables for values of granularities and additional field names */
       // should sumDays, sum... methods take long or int ???
       long hourOfDay = 0, dayOfWeek = 0, weekOfTerm = 0, termOfYear = 0,
           year = 0, term = 0, week = 0, day = 0, hour = 0;

       /* get each granularity or additional field values from fields */
       if(yearField != null) year = yearField.getValue();
       if(termField != null) term = termField.getValue();
       if(weekField != null) week = weekField.getValue();
       if(dayField != null) day = dayField.getValue();
       if(hourField != null) hour = hourField.getValue();
       if(termOfYearField != null) termOfYear = termOfYearField.getValue();
       if(weekOfTermField != null) weekOfTerm = weekOfTermField.getValue();
       if(dayOfWeekField != null) dayOfWeek = dayOfWeekField.getValue();
       if(hourOfDayField != null) hourOfDay = hourOfDayField.getValue();

      /* multiplexing of input formats, compatibility checks between fields
         are not handled yet  */
      if(fields.getName().equals("InstantInputFormat")){

          long sum = 0;
          
          if(hourOfDayField != null && termOfYearField != null && yearField != null
             && weekOfTermField != null && dayOfWeekField != null){
              sum = sumHours(year, termOfYear, weekOfTerm, dayOfWeek, hourOfDay);
              //System.out.println(hourOfDay + "/" + dayOfWeek + "/" + weekOfTerm
              //                   + "/" + termOfYear + "/" + year + "->" + sum);
              return new Granule(new Granularity("hour"), sum);
          }
          if( termOfYearField != null && yearField != null
             && weekOfTermField != null && dayOfWeekField != null){
              sum = sumDays(year, termOfYear, weekOfTerm, dayOfWeek);
              return new Granule(new Granularity("day"), sum);
          }
          if( termOfYearField != null && yearField != null && weekOfTermField != null){
              sum = sumWeeks(year, termOfYear, weekOfTerm);
              return new Granule(new Granularity("week"), sum);
          }
          if( termOfYearField != null && yearField != null ){
              sum = sumTerms(year, termOfYear);
              return new Granule(new Granularity("term"), sum);
          }
          if(yearField != null){
              sum = year;
              return new Granule(new Granularity("year"), sum);
          }
      }
      else if(fields.getName().equals("IntervalInputFormat")){

          long sum = 0;

          if(hourField != null ) {
              sum = sumHours(year, term, week, day, hour);
              return new Granule(new Granularity("hour"), sum);
          }
          if(dayField != null) {
              sum = sumDays(year, term, week, day);
              return new Granule(new Granularity("day"), sum);
          }
          if(weekField != null){
              sum = sumWeeks(year, term, week);
              return new Granule(new Granularity("week"), sum);
          }
          if(termField != null){
              sum = sumTerms(year, term);
              return new Granule(new Granularity("term"), sum);
          }
          if(yearField != null){
              sum = year;
              return new Granule(new Granularity("year"), sum);
          }
      }

      throw new CalendarServiceException("Exception when converting Fields to Granule: undefined temporal data type " +
                                                                                                      fields.getName());
  }

  public static void main(String[] args)
  {
    long[] splitResults;
    
    /*
    0-based counting:
    ----------------------------------------------------------------------------------
    | 2000 - 2001 Academic Calendar:                                                 |
    | 0.   Fall        2000-8-21     ->    2000-12-15  (17 weeks    ---  0   -   16) |x
    | 1.   Winter      2000-12-18    ->    2001-1-9    (3.4 weeks   ---  17  -   20) |x
    | 2.   Spring      2001-1-10     ->    2001-5-11   (17.6 weeks  ---  20  -   37) |
    | 3.   Summer      2001-5-14     ->    2001-8-8    (12.6 weeks  ---  38  -   50) |  -2
    |                                                                                |
    | 2001 - 2002 Academic Calendar                                                  |
    | 4.   Fall        2001-8-20     ->    2001-12-14  (17 weeks    ---  51  -   67) |x
    | 5.   Winter      2001-12-17    ->    2002-1-8    (3.4 weeks   ---  68  -   71) |
    | 6.   Spring      2002-1-9      ->    2002-5-10   (17.6 weeks  ---  71  -   88) |x
    | 7.   Summer      2002-5-13     ->    2002-8-8    (12.8 weeks  ---  89  -   101)|  -1
    |                                                                                |
    | 2002 - 2003 Academic Calendar                                                  |
    | 8.   Fall        2002-8-26     ->    2002-12-20  (17 weeks    ---  102 -   118)|x
    | 9.   Winter      2002-12-26    ->    2003-1-14   (3.8 weeks   ---  119 -   122)|  -3
    | 10.  Spring      2003-1-15     ->    2003-5-16   (17.6 weeks  ---  122 -   139)|x
    | 11.  Summer      2003-5-19     ->    2003-8-13   (12.6 weeks  ---  140 -   152)|  -2
    |                                                                                |
    | 2003 - 2004 Academic Calendar                                                  |
    | 12.  Fall        2003-8-25     ->    2003-12-19  (17 weeks    ---  153 -   169)|
    | 13.  Winter      2003-12-22    ->    2004-1-13   (3.4 weeks   ---  170 -   173)|
    | 14.  Spring      2004-1-14     ->    2004-5-14   (17.6 weeks  ---  173 -   190)|
    | 15.  Summer      2004-5-17     ->    2004-8-11   (12.6 weeks  ---  191 -   203)|x -2
    ----------------------------------------------------------------------------------

   

    // ********************** test castWeekToTerm ********************** 
    System.out.println("\n********************** test castWeekToTerm **********************");
    System.out.println("cast(1 week(s), terms)   = " + castWeekToTerm(0)   + " term(s) [should be 0]");
    System.out.println("cast(1 week(s), terms)   = " + castWeekToTerm(1)   + " term(s) [should be 0]");
    System.out.println("cast(15 week(s), terms)  = " + castWeekToTerm(15)  + " term(s) [should be 0]");
    System.out.println("cast(30 week(s), terms)  = " + castWeekToTerm(30)  + " term(s) [should be 2]");
    System.out.println("cast(46 week(s), terms)  = " + castWeekToTerm(46)  + " term(s) [should be 3]");
    System.out.println("cast(72 week(s), terms)  = " + castWeekToTerm(71)  + " term(s) [should be 5]");
    System.out.println("cast(102 week(s), terms) = " + castWeekToTerm(102) + " term(s) [should be 8]");
    System.out.println("cast(135 week(s), terms) = " + castWeekToTerm(135) + " term(s) [should be 10]");
    System.out.println("cast(140 week(s), terms) = " + castWeekToTerm(139) + " term(s) [should be 10]");
    System.out.println("cast(141 week(s), terms) = " + castWeekToTerm(140) + " term(s) [should be 11]");
    System.out.println("cast(198 week(s), terms) = " + castWeekToTerm(198) + " term(s) [should be 15]");
    System.out.println("cast(205 week(s), terms) = " + castWeekToTerm(205) + " term(s) [should be -1]");


    // ********************** test castTermToWeek ********************** 
    System.out.println("\n********************** test castTermToWeek **********************");
    System.out.println("cast(1 term(s), weeks)   = " + castTermToWeek(0)   + " week(s) [should be 0]");
    System.out.println("cast(3 term(s), weeks)   = " + castTermToWeek(2)   + " week(s) [should be 20]");
    System.out.println("cast(5 term(s), weeks)   = " + castTermToWeek(5)   + " week(s) [should be 68]");
    System.out.println("cast(6 term(s), weeks)   = " + castTermToWeek(6)   + " week(s) [should be 71]");
    System.out.println("cast(7 term(s), weeks)   = " + castTermToWeek(7)   + " week(s) [should be 89]");
    System.out.println("cast(8 term(s), weeks)   = " + castTermToWeek(8)   + " week(s) [should be 102]");
    System.out.println("cast(10 term(s), weeks)  = " + castTermToWeek(10)  + " week(s) [should be 122]");
    System.out.println("cast(11 term(s), weeks)  = " + castTermToWeek(11)  + " week(s) [should be 140]");
    System.out.println("cast(13 term(s), weeks)  = " + castTermToWeek(13)  + " week(s) [should be 170]");
    System.out.println("cast(16 term(s), weeks)  = " + castTermToWeek(15)  + " week(s) [should be 191]");
    System.out.println("cast(18 term(s), weeks)  = " + castTermToWeek(16)  + " week(s) [should be -1]");
    
    
    // ********************** test sumTerms **********************
    System.out.println("\n********************** test sumTerms **********************");
    System.out.println("term 0, academic year 0    = " + sumTerms(0, 0) + " [should be 0]");
    System.out.println("term 1, academic year 0    = " + sumTerms(0, 1) + " [should be 1]");    
    System.out.println("term 2, academic year 0    = " + sumTerms(0, 2) + " [should be 2]");
    System.out.println("term 3, academic year 0    = " + sumTerms(0, 3) + " [should be 3]");    
    System.out.println("term 0, academic year 1    = " + sumTerms(1, 0) + " [should be 4]");
    System.out.println("term 1, academic year 1    = " + sumTerms(1, 1) + " [should be 5]");    
    System.out.println("term 2, academic year 1    = " + sumTerms(1, 2) + " [should be 6]");
    System.out.println("term 3, academic year 1    = " + sumTerms(1, 3) + " [should be 7]");  
    System.out.println("term 0, academic year 2    = " + sumTerms(2, 0) + " [should be 8]");
    System.out.println("term 1, academic year 2    = " + sumTerms(2, 1) + " [should be 9]");    
    System.out.println("term 2, academic year 2    = " + sumTerms(2, 2) + " [should be 10]");
    System.out.println("term 3, academic year 2    = " + sumTerms(2, 3) + " [should be 11]");     
    System.out.println("term 0, academic year 3    = " + sumTerms(3, 0) + " [should be 12]");
    System.out.println("term 1, academic year 3    = " + sumTerms(3, 1) + " [should be 13]");    
    System.out.println("term 2, academic year 3    = " + sumTerms(3, 2) + " [should be 14]");
    System.out.println("term 3, academic year 3    = " + sumTerms(3, 3) + " [should be 15]");  
    System.out.println("term 4, academic year 2    = " + sumTerms(2, 4) + " [should be -1]");  
    
    
    // ********************** test sumWeeks **********************
    System.out.println("\n********************** test sumWeeks **********************");
    System.out.println("week 3, term 0, academic year 0  = " + sumWeeks(0, 0, 3)  + " [should be 3]");
    System.out.println("week 3, term 1, academic year 0  = " + sumWeeks(0, 1, 3)  + " [should be 20]");    
    System.out.println("week 3, term 2, academic year 0  = " + sumWeeks(0, 2, 3)  + " [should be 23]");
    System.out.println("week 11, term 3, academic year 0 = " + sumWeeks(0, 3, 11) + " [should be 49]");    
    // you can run test 0 of UofATests to see the results of all tests  


    // ********************** test sumDays **********************
    System.out.println("\n********************** test sumDays **********************");
    System.out.println("day 0, week 0,  term 0, academic year 0 = " + sumDays(0, 0, 0,  0) + " [should be 0]");        
    System.out.println("day 4, week 3,  term 0, academic year 0 = " + sumDays(0, 0, 3,  4) + " [should be 14]");    
    System.out.println("day 4, week 16, term 0, academic year 0 = " + sumDays(0, 0, 16, 4) + " [should be 79]");    
    System.out.println("day 0, week 3,  term 1, academic year 0 = " + sumDays(0, 1, 3, 0)  + " [should be 95]");    
    System.out.println("day 4, week 3,  term 0, academic year 1 = " + sumDays(1, 0, 3, 4)  + " [should be 267]");    
    System.out.println("day 1, week 8, term 2, academic year 2  = " + sumDays(2, 2, 8, 1)  + " [should be 640]");    
    System.out.println("day 5, week 3, term 0, academic year 0  = " + sumDays(0, 0, 3, 5)  + " [should be -1]");    
    System.out.println("day 4, week 4, term 1, academic year 0  = " + sumDays(0, 1, 4, 4)  + " [should be -1]");    
  
  
    // ********************** test sumHours **********************
    System.out.println("\n********************** test sumHours **********************");
    System.out.println("hour 0,  day 0, week 0,  term 0, academic year 0 = " + sumHours(0, 0, 0,  0, 0)  + " [should be 0]");
    System.out.println("hour 3,  day 2, week 0,  term 0, academic year 0 = " + sumHours(0, 0, 0,  2, 3)  + " [should be 17]");    
    System.out.println("hour 3,  day 4, week 3,  term 0, academic year 0 = " + sumHours(0, 0, 3,  4, 3)  + " [should be 185]");    
    System.out.println("hour 11, day 4, week 16, term 0, academic year 0 = " + sumHours(0, 0, 16, 4, 11) + " [should be 1103]");    
    System.out.println("hour 6,  day 0, week 3,  term 1, academic year 0 = " + sumHours(0, 1, 3, 0, 6)   + " [should be 1322]");    
    System.out.println("hour 8,  day 4, week 3,  term 0, academic year 1 = " + sumHours(1, 0, 3, 4, 8)   + " [should be 3732]");    
    System.out.println("hour 0,  day 1, week 8,  term 2, academic year 2 = " + sumHours(2, 2, 8, 1, 0)   + " [should be 8946]");    
    System.out.println("hour 14, day 4, week 3,  term 0, academic year 0 = " + sumHours(0, 0, 3, 5, 14)  + " [should be -1]");
    
    
    // ********************** test splitTerms **********************
    // you can run test 1 of UofATests to see the results of all splitTerms tests  


    // ********************** test splitDay **********************
    splitResults = splitDay(0);
    System.out.println("0 day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 0, 0, 0, 0]");
                       
    splitResults = splitDay(14);
    System.out.println("14 day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 4, 3, 0, 0]");                       
                       
    splitResults = splitDay(79);
    System.out.println("79 day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 4, 16, 0, 0]");                       

    splitResults = splitDay(95);
    System.out.println("95 day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 0, 3, 1, 0]"); 

    // next two test splitting days over week with less than normal amt of days
    splitResults = splitDay(sumDays(0, 3, 12, 2));
    System.out.println(sumDays(0, 3, 12, 2) + " day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 2, 12, 3, 0]");                       

    splitResults = splitDay(248);
    System.out.println("248 day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 0, 0, 0, 1]"); 

    splitResults = splitDay(267);
    System.out.println("267 day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 4, 3, 0, 1]");  
                       
    splitResults = splitDay(sumDays(1, 2, 1, 3));
    System.out.println(sumDays(1, 2, 1, 3) + " day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 3, 1, 2, 1]"); 
                       
    splitResults = splitDay(sumDays(2, 0, 4, 0));
    System.out.println(sumDays(2, 0, 4, 0) + " day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 0, 4, 0, 2]");                          
                       
    splitResults = splitDay(640);
    System.out.println("640 day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 1, 8, 2, 2]"); 
    
    // next two test splitting days over week that crosses two terms
    splitResults = splitDay(sumDays(3, 1, 3, 1));
    System.out.println(sumDays(3, 1, 3, 1) + " day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 1, 3, 1, 3]"); 
                       
    splitResults = splitDay(854);
    System.out.println("854 day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 0, 0, 2, 3]");                        
    
    splitResults = splitDay(sumDays(3, 3, 3, 3));
    System.out.println(sumDays(3, 3, 3, 3) + " day(s) = " 
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 3, 3, 3, 3]");
*/       

    // ********************** test splitHours **********************
    splitResults = splitHour(sumHours(0, 0, 0,  0, 0));
    System.out.println(sumHours(0, 0, 0,  0, 0) + " hour(s) = " 
                       + splitResults[UofACalendar.HOUR_INDEX] + " h, "        
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 0, 0, 0, 0, 0]");
                       
    splitResults = splitHour(sumHours(0, 0, 0,  2, 3));
    System.out.println(sumHours(0, 0, 0,  2, 3) + " hour(s) = " 
                       + splitResults[UofACalendar.HOUR_INDEX] + " h, "        
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 3, 2, 0, 0, 0]");
                       
    splitResults = splitHour(sumHours(0, 0, 3,  4, 3));
    System.out.println(sumHours(0, 0, 3,  4, 3) + " hour(s) = " 
                       + splitResults[UofACalendar.HOUR_INDEX] + " h, "        
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 3, 4, 3, 0, 0]");
                       
    splitResults = splitHour(sumHours(0, 0, 16, 4, 11));
    System.out.println(sumHours(0, 0, 16, 4, 11) + " hour(s) = " 
                       + splitResults[UofACalendar.HOUR_INDEX] + " h, "        
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 11, 4, 16, 0, 0]");
                       
    splitResults = splitHour(sumHours(0, 1, 3, 0, 6));
    System.out.println(sumHours(0, 1, 3, 0, 6) + " hour(s) = " 
                       + splitResults[UofACalendar.HOUR_INDEX] + " h, "        
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 6, 0, 3, 1, 0]");                       

    splitResults = splitHour(sumHours(1, 0, 3, 4, 8));
    System.out.println(sumHours(1, 0, 3, 4, 8) + " hour(s) = " 
                       + splitResults[UofACalendar.HOUR_INDEX] + " h, "        
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 8, 4, 3, 0, 1]");  
                       
    splitResults = splitHour(sumHours(2, 2, 8, 1, 0));
    System.out.println(sumHours(2, 2, 8, 1, 0) + " hour(s) = " 
                       + splitResults[UofACalendar.HOUR_INDEX] + " h, "        
                       + splitResults[UofACalendar.DAY_INDEX] + " d, "    
                       + splitResults[UofACalendar.WEEK_INDEX] + " w, "
                       + splitResults[UofACalendar.TERM_INDEX] + " t, " 
                       + splitResults[UofACalendar.YEAR_INDEX] + " y [should be 0, 1, 8, 2, 2]");                         
  }
}





