/**
 * Program Name: WorldCoords.java
 */



package wdcor;

import static java.lang.Math.PI;

/**
 *@author <a href="http://www1.cs.columbia.edu/~zeph">Zeph Grunschlag</a>
 *
 * <p>The <code>WorldCoords</code> class provides an encapsulation of a location
 * on the Planet Earth.   You instantiate a <code>WorldCoords</code> object
 * by specifying the longitude and latitude in degrees, as well as the name
 * of the location.
 * 
 * <p> There is also a static <code>WorldCoords</code> object WORLDCENTER
 * which can be specified by the user for purposes of comparing the distances of
 * various other <code>WorldCoords</code> objects.
 * 
 * <p>Methods are provided for computing distances between 
 * <code>WorldCoords</code> objects via spherical geometry, 
 * for settting the distance to the current WORLDCENTER, 
 * for comparing <code>WorldCoords</code> objects by their name,
 * and for comparing <code>WorldCoords</code> objects by their distance to the 
 * current WORLDCENTER.
 */
public class WorldCoords {
	// CONSTANTS
	 /**
	  *  World's radius in kilometers (6373.15 <i>km</i>)
	  */
	 public  final static double RADIUS = 6373.15;  

	 // DEFINING MEMBERS
	 /**
	  * The meridian angle
	  */
	  public double longitude = -1000.0;
	  
	  /**
	   * The azimuth angle 
	   */
	  public double latitude = -1000.0;  
	  
	  /**
	   * The space separated city and state names
	   */
	  public String city_state = " ";  // original string that was read in.
														// Kept so can print-out unaltered name
	  public String name; // name in upper-case, with spaces and punctuation
										// removed.  Used for comparison

	  /**
	   * Distance in kilometers to the world center 
	   */
	  public double dist2ctr = -1.0;    // value is NOT created at construction. Only after
														// world-center is set.
	  private double theta, phi;  	// Spherical coordinates: theta "parallel" to
													// equator, phi perpendicular to equator

	  
	 //CONSTRUCTOR
	/**
	 * Construct a <code>WorldCoords</code> object.  Angles are given in degrees, but
	 * are converted internally to radians.
	 * @param lo the longitude in degrees
	 * @param la the latitude in degrees
	 * @param c_s city and state (spaces are fine)
	 */
	  public WorldCoords(double lo, double la, String c_s) 
	  {
		  longitude = lo;	
		  latitude = la;
		  city_state = c_s;
		  reset();
	  }

	  //COPY CONSTRUCTOR
	/**
	 * Copy constructor.  Nothing surprising.
	 * @param wc the <code>WorldCoords</code> object which is to be copied 
	 */
	 public WorldCoords( WorldCoords wc ) 
	  {
		  longitude = wc.longitude;
		  latitude = wc.latitude;
		  city_state = wc.city_state;
		  dist2ctr = wc.dist2ctr;	
		  reset();
	  }

	 /**
	  * Current world center.
	  */ 
	  public static WorldCoords WORLDCENTER = new WorldCoords(-1000,-1000,"UNINITIALIZED");

	  /**
	   * DISTANCE FUNCTION. Calculates the distance in kilometers	 between city1
	   * and city2. The spherical (non-Euclidean) formula is used.
	   * @param c1  the first 	 <code>WorldCoords</code> object \
	   * @param c2  the second <code>WorldCoords</code> object 
	   * @return the distance between <code>c1</code> and <code>c2</code> in 
	   * kilometers 
	   */	
	 public static double earthDistance( WorldCoords c1, WorldCoords c2 ){
	    return  RADIUS * 
	      java.lang.Math.acos( 
	    		  java.lang.Math.cos(c1.phi)*java.lang.Math.cos(c2.phi) 
	    		  * ( 		java.lang.Math.cos(c1.theta) * java.lang.Math.cos(c2.theta)
	    				 + java.lang.Math.sin(c1.theta) * java.lang.Math.sin(c2.theta) 
	    			 )
			 + java.lang.Math.sin(c1.phi)*java.lang.Math.sin(c2.phi) 
		  );
	  }

	  /**
	   *	
	   * Reset the distance to center using the current WORLDCENTER
	   */
	  public void resetDist2ctr(){
	    dist2ctr = earthDistance( this, WORLDCENTER );
	  }
	  
		 // Sorting related functions for
		/**
		 *  Comparison by distance to current WORLDCENTER.
		 * @param c1 the first <code>WorldCoords</code> object  to compare 
		 * @param c2 the second <code>WorldCoords</code> object  to compare
		 * @return true iff the <code>c1</code> has smaller <code>dist2ctr</code> field
		 * 	 than  <code>c2</code> 
		 */
	  public static boolean lt_byDist(WorldCoords c1, WorldCoords c2){
	    return c1.dist2ctr < c2.dist2ctr;
	  }

	/**
	 *  Comparison by name.  Recall that name is the capitalized, de-spaced version 
	 *  of the originaly city/state information.
	 * @param c1 the first <code>WorldCoords</code> object  to compare 
	 * @param c2 the second <code>WorldCoords</code> object  to compare
	 * @return true iff the <code>c1</code> has lexicographically smaller name than
	 *  than  <code>c2</code> 
	 */
	  public static boolean lt_byName(WorldCoords wc1, WorldCoords wc2){
	    return wc1.name.compareTo(wc2.name) < 0;
	  }

	  /**
	   * This method is useful for radix sort. 
	   * @param wc the <code>WorldCoords</code> object
	   * @param index the index at which to peek at
	   * @return the letter at the given index, or <tt>(char)0</tt> if index is outside allowable 
	   * range
	   */
	  public static char digit(WorldCoords wc, int d){
	    return (d < wc.name.length() && d>=0) ? wc.name.charAt(d) : (char)0;
	  }
	  
	/**
	 * Conforms with output file format 
	 * <pre>
	 * &hellip;
	 * City, State 
	 * longitude 
	 * latitude 
	 * City, State
	 * longitude 
	 * latitude 
	 * City, State 
	 * longitude 
	 * latitude 
	 * &hellip;
	 * </pre>
	 * @return standard information about this WorldCoords object
	 */
	  public String toString(){
		  return "" + city_state + "\n" + longitude + "\n" + latitude + "\n";
	  }

	  
	  /**
		 * Re-calculate the short-name and angles in radians 
		 */ 
	  public void reset(){
	    resetAngles();
	    resetName();
	  }

	  private void resetAngles(){
	   theta = longitude * PI / 180;
	   phi = latitude * PI / 180;
	  }

	  private void resetAngles(double lo, double la){
	    longitude = lo;
	    latitude = la;
	    dist2ctr = -1; // force re-computation elsewhere
	    resetAngles();
	  }

	  /**
		 * Convert all values in city_state to cap's, remove non-alphabetic and
		 * store in name
		 */
	  private void resetName(){
		StringBuilder tmp = new StringBuilder("");
	    char c;
	    for(int i=0, j=0; i< city_state.length(); i++){
	      c = Character.toUpperCase(city_state.charAt(i));
	      if( c >= 'A' && c <= 'Z' ) tmp.append(c);
	    }
	    name = tmp.toString();
	  }    
}
