/*
 * Draws a lunar calendar.
 * John D. Ramsdell - March 1996
 * In memory of my mother.
 */

/*
 * Copyright 1996 by John D. Ramsdell
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package lunisolar;
import java.util.*;
import java.text.*;
import java.awt.*;
import java.awt.event.*;

/**
 * This applet draws a lunar calendar.
 * @version March 1996
 * @author John D. Ramsdell
 */
public final class Lunar_calendar extends java.applet.Applet {

  /**
   * This application draws a lunar calendar.  
   * If present, the args are concatinated to give the date.
   * Otherwise, the current date is used.
   */
  public static void main(String args[]) {
    Date middate;
    if (args.length == 0)
      middate = new Date();
    else {
      String s = args[0];
      for (int i = 1; i < args.length; i++)
	s += " " + args[i];
      try {
	middate = parse_date_string(s);
      }
      catch (ParseException pe) {
	System.err.println("Cannot parse " + s + " as a date");
	System.exit(1);
	return;			// Needed so that middate is always set
      }
    }

    Frame f = new Frame("Lunar Calendar");
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
	System.exit(0);
      }
    });

    Lunar_calendar l = new Lunar_calendar();
    l.begin(middate);
    f.add(l, "Center");
    f.pack();
    f.setVisible(true);
    l.start();
  }

    public static final int FULL = 0;
    /**
     * Constant for long style pattern.
     */
    public static final int LONG = 1;
    /**
     * Constant for medium style pattern.
     */
    public static final int MEDIUM = 2;
    /**
     * Constant for short style pattern.
     */
    public static final int SHORT = 3;
    /**
     * Constant for default style pattern.
     */
    public static final int DEFAULT = MEDIUM;

  private static Date parse_date_string(String text) 
       throws ParseException
  {				// Try parsing with the different formats
    try {
      DateFormat df = DateFormat.getDateInstance(SHORT);
      return df.parse(text);
    }
    catch (ParseException pe_short) {
      try {
	DateFormat df = DateFormat.getDateInstance(MEDIUM);
	return df.parse(text);
      }
      catch (ParseException pe_medium) {
	try {
	  DateFormat df = DateFormat.getDateInstance(LONG);
	  return df.parse(text);
	}
	catch (ParseException pe_long) {
	  DateFormat df = DateFormat.getDateInstance(FULL);
	  return df.parse(text);
	}
      }
    }
  }

  public String getAppletInfo() {
    return "John D. Ramsdell---March 1996";
  }

  public String[][] getParameterInfo() {
    String info[][] = {
      {"date", "string", "median date of the calendar" }
    };
    return info;
  }

  public void init() {
    String date_parameter = getParameter("date");
    try {
      Date middate;
      if (date_parameter == null)
	middate = new Date();
      else
	middate = parse_date_string(date_parameter);
      begin(middate);
    }
    catch (ParseException pe) {
      System.err.println("Cannot parse " + date_parameter + " as a date");
    }
  }

  /* Number of days in the calendar */
  private static final int table_days = 91;
  /* The calendar entries are computed by begin and used by paint. */
  double moon_phase_table[];
  String moon_label_table[];

  /* Computes calendar entries */
  private void begin(Date middate) {
    moon_phase_table = new double[table_days];
    moon_label_table = new String[table_days];

    GregorianCalendar midday = new GregorianCalendar();
    midday.setTime(middate);    /* Compute midday */
    midday.set(Calendar.HOUR_OF_DAY, 12);
    midday.set(Calendar.MINUTE, 0);
    midday.set(Calendar.SECOND, 0);
    
    midday.add(Calendar.DATE, -(table_days / 2)); // Start date

    for (int i = 0; i < table_days; i++) {
      try {
	moon_phase_table[i] = Lunisolar.moon_phase(midday.getTime().getTime());
	moon_label_table[i] = (1 + midday.get(Calendar.MONTH))
	                      + "/" + midday.get(Calendar.DATE);
      } catch (Exception e) {
	moon_phase_table[i] = 0.0;
	moon_label_table[i] = "Error";
      }
      midday.add(Calendar.DATE, 1); // Next day
    }
  }

  private static void drawCenteredString(Graphics g, String s, int x, int y) {
    FontMetrics fm = g.getFontMetrics();
    int h = fm.getMaxAscent();
    int w = fm.stringWidth(s);
    g.drawString(s, x - w / 2, y + h / 2);
  }

  private static double tan_day = Math.tan(Math.PI/15.0);

  /**
   * Draws the lunar calendar
   * Prints dots when there is not enough room for a date.
   */
  public void paint(Graphics g) {
    FontMetrics fm = g.getFontMetrics();
    int font_height = fm.getHeight();
    int font_ascent = fm.getMaxAscent();
    double baseline_skip = 3.0 * (double)font_height;
    int max_width = fm.stringWidth("12/30");
    double dot_rad = (double)max_width / tan_day;
    Dimension window_size = getSize();
    int diameter = Math.min(window_size.width, window_size.height);
    double r = 0.5 * (double)diameter - baseline_skip;
    int x = window_size.width / 2;
    int y = window_size.height / 2;
    int x_offset = Math.max(x - y, 0) + 2*font_height - font_ascent;
    int y_offset = Math.max(y - x, 0) + 2*font_height - font_ascent;
    double d = 0.5 * baseline_skip / Math.PI;
    double old_a = moon_phase_table[0];

    g.setColor(getForeground());

    g.drawLine(x_offset, y, 2*x-x_offset, y);
    g.drawLine(x, y_offset, x, 2*y - y_offset);
    drawCenteredString(g, "New Moon", x, font_ascent);
    drawCenteredString(g, "Full Moon", x, 2*y - font_ascent);

    for (int i = 0; i < table_days; i++) {
      double a = moon_phase_table[i];
      if (a < old_a) r -= baseline_skip;
      double rad = r - a * d;
      double dx = rad * Math.sin(a);
      double dy = rad * Math.cos(a);
      if (rad > dot_rad) 
	drawCenteredString(g, moon_label_table[i],
			   x + (int)Math.round(dx),
			   y - (int)Math.round(dy));
      else
	g.fillOval(x + (int)Math.round(dx) - 2,
		   y - (int)Math.round(dy) - 2,
		   4, 4);
      old_a = a;
    }
  }

  public Dimension getMinimumSize() {
    return new Dimension(200, 200);
  }

  public Dimension getPreferredSize() {
    return new Dimension(600, 600);
  }
}

