/*
 * PlanetStats.java
 *
 * Class:   PlanetStats
 * Author:  Samuel Penn (sam@glendale.org.uk)
 * Version: 1.00 (17th May 1998)
 * Purpose: Java version of my original !Planet application
 *          for RISC OS. Calculates useful information about
 *          a planet.
 *
 * License: BSD
 */


import java.applet.*;
import java.awt.*;
import java.util.*;
import java.net.*;
import java.io.*;


public class PlanetStats extends Applet {
  GridBagLayout         gl;
  GridBagConstraints    gb;
  Label                 label;
  TextField             tfStarMass, tfDistance, tfDensity;
  TextField             tfDiameter, tfPeriod, tfPlanetMass;
  TextField             tfSurface, tfEscape, tfRealMass;
  Button                bOkay;
  Choice                chPlanets, chStars;

  void addLabel(int x, int y, String text) {
    gb.anchor = gb.EAST;
    gb.fill = gb.NONE;
    gb.weightx=gb.weighty=0;
    gb.gridwidth=gb.gridheight=1;
    gb.gridx=x;
    gb.gridy=y;
    label = new Label(text);
    gl.setConstraints(label, gb);
    add(label);
  }

  void addLabelL(int x, int y, String text) {
    gb.anchor = gb.WEST;
    gb.fill = gb.NONE;
    gb.weightx=gb.weighty=0;
    gb.gridwidth=gb.gridheight=1;
    gb.gridx=x;
    gb.gridy=y;
    label = new Label(text);
    gl.setConstraints(label, gb);
    add(label);
  }

  TextField addTextField(int x, int y, int width, String text) {
    TextField            tf;

    gb.anchor = gb.WEST;
    gb.fill = gb.HORIZONTAL;
    gb.weightx=gb.weighty=1;
    gb.gridwidth=width;
    gb.gridheight=1;
    gb.gridx=x;
    gb.gridy=y;
    tf = new TextField(text);
    gl.setConstraints(tf, gb);
    add(tf);

    return tf;
  }

  public void init() {
    gl = new GridBagLayout();
    gb = new GridBagConstraints();

    setLayout(gl);

    // Add all the static labels.
    addLabel(0, 0, "Star mass (solar)");
    addLabel(0, 1, "Distance (Mkm)");
    addLabel(0, 2, "Density (rd)");
    addLabel(0, 3, "Diameter (km)");
    addLabel(0, 4, "Period");
    addLabelL(0, 5, "Planetary Mass");
    addLabelL(1, 5, "Surface Gravity");
    addLabelL(2, 5, "Escape Velocity");

    // Add the various text fields.
    gb.insets = new Insets(3, 3, 3, 3);
    tfStarMass = addTextField(1, 0, 1, "1.0");
    tfRealMass = addTextField(2, 0, 1, "2.2e30");
    tfDistance = addTextField(1, 1, 1, "150");
    tfDensity = addTextField(1, 2, 1, "5.5");
    tfDiameter = addTextField(1, 3, 1, "12756");
    tfPeriod = addTextField(1, 4, 1, "365d");
    tfPlanetMass = addTextField(0, 6, 1, "5.97e24");
    tfSurface = addTextField(1, 6, 1, "1.00");
    tfEscape = addTextField(2, 6, 1, "11.18");

    // For those text fields which are display only,
    // set editable flag to be false.
    tfRealMass.setEditable(false);
    tfPlanetMass.setEditable(false);
    tfSurface.setEditable(false);
    tfEscape.setEditable(false);
    tfPeriod.setEditable(false);

    // Now create a 'calculate' button for the user to click.
    gb.gridx = 2;
    gb.gridy = 4;
    gb.fill = gb.HORIZONTAL;
    bOkay = new Button("Calculate");
    gl.setConstraints(bOkay, gb);
    add(bOkay);

    // Drop down list of pre-defined star types.
    addLabelL(3, 0, "Choose a star:");
    gb.gridx = 3;
    gb.gridy = 1;
    gb.fill = gb.HORIZONTAL;
    chStars = new Choice();
    gl.setConstraints(chStars, gb);
    add(chStars);

    chStars.addItem("Sol");
    chStars.addItem("Rigel (Supergiant)");
    chStars.addItem("Betelgeux (Red Supergiant)");
    chStars.addItem("Aldebaran (Red Giant)");
    chStars.addItem("Sirius B (White Dwarf)");
    chStars.addItem("Wolf 339 (Red Dwarf)");

    // Drop down list of pre-defined planet types.
    addLabelL(3, 2, "Choose a planet:");
    gb.gridx = 3;
    gb.gridy = 3;
    gb.fill = gb.HORIZONTAL;
    chPlanets = new Choice();
    gl.setConstraints(chPlanets, gb);
    add(chPlanets);

    chPlanets.addItem("Mercury");
    chPlanets.addItem("Venus");
    chPlanets.addItem("Earth");
    chPlanets.addItem("Mars");
    chPlanets.addItem("Jupiter");
    chPlanets.addItem("Saturn");
    chPlanets.addItem("Uranus");
    // Make sure planets are listed in order from the sun.
    if (plutoFurthest()) {
      chPlanets.addItem("Neptune");
      chPlanets.addItem("Pluto");
    } else {
      chPlanets.addItem("Pluto");
      chPlanets.addItem("Neptune");
    }
    chPlanets.select(2);

    calculateValues();
  }

  /*
   * PLUTOFURTHEST
   *
   * Returns true if Pluto is the further from the Sun
   * then Neptune is (they swap in March 1999).
   * I'm going to be short sighted and not worry about
   * people using this applet in the 23rd century.
   */
  boolean
  plutoFurthest() {
    Date          now = new Date();
    Date          swap = new Date(99, 2, 1);

    if (now.after(swap)) {
      return true;
    }

    return false;
  }



  double
  planet_mass(double density, double radius) {
    double        mass, volume;

    volume = (4.0/3.0)*Math.PI*Math.pow(radius, 3);
    mass = volume*density;

    return mass;
  }

  double
  surface_gravity(double density, double radius) {
    double        g, mass;

    mass = planet_mass(density, radius);
    g = (mass * 6.67e-11);
    g = g/Math.pow(radius, 2);

    return g;
  }

  double
  escape_velocity(double density, double radius) {
    double        escape, mass;

    mass = planet_mass(density, radius);
    escape = Math.sqrt(2*6.67e-11*mass/radius);

    return escape;
  }

  /*
   * SECONDSTOSTRING
   *
   * Takes a number of seconds, and returns a string
   * giving that time in seconds, minutes...years.
   */
  String
  secondsToString(double time) {
    String        str = new String("");
    int           years=0, days=0, hours=0;
    int           minutes=0, seconds=0;

    // Seconds.
    if (time>0) {
      seconds = (int)(time % 60);
      time = Math.floor(time/60);
    }
    // Minutes.
    if (time>0) {
      minutes = (int)(time % 60);
      time = Math.floor(time/60);
    }
    // Hours. We assume exactly 24 hours to a day.
    if (time>0) {
      hours = (int)(time % 24);
      time = Math.floor(time/24);
    }
    // Days. We assume exactly 365 days to a year.
    if(time>0) {
      days = (int)(time % 365);
      time = Math.floor(time/365);
    }
    // Years.
    years = (int)time;

    System.out.println(years+"y "+days+"d "+hours+"h "+
           minutes+"m "+seconds+"s");

    /*
     * Now for the complicated stuff. Only output those values
     * which are non-zero, but also try to be sensible about
     * accuracy. If total time is in years, don't output anything
     * less than days etc.
     */
    if (years>100) {
      // If time is > 100 years, only output years.
      str = new String(years+" years");
    } else if (years>1) {
      // If years at least 2, output years and days.
      str = new String(years+"y");
      if (days>0) {
        str = new String(str + " "+days+"d");
      }
    } else if (years>0) {
      // If only 1 year, then convert to days.
      str = new String((years*365+days)+" days");
    } else if (days>9) {
      // Less than a year. More than nine days.
      str = new String(days+"d");
      if (hours>0) {
        str = new String(str+" "+hours+"h");
      }
    } else if (days>1) {
      // Two days or more.
      str = new String(days+"d");
      if (hours>0) {
        str = new String(str+" "+hours+"h");
      }
      if (minutes>0) {
        str = new String(str+" "+minutes+"m");
      }
    } else {
      // Final case - display right down to seconds.
      if (days>0) {
        str = new String(str+days+"d ");
      }
      if (hours>0) {
        str = new String(str+hours+"h ");
      }
      if (minutes>0) {
        str = new String(str+minutes+"m ");
      }
      if (seconds>0) {
        str = new String(str+seconds+"s");
      }
    }

    return str;
  }

  /*
   * CALCULATE_VALUES
   *
   * Read the various user entry fields, and work out the
   * values of the rest.
   */
  public void
  calculateValues() {
    Float            value;
    double           starMass, velocity, distance;
    double           v, c, period;
    double           d, r, mass, g;

    // Mass of star (solar masses).
    try {
      value = new Float(tfStarMass.getText());
      starMass = value.doubleValue();
    } catch (NumberFormatException e) {
      starMass=0;
    }
    // Convert to metric (kg).
    starMass *= 2e30;
    tfRealMass.setText(""+starMass+" kg");
    // And now multiply by gravitational constant (G)
    // to find just how much it sucks...
    starMass *= 6.67e-11;

    // Distance of planet (Mkm).
    try {
      value = new Float(tfDistance.getText());
      distance = value.doubleValue();
    } catch (NumberFormatException e) {
      distance=0;
    }
    // Convert to metres.
    distance *= 1e9;

    // Find period of the planet's orbit.
    // v = Velocity of the orbit.
    // c = Circumference of the orbit.
    v = Math.sqrt(starMass/distance);
    c = 2 * distance * Math.PI;
    period = c/v;
    value = new Float(period);
    tfPeriod.setText(secondsToString(value.doubleValue()));

    // Now for planetary mass and gravity.
    try {
      value = new Float(tfDiameter.getText());
      d = value.doubleValue();
    } catch (NumberFormatException e) {
      d = 0;
    }
    r = d*500; // Find radius in metres.
    v = (Math.pow(r, 3.0))*(4/3)*Math.PI;
    try {
      value = new Float(tfDensity.getText());
      d = value.doubleValue();
    } catch (NumberFormatException e) {
      d = 0;
    }
    mass = v * d * 1000; // Find mass in kg.

    // Display mass of the planet (kg).
    tfPlanetMass.setText(""+mass+" kg");

    g = surface_gravity(d*1000, r);
    v = escape_velocity(d*1000, r);

    tfEscape.setText(""+(v/1000)+" km/s");
    tfSurface.setText(""+g+" m/s/s");
  }

  public void
  selectStar() {
    switch(chStars.getSelectedIndex()) {
      case 0: // Sol.
        tfStarMass.setText("1.0");
        break;
      case 1: // Rigel.
        tfStarMass.setText("50");
        break;
      case 2: // Betelgeux.
        tfStarMass.setText("20");
        break;
      case 3: // Aldebaran.
        tfStarMass.setText("6");
        break;
      case 4: // Sirius B.
        tfStarMass.setText("1");
        break;
      case 5: // Wolf 339.
        tfStarMass.setText("0.2");
        break;
    }
  }

  public void
  selectPlanet() {
    int          idx = chPlanets.getSelectedIndex();

    // Fudge the index if Pluto is eigth in list.
    if (idx==7 && !plutoFurthest()) {
      idx=8;
    } else if (idx==8 && !plutoFurthest()) {
      idx=7;
    }

    switch (idx) {
      case 0: // Mercury.
        tfDistance.setText("57.9");
        tfDiameter.setText("4878");
        tfDensity.setText("5.5");
        break;
      case 1: // Venus.
        tfDistance.setText("108.2");
        tfDiameter.setText("12104");
        tfDensity.setText("5.25");
        break;
      case 2: // Earth.
        tfDistance.setText("149.5979");
        tfDiameter.setText("12756");
        tfDensity.setText("5.517");
        break;
      case 3: // Mars.
        tfDistance.setText("227.94");
        tfDiameter.setText("6787");
        tfDensity.setText("3.94");
        break;
      case 4: // Jupiter.
        tfDistance.setText("778.34");
        tfDiameter.setText("142200");
        tfDensity.setText("1.33");
        break;
      case 5: // Saturn.
        tfDistance.setText("1427.0");
        tfDiameter.setText("119300");
        tfDensity.setText("0.71");
        break;
      case 6: // Uranus.
        tfDistance.setText("2869.6");
        tfDiameter.setText("51200");
        tfDensity.setText("1.27");
        break;
      case 7: // Neptune.
        tfDistance.setText("4496.7");
        tfDiameter.setText("49500");
        tfDensity.setText("1.77");
        break;
      case 8: // Pluto.
        tfDistance.setText("5900");
        tfDiameter.setText("2290");
        tfDensity.setText("2.1");
        break;
    }
  }

  /*
   * HELP
   *
   * Displays useful help information on the applet.
   */
  public void
  help() {
    URL         url;
    String      file;

    try {
      file = getDocumentBase().toExternalForm();
      System.out.println("DocumentBase = "+file);
      file = file.substring(0, file.lastIndexOf("/"));
      System.out.println("Path of document = "+file);
      file = new String(file+"/help.html");
      System.out.println("Help document = "+file);
      url = new URL(file);
    } catch (MalformedURLException e) {
      System.out.println("Malformed help URL");
      return;
    }
    getAppletContext().showDocument(url);
  }

  /*
   * ACTION
   *
   * General event handling.
   */
  public boolean
  action(Event e, Object arg) {
    if (e.target == bOkay) {
      calculateValues();
      return true;
    } else if (e.target == chStars) {
      selectStar();
      calculateValues();
      return true;
    } else if (e.target == chPlanets) {
      selectPlanet();
      calculateValues();
      return true;
    }

    return false;
  }
}
