/* Calendar.java:  Java program to display a calendar for any month
   *   after 1582. The user inputs the year into a TextField and chooses
   *   the month from a Choice object (drop-down combo box), then clicks
   *   'New Calendar' to show the calendar for that month and year.
   *
   *   The applet assumes a 421x481 pixel surface on which to draw.
   */
  import java.applet.Applet;
  import java.awt.*;
  import java.util.Date;
  
  public class Calendar extends Applet
    {
    static final int YTOP = 90;      /* y-size of margin above calendar box */
    static final int YHEADER = 30;   /* y-size of horz strip with day names */
    static final int NCELLX = 7;     /* number of cells across */
    static final int CELLSIZE = 60;  /* size of each square cell */
    static final int MARGIN = 8;     /* margin from number to cell top, right */
    static final int FEBRUARY = 1;   /* special month during leap years */
  
    // Data entry controls at top.
    Label yearLabel = new Label("Year:");
    TextField yearTextField = new TextField("1996", 5);
    Label monthLabel = new Label("Month:");
    Choice monthChoice = new Choice();
    Button newCalButton = new Button("New Calendar");
  
    // Date object to get current month and year.
    Date now = new Date();
  
    // Font for controls at top.
    Font smallArialFont = new Font("Arial", Font.PLAIN, 15);
    // Font for for month/year caption above calendar.
    Font largeArialFont = new Font("Arial", Font.BOLD, 30);
  
    String days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
                     "Thursday", "Friday", "Saturday"};
  
    String months[] = {"January", "February", "March", "April",
                       "May", "June", "July", "August", "September",
                       "October", "November", "December"};
  
    int DaysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  
    // Month and year entered by user.
    int userMonth;
    int userYear;
  
    public void init()
    /*
       USE:  Get current month and year, initialize controls.
       NOTE: Called automatically when applet starts.
    */
      {
      setBackground(Color.white);
  
      // Init month and year to current values.
      userMonth = now.getMonth();
      userYear  = now.getYear() + 1900;
  
      // "Year:" label.
      yearLabel.setFont(smallArialFont);
      add(yearLabel);
  
      // Text field to get year from user.
      yearTextField.setFont(smallArialFont);
      yearTextField.setText(String.valueOf(userYear));
      add(yearTextField);
  
      // "Month:" label.
      monthLabel.setFont(smallArialFont);
      add(monthLabel);
  
      // Combo box to get month from user: add months, set default to now.
      monthChoice.setFont(smallArialFont);
      for (int i = 0; i < 12; i++)
        monthChoice.addItem(months[i]);
      monthChoice.select(userMonth);
      add(monthChoice);
  
      // "New calendar" button.
      newCalButton.setFont(smallArialFont);
      add(newCalButton);
      } // init
  
    public void paint(Graphics g)
    /*
       USE:  Draw calendar using globals userMonth and userYear.
       NOTE: Called automatically whenever surface needs to be redrawn;
             also when user clicks 'New Calendar' button, triggering repaint.
    */
      {
      FontMetrics fm;     /* to get font info */
      int fontAscent;     /* character height */
      int dayPos;         /* y-position of day strings */
      int xSize, ySize;   /* size of calendar body (cell table) */
      int numRows;        /* number of rows in cell table (4, 5, 6) */
      int xNum, yNum;     /* number position at top right of cells */
      int numDays;        /* number of days in month */
      String dayStr;      /* day of the week as a string */
      int marg;           /* margin of month string baseline from cell table */
      String caption;     /* month string at top center */
  
      // Get font info for number string positioning (default small font).
      fm = g.getFontMetrics();
      fontAscent = fm.getAscent();
      dayPos = YTOP + (YHEADER + fontAscent) / 2;
  
      // Get x-size of calendar body (cell table).
      xSize = NCELLX * CELLSIZE;
  
      // Header rectangle across top for day names.
      g.drawRect(0, YTOP, xSize, YHEADER);
  
      // Put days at top of each column, centered.
      for (int i = 0; i < NCELLX; i++)
        g.drawString(days[i], (CELLSIZE-fm.stringWidth(days[i]))/2 + i*CELLSIZE,
                     dayPos);
  
      // Get number of calendar rows needed for this month.
      numRows = NumberRowsNeeded(userYear, userMonth);
  
      // Vertical lines of cell table.
      ySize =  numRows * CELLSIZE;
      for (int i = 0; i <= xSize; i += CELLSIZE)
        g.drawLine(i, YTOP + YHEADER, i, YTOP + YHEADER + ySize);
  
      // Horizontal lines of cell table.
      for (int i = 0, j = YTOP + YHEADER; i <= numRows; i++, j += CELLSIZE)
        g.drawLine(0, j, xSize, j);
  
      // Init number positions (upper right of cell).
      xNum = (CalcFirstOfMonth(userYear, userMonth) + 1) * CELLSIZE - MARGIN;
      yNum = YTOP + YHEADER + MARGIN + fontAscent;
  
      // Get number of days in month, adding one if February of leap year.
      numDays = DaysInMonth[userMonth] +
                ((IsLeapYear(userYear) && (userMonth == FEBRUARY)) ? 1 : 0);
  
      // Show numbers at top right of each cell, right justified.
      for (int day = 1; day <= numDays; day++)
        {
        dayStr = String.valueOf(day);
        g.drawString(dayStr, xNum - fm.stringWidth(dayStr), yNum);
        xNum += CELLSIZE;
        // If xNum to right of calendar, 'new line'.
        if (xNum > xSize)
          {
          xNum = CELLSIZE - MARGIN;
          yNum += CELLSIZE;
          }  // if
        }  // for
  
      // Set large font for month/year caption.
      g.setFont(largeArialFont);
      // Get font info for string positioning (large font now current).
      fm = g.getFontMetrics();
      // Set margin for y-positioning of caption.
      marg = 2 * fm.getDescent();
  
      // Set caption to month string and center at top.
      caption = months[userMonth] + " " + String.valueOf(userYear);
      g.drawString(caption, (xSize-fm.stringWidth(caption))/2, YTOP - marg);
      } // paint
  
    public boolean action(Event e, Object o)
    /*
       USE:  Update month and year globals, paint when user clicks button.
    */
      {
      int userYearInt;
  
      if (e.target instanceof Button)
        {
        if ("New Calendar".equals((String)o))
          {
          // Get month from combo box (Choice control).
          userMonth = monthChoice.getSelectedIndex();
  
          // Get year from TextField, update userYear only if year ok.
          userYearInt = Integer.parseInt(yearTextField.getText(), 10);
          if (userYearInt > 1581)
            userYear = userYearInt;
  
          // Call paint() to draw new calendar.
          repaint();
          return true;
          } // inner if
        } // outer if
  
      return false;
      } // action
  
    int NumberRowsNeeded(int year, int month)
    /*
       USE:  Calculates number of rows needed for calendar.
       IN:   year = given year after 1582 (start of the Gregorian calendar).
             month = 0 for January, 1 for February, etc.
       OUT:  Number of rows: 5 or 6, except for a 28 day February with
             the first of the month on Sunday, requiring only four rows.
    */
      {
      int firstDay;       /* day of week for first day of month */
      int numCells;       /* number of cells needed by the month */
    
      /* Start at 1582, when modern calendar starts. */
      if (year < 1582) return (-1);
    
      /* Catch month out of range. */
      if ((month < 0) || (month > 11)) return (-1);
    
      /* Get first day of month. */
      firstDay = CalcFirstOfMonth(year, month);
    
      /* Non leap year February with 1st on Sunday: 4 rows. */
      if ((month == FEBRUARY) && (firstDay == 0) && !IsLeapYear(year))
        return (4);
    
      /* Number of cells needed = blanks on 1st row + days in month. */
      numCells = firstDay + DaysInMonth[month];
    
      /* One more cell needed for the Feb 29th in leap year. */
      if ((month == FEBRUARY) && (IsLeapYear(year))) numCells++;
    
      /* 35 cells or less is 5 rows; more is 6. */
      return ((numCells <= 35) ? 5 : 6);
      } // NumberRowsNeeded
    
    int CalcFirstOfMonth(int year, int month)
    /*
       USE:  Calculates day of the week the first day of the month falls on.
       IN:   year = given year after 1582 (start of the Gregorian calendar).
             month = 0 for January, 1 for February, etc.
       OUT:  First day of month: 0 = Sunday, 1 = Monday, etc.
    */
      {
      int firstDay;       /* day of week for Jan 1, then first day of month */
      int i;              /* to traverse months before given month */
    
      /* Start at 1582, when modern calendar starts. */
      if (year < 1582) return (-1);
    
      /* Catch month out of range. */
      if ((month < 0) || (month > 11)) return (-1);
    
      /* Get day of week for Jan 1 of given year. */
      firstDay = CalcJanuaryFirst(year);
    
      /* Increase firstDay by days in year before given month to get first day
       * of month.
       */
      for (i = 0; i < month; i++)
        firstDay += DaysInMonth[i];
    
      /* Increase by one if month after February and leap year. */
      if ((month > FEBRUARY) && IsLeapYear(year)) firstDay++;
    
      /* Convert to day of the week and return. */
      return (firstDay % 7);
      } // CalcFirstOfMonth
    
    boolean IsLeapYear(int year)
    /*
       USE:  Determines if given year is a leap year.
       IN:   year = given year after 1582 (start of the Gregorian calendar).
       OUT:  TRUE if given year is leap year, FALSE if not.
       NOTE: Formulas capture definition of leap years; cf CalcLeapYears().
    */
      {
    
      /* If multiple of 100, leap year iff multiple of 400. */
      if ((year % 100) == 0) return((year % 400) == 0);
    
      /* Otherwise leap year iff multiple of 4. */
      return ((year % 4) == 0);
      } // IsLeapYear
    
    int CalcJanuaryFirst(int year)
    /*
       USE:  Calculate day of the week on which January 1 falls for given year.
       IN:   year = given year after 1582 (start of the Gregorian calendar).
       OUT:  Day of week for January 1: 0 = Sunday, 1 = Monday, etc.
       NOTE: Formula starts with a 5, since January 1, 1582 was a Friday; then
             advances the day of the week by one for every year, adding the
             number of leap years intervening, because those years Jan 1
             advanced by two days. Calculate mod 7 to get the day of the week.
    */
      {
      /* Start at 1582, when modern calendar starts. */
      if (year < 1582) return (-1);
    
      /* Start Fri 01-01-1582; advance a day for each year, 2 for leap yrs. */
      return ((5 + (year - 1582) + CalcLeapYears(year)) % 7);
      } // CalcJanuaryFirst
    
    int CalcLeapYears(int year)
    /*
       USE:  Calculate number of leap years since 1582.
       IN:   year = given year after 1582 (start of the Gregorian calendar).
       OUT:  number of leap years since the given year, -1 if year < 1582
       NOTE: Count doesn't include the given year if it is a leap year.
             In the Gregorian calendar, used since 1582, every fourth year
             is a leap year, except for years that are a multiple of a
             hundred, but not a multiple of 400, which are no longer leap
             years. Years that are a multiple of 400 are still leap years:
             1700, 1800, 1990 were not leap years, but 2000 will be.
    */
      {
      int leapYears;      /* number of leap years to return */
      int hundreds;       /* number of years multiple of a hundred */
      int fourHundreds;   /* number of years multiple of four hundred */
    
      /* Start at 1582, when modern calendar starts. */
      if (year < 1582) return (-1);
    
      /* Calculate number of years in interval that are a multiple of 4. */
      leapYears = (year - 1581) / 4;
        
      /* Calculate number of years in interval that are a multiple of 100;
       * subtract, since they are not leap years.
       */
      hundreds = (year - 1501) / 100;
      leapYears -= hundreds;
        
      /* Calculate number of years in interval that are a multiple of 400;
       * add back in, since they are still leap years.
       */
      fourHundreds = (year - 1201) / 400;
      leapYears += fourHundreds;
        
      return (leapYears);
      } // CalcLeapYears
        
  } // class Calendar