package usgs.util;

/**
 *  Units.java - static class for converting types of units
 *  Inspired by an applet created by Robert Johnson, 5/96
 *  Rewritten and expanded for use in USGS by Mark Gray 5/97
 *  Temperature conversions are a special case because they
 *  involve shifting the zero point. Functions CelsiusToFahrenheit,
 *  KelvinToCelsius, RankineToFahrenheit, and the reverses are provided.
**/

//import java.util.*;

public class Units {

  //metric interrelationships, several are not commonly used
  public final static double exa       = 1E18;  // E = exa;
  public final static double peta      = 1E15;  // P = peta;
  public final static double tera      = 1E12;  // T = tera;
  public final static double giga      = 1E9;   // G = giga;
  public final static double mega      = 1E6;   // M = mega;
  public final static double hectokilo = 1E5;   // hk= hectokilo;
  public final static double myria     = 1E4;   // ma= myria;
  public final static double kilo      = 1000;  // k = kilo;
  public final static double hecto     = 100;   // h = hecto;
  public final static double deka      = 10;    // da= deka;
  public final static double deci      = 0.1;   // d = deci;
  public final static double centi     = 0.01;  // c = centi;
  public final static double milli     = 1E-3;  // m = milli;
  public final static double decimilli = 1E-4;  // dm= decimilli;
  public final static double centimilli= 1E-5;  // cm= centimilli;
  public final static double micro     = 1E-6;  // u = micro;
  public final static double nano      = 1E-9;  // n = nano;
  public final static double pico      = 1E-12; // p = pico;
  public final static double femto     = 1E-15; // f = femto;
  public final static double atto      = 1E-18; // a = atto;

  /* Unit conversions
   * All units are United States or metric unless otherwise stated.
   * CIA indicates that a value was found in the 1996 CIA World Fact Book
   *  Appendix E - Weights and Measures 
   *  http://www.odci.gov/cia/publications/nsolo/wfb-appe.htm
   * CRC indicates that the value was found in the 
   * CRC Standard Mathematical Tables and Formulae, 30th edition, Chapter 10.1
   * CRC specifies more decimal places, CIA contains more unit types.
   * NASA = NASA Dictionary of Technical Terms for Aerospace Use, 1965
   *  http://sulu.lerc.nasa.gov/dictionary/content.html
   *
   * Stated values are exact if they are expressed as integers, unless
   * stated otherwise. Tilde (~) means approximately, not exactly.
   *
   * Units are abbreviated and concatenated with a P in between, so
   * inPft should be read as "inches per foot".
   * Numbers in constants are powers: in3Pgal is cubic inches per gallon.
   *
   * To reduce error and improve understanding of unit relationships,
   * as few values as possible are expressed as inexact decimals.
   * Most are expressed as an equation which computes the value. (These 
   * equations are evaluated only once, in most cases at compile time.)
   *
   * Some decimal values here are not attributed to any source. 
   * Most of these were part of the applet this class was inspired by 
   * and should be verified and attributed to a reliable source.
   */

  //count
  public final static double score     = 20;
  public final static double dozen     = 12;
  public final static double gross     = dozen*dozen; //  144
  public final static double greatgross= dozen*gross; // 1728

  //linear distance
  public final static double milPin    = 1000;
  public final static double caliberPin= 100;
  public final static double inPhand   = 4;
  public final static double inPspan   = 9;
  public final static double inPft     = 12;
  public final static double inPpace   = 30;
  public final static double ftPlink   = 0.66; // exact
  public final static double ftPyd     = 3;
  public final static double ftPfathom = 6;
  public final static double ftProd    = 16.5; // exact
  public final static double ftPchain  = 66;
  public final static double ftPbolt   = 120;
  public final static double ftPfurlong= 660;
  public final static double ftPmi     = 5280;

  /* In 1959 the U.S. foot was legally defined as 0.3048m to bring the foot 
   * into agreement with the definition used in other countries. 
   * Any data in feet derived from and published as a result of geodetic 
   * surveys within the U.S. remain with the old standard, which is named the 
   * U.S. survey foot, defined as 1200/3937m in 1893. (0.3048006m at 7 decimal
   * places) The new foot is shorter by exactly two parts per million. 
   * (Federal Standard 376B January 27, 1993)
   */
  public final static double mPft      = 0.3048;     //          exact
  public final static double mPin      = mPft/inPft; // 0.0254   exact
  public final static double mPmi      = mPft*ftPmi; // 1609.344 exact

  public final static double ftPm      = 1/mPft;     //~3.28
  public final static double inPm      = 1/mPin;     //~39.37
  public final static double miPm      = 1/mPmi;     //~0.00062137

  /* Nautical mile (NASA): For practical navigation it is usually considered 
   * the length of 1 minute of any great circle of the earth, the meridian 
   * being the great circle most commonly used. Because of various lengths 
   * of the nautical mile in use throughout the world, due to differences 
   * in definition and the assumed size and shape of the earth, the 
   * International Hydrographic Bureau in 1929 proposed a standard length of 
   * 1852 meters which is known as the international nautical mile. 
   * This has been adopted by nearly all maritime nations. The U.S. 
   * Departments of Defense and Commerce adopted this value July 1, 1954.
   */
  public final static double mPnmi      = 1852;  // meter per nautical mi exact
  public final static double mmPpoint   = 0.3514598;// mm per typographic pt
  public final static double pointPpica = 12;    // points per typographic pica
  //public final static double mProwland = 1E-10;// meters per rowland (?)

  //area
  public final static double in2Pft2  = 144;
  public final static double ft2Pyd2  = 9;
  public final static double ft2Pacre = 43560;
  public final static double acrePmi2 = 640;       // 1 section = 1 mi^2
  public final static double in2Pm2   = inPm*inPm; //~1550
  public final static double ft2Pm2   = ftPm*ftPm; //~10.76
  public final static double m2Pft2   = mPft*mPft; //~0.0929
  public final static double m2Pacre  = m2Pft2*ft2Pacre; //~4046.856
  public final static double m2Pare   = 100;       // sq meters per are
  public final static double m2Pha    = 10000;     // sq meters per hectare

  //volume, liquid
  public final static double BozPoz   = 1.041; // British ounces per ounce, CIA
  public final static double minimPoz = 480;   // minims per fluid ounce
  public final static double ldramPoz = 8;     // liquid drams per ounce
  public final static double tspPoz   = 6;     // teaspoons per ounce
  public final static double tbspPoz  = 2;     // tablespoons per ounce
  public final static double ozPgill  = 4;     // ounces per gill
  public final static double ozPcup   = 8;     // ounces per cup
  public final static double ozPpint  = 16;    // ounces per pint
  public final static double ozPqt    = 32;    // ounces per quart
  public final static double ozPgal   = 64;    // ounces per gallon
  public final static double in3Pgal  = 231;   // cubic inch per gallon, exact
  public final static double LPm3     = 1000;  // liters per cubic meter
  public final static double in3Pft3  = inPft*inPft*inPft; // 1728
  public final static double ft3Pyd3  = ftPyd*ftPyd*ftPyd; //   27
  public final static double ft3Pgal  = in3Pgal/in3Pft3;   //   ~0.13368
  public final static double ft3Pm3   = ftPm*ftPm*ftPm;    //  ~35.3146667
  public final static double LPft3    = LPm3/ft3Pm3;       //  ~28.31685;
  public final static double LPgal    = LPft3*ft3Pgal;     //   ~3.785412;
  public final static double sterePm3 = 1;     // steres = cubic meters

  //mass (or weight on Earth)
  public final static double adramPoz = 16;    //avoirdupois drams/ounce
  public final static double ozPlb    = 16;    //avoirdupois ounces/pound
  public final static double grainPlb = 7000;  //grains per avoirdupois pound
  public final static double grainPtlb= 5760;  //grains per troy/apoth. pound
  public final static double lbPst    = 2000;  //avoirdupois pounds/short ton
  public final static double lbPlt    = 2240;  //avoirdupois pounds/long ton

  public final static double gPslug   = 14593.90;   //grams per slug 
  public final static double gPlb     = 453.59237;  // per avoirdupois pound
  public final static double gPtlb    = 373.2417216;// per troy pound
  public final static double gPmt     = mega;       //metric ton = 1 Megagram
  public final static double gPquintal= hectokilo;  //100,000 grams

  //pressure - Pascal (Pa) = Newton per sq meter
  public final static double PaPatm   = 101325;     //per atmosphere (exact?)
  public final static double PaPbar   = 100000;     //per bar (meterological)
  public final static double PaPpsi   = 47.88026;   //per pound-force per sq in
  public final static double PaPmmHg  = 133.3224;   //per millimeter of mercury
  public final static double PaPtorr  = 133.3224;   //per torr (=mmHg)
  public final static double PaPinHg  = 3386.38;    //per inch of mercury
  public final static double PaPftH2O = 2988.98;    //per foot of water

  //force newton (N) = kg*m/sec/sec
  public final static double NPlbf    = 4.448222;   //Newtons per pound-force
  public final static double NPpoundal= 0.1382550;
  public final static double NPdyn    = 1/100000;   // 1/dynes per Newton

  //Energy/heat - Joule (J) = Newton-meter = watt-second
  public final static double JPbtu    = 1055.056;       //british thermal unit
  public final static double JPtherm  = 105.4804 * mega;//Joules per therm
  public final static double JPhphr   = 2.684520 * mega;//horsepower-hour
  public final static double JPftlbs  = 1.355818;       //foot-pound-force-sec
  public final static double JPerg    = 1/10000000;     //Joules per erg
  public final static double JPcal    = 4.184;          //physics calorie
  //physics calories per nutrition Calorie (kilocalorie)
  public final static double calPCal  = kilo;         

  //power - watt (W) = Joule/sec
  public final static double WPbtus     = 1055.056;  // Watts per btu/second
  public final static double WPbtuhr    = 0.2930711; // Watts per btu/hr
  public final static double WPhp       = 746;       // per electric horsepower
  public final static double WPton      = 3516.85;   // per ton (refrigeration)
  public final static double WPftlbs    = 1.355818;  // foot-pound-force/sec

  //luminance - candela per square meter (cd/m2)
  public final static double cdm2Plambert     = 3183.099;
  public final static double cdm2Pfootlambert = 3.426259;

  //illuminance
  public final static double lxPftc      = 10.76391; //lux per footcandle

  //time - duplicate in Time.java
  public final static long secPmin       = 60;
  public final static long minPhour      = 60;
  public final static long hourPday      = 24;
  public final static long dayPweek      = 7;
  public final static long dayPfortnight = 14;
  public final static long minPday       = minPhour * hourPday; //      1,440
  public final static long secPhour      = secPmin * minPhour;  //      3,600
  public final static long secPday       = secPhour * hourPday; //     86,400
  public final static long msecPmin      = 1000 * secPmin;      //     60,000
  public final static long msecPhour     = 1000 * secPhour;     //  3,600,000
  public final static long msecPday      = 1000 * secPday;      // 86,400,000

  //circular measure
  public final static double minPdeg     = 60;        //minutes per degree
  public final static double degPcircle  = 360;       //degrees per circle
  public final static double radPcircle  = 2*Math.PI; //radians per circle
  public final static double degPrad     = degPcircle/radPcircle; //~57.2958
  public final static double radPdeg     = radPcircle/degPcircle; //~0.01745

  //temperature
  public final static double boilingFahrenheit  = 212;
  public final static double freezingFahrenheit = 32;
  public final static double absZeroFahrenheit  = -459.67;
  public final static double absZeroCelsius     = -273.15;
  public final static double degFPdegC          = 9/5;

  //constants from CRC
  // speed of light in meters per second, this is the definition of a meter
  public final static double c_mPs            = 299792458; // exact
  // Gravitational constant in cubic centimeters per gram-second squared
  public final static double G_ccPgs          = 6.673E-8;  // +-0.003
  // Acceleration due to gravity at sea level, 45 degrees latitude
  public final static double grav_accel_cmPs2 = 980.6194;  // cm/sec2
  public final static double grav_accel_ftPs2 = 32.1726;   // ft/sec2
  public final static double Avogadro         = 6.022169E23;
  //mean distance of earth from the sun, Astronomical Unit, in meters
  public final static double mPAU             = 1.495979E11;


  /* Arrays of long unit names, unit abbreviations, and conversion factors:
   * These three arrays for each class of unit must be kept synchronized.
   * Name:         the full name of the unit
   * Abbreviation: the abbreviation of the unit, without a trailing period
   * Conversion:   what to multiply a number in these unit by to get a number
   *               in the base units (the first units in the array)
   */

  public final static String[] LengthName = 
  { "Length", "meters", "millimeters", "centimeters", "kilometers", 
    "inches", "feet", "yards", "miles", "nautical miles" };
  public final static String[] LengthAbbreviation = 
  { "Length",     "m",      "mm",       "cm",     "km",     
    "in",   "ft",   "yd",   "mi",   "nmi" };
  public final static double[] LengthConversion = 
  { 0,            1,        milli,      centi,    kilo,    
    mPin,   mPft,  mPft*ftPyd, mPmi, mPnmi };


  public final static String[] VolumeName = 
  { "Volume", "liters", "milliliters",   "cubic centimeters", 
    "cubic centimeters", "cubic meters", "cubic inches", "cubic feet", 
    "ounces", "quarts", "gallons"};
  public final static String[] VolumeAbbreviation = 
  { "Volume",     "L",      "mL",        "cm^3",   
    "cc",                "m^3",          "in^3",         "ft^3", 
    "oz",     "qt",     "gal" };
  public final static double[] VolumeConversion = 
  { 0,            1,        milli,      milli,    milli,   
    LPm3, LPft3/in3Pft3, LPft3, LPgal/ozPgal, LPgal/ozPgal/ozPqt, LPgal };


  public final static String[] MassName = 
  { "Mass",   "grams", "micrograms", "milligrams", "kilograms", 
    "pounds", "ounces", "short tons", "long tons", "metric tons" };

  public final static String[] MassAbbreviation = 
  { "Mass",       "g",      "ug",       "mg",     "kg",     
    "lb",   "oz",        "ston",     "lton",    "mton" };
  public final static double[] MassConversion = 
  { 0,            1,        micro,      milli,    kilo,
    gPlb,   gPlb/ozPlb,  gPlb*lbPst, gPlb*lbPlt, gPmt };


  public final static String[] EnergyName = 
  { "Energy", "joules", "kilojoules", "british thermal units", "ergs", 
    "calories", "kilocalories" };
  public final static String[] EnergyAbbreviation = 
  { "Energy", "J",      "kJ",        "Btu",    "erg",    "cal",  "kcal" };
  public final static double[] EnergyConversion = 
  { 0,         1,        kilo,       JPbtu,    JPerg,   JPcal,  JPcal*calPCal};


  public final static String[] ForceName = 
  { "Force",  "newtons", "pound-force", "poundal", "dyn" };
  public final static String[] ForceAbbreviation = 
  { "Force",      "N",      "lbf",      "poundal","dyn" };
  public final static double[] ForceConversion = 
  { 0,            1,        NPlbf,      NPpoundal,NPdyn };


  public final static String[] PressureName = 
  { "Pressure", "pascals", "kilopascals", "megapascals", "atmospheres", 
    "bars", "pounds per square inch", "torr", "millimeters of mercury", 
    "inches of mercury", "feet of water"};
  public final static String[] PressureAbbreviation = 
  { "Pressure",   "Pa",     "kPa",      "MPa",    "atm",
    "bar",  "psi",  "Torr", "mmHg", "inHg", "ftH2O"};
  public final static double[] PressureConversion = 
  { 0,            1,        kilo,       mega,     PaPatm,
    PaPbar, PaPpsi,  PaPtorr,  PaPmmHg, PaPinHg, PaPftH2O };


  public final static String[] EnergyPMassName = 
  { "Energy/mass", "kilojoules per kilogram",  "BTUs per pound",
    "kilocalories per kilogram" };
  public final static String[] EnergyPMassAbbreviation = 
  { "Energy/mass","kJ/kg",  "Btu/lbm",  "kcal/kg" };
  public final static double[] EnergyPMassConversion = 
  { 0,            1,        JPbtu/gPlb, JPcal };


  public final static String[] PowerName = 
  { "Power",    "watts", "kilowatts", "horsepower", "foot-pounds per second"};
  public final static String[] PowerAbbreviation = 
  { "Power",    "W",     "kW",        "hp",         "ft*lbf/s"};
  public final static double[] PowerConversion = 
  { 0,           1,       kilo,        WPhp,         WPftlbs };


  public final static String[] FlowName = 
  { "Flow", "cubic feet per second", "cubic feet per second", 
    "cubic meters per second" };
  public final static String[] FlowAbbreviation = 
  { "Flow", "cfs",    "ft^3/s",   "m^3/s" };
  public final static double[] FlowConversion = 
  { 0,       1,        1,          LPm3/LPft3 };


  public final static String[] ConcentrationName = 
  { "Concentration", "grams per liter", "milligrams per liter", 
    "micrograms per liter" };
  public final static String[] ConcentrationAbbreviation = 
  { "Concentration", "g/L", "mg/L",     "ug/L" };
  public final static double[] ConcentrationConversion = 
  { 0,            1,        milli,      micro };


  public final static String[] DischargeName = 
  { "Discharge",  "short tons per day", "pounds per day", 
    "kilograms per day", "metric tons per day", 
    "cfs*mg/l", "mg/l*cfs" };
  public final static String[] DischargeAbbreviation = 
  { "Discharge",  "T/d",    "lb/d",     
    "kg/d",   "Mg/d",   
    "cfs*mg/l", "mg/l*cfs" };
  public final static double[] DischargeConversion = 
  { 0,            1,        1/lbPst,    
    1/kilo*gPlb*lbPst, 1/mega*gPlb*lbPst, 
    LPft3*secPday/((gPlb/milli)*lbPst), LPft3*secPday/((gPlb/milli)*lbPst) };


  public final static String[] MetricName =
  { "Metric",     "",  "peta", "tera", "giga", "mega", "kilo", "hecto", "deka",
    "centi", "milli", "micro", "nano", "pico", "femto", "atto" };
  public final static String[] MetricAbbreviation =
  { "Metric",     "", "E", "P", "T",  "G",  "M",  "k",  "h",   "d",  
    "c",   "m",   "u",   "n",  "p",  "f",   "a" };
  public final static double[] MetricConversion = 
  { 0,            1, exa, peta, tera, giga, mega, kilo, hecto, deka, 
    centi, milli, micro, nano, pico, femto, atto };




  /* These arrays collect all the Names, Abbreviations, and Conversions into
     one array each so we can look up units without knowing what type of units
     they are. These three arrays must be aligned with each other to be useful.
   */

  public final static String[][] Name = {
    LengthName, VolumeName, MassName, 
    EnergyName, ForceName, PressureName, 
    EnergyPMassName, PowerName, FlowName, 
    ConcentrationName, DischargeName };

  public final static String[][] Abbreviation = {
    LengthAbbreviation, VolumeAbbreviation, MassAbbreviation, 
    EnergyAbbreviation, ForceAbbreviation, PressureAbbreviation, 
    EnergyPMassAbbreviation, PowerAbbreviation, FlowAbbreviation, 
    ConcentrationAbbreviation, DischargeAbbreviation };
    
  public final static double[][] Conversion = {
    LengthConversion, VolumeConversion, MassConversion, 
    EnergyConversion, ForceConversion, PressureConversion, 
    EnergyPMassConversion, PowerConversion, FlowConversion, 
    ConcentrationConversion, DischargeConversion };


  // Static temerature conversion routines

  public static double CelsiusToFahrenheit(double degreesCelsius) {
    return (degreesCelsius * degFPdegC + freezingFahrenheit);
  }
  public static double FahrenheitToCelsius(double degreesFahrenheit) {
    return ((degreesFahrenheit - freezingFahrenheit) / degFPdegC);
  }
  public static double KelvinToCelsius(double degreesKelvin) {
    return (degreesKelvin + absZeroCelsius);
  }
  public static double CelsiusToKelvin(double degreesCelsius) {
    return (degreesCelsius - absZeroCelsius);
  }
  public static double FahrenheitToRankine(double degreesFahrenheit) {
    return (degreesFahrenheit - absZeroFahrenheit);
  }
  public static double RankineToFahrenheit(double degreesRankine) {
    return (degreesRankine + absZeroFahrenheit);
  }

  /** Returns conversion factor to multiply fromUnits by to get toUnits.
   *  fromUnits and toUnits are abbreviations for the unit names
   **/
  public static double getConversionFactor(String fromUnits, String toUnits) {
    double conversion = 0;
//  System.out.println("From " + fromUnits +" to "+ toUnits);
//  System.out.println("From " + longName(fromUnits)+" to "+longName(toUnits));
    int measureType = -1;
    while (conversion == 0 && ++measureType < Abbreviation.length) {
      int unitIndex = 0;
      while (conversion == 0 && ++unitIndex < Abbreviation[measureType].length)
        if (Abbreviation[measureType][unitIndex] != null && 
          Abbreviation[measureType][unitIndex].equalsIgnoreCase (fromUnits))
          conversion = Conversion[measureType][unitIndex];
    }
    // System.out.println("conversion1 = " + conversion + " measureType = " + measureType);
    if (conversion == 0 || measureType >= Abbreviation.length) return 0;
    int unitIndex = 0;
    while (++unitIndex < Abbreviation[measureType].length)
      if(Abbreviation[measureType][unitIndex].equalsIgnoreCase(toUnits)) {
//System.out.println("conversion2 = " + Conversion[measureType][unitIndex]);
//System.out.println("final conversion = " + (conversion / Conversion[measureType][unitIndex]));
        return (conversion / Conversion[measureType][unitIndex]);
      }
// System.out.println(toUnits + " != " + Abbreviation[measureType][unitIndex]);
    return 0;
  }

  /** Given any abbreviation, return the long name of the unit type **/
  public static String getLongName(String units) {
    int[] location = lookupUnits(units);
    if (location[0] < 0) return units;
    return Name[location[0]][location[1]];
  }

  /** Given any abbreviation, return the name of the class of units it
   *  belongs to: gal -> "Volume"       ft -> "Length"
   **/
  public static String getMeasureType(String units) {
    int measureIndex=getMeasureIndex(units);
    if (measureIndex < 0) return "";
    return Abbreviation[measureIndex][0];
  }
  
  /** Given any abbreviation, return an array of the abbreviations of
   *  other units that measure this quantity: in -> [m, mm, cm, ft, yd, mi]
   **/
  public static String[] getUnitsWithSameMeasureType(String units) {
    int index=getMeasureIndex(units);
    if (index < 0) return null;
    String[] retval = new String[Abbreviation[index].length - 1];
    //copy all units, but not type name [0] to retval
    System.arraycopy(Abbreviation[index], 1, retval, 0, retval.length); 
    return retval;
  }

  /** Returns the index of the subarray of Abbreviation containing lookUnits**/
  protected static int getMeasureIndex(String lookUnits) {
    int measureType=-1;
    while (++measureType < Abbreviation.length) {
      int unitIndex = 0;
      while (++unitIndex < Abbreviation[measureType].length)
        if(Abbreviation[measureType][unitIndex].equalsIgnoreCase(lookUnits))
          return measureType;
      //else System.out.println(lookUnits + " != " + Abbreviation[measureType][unitIndex]);
    }
    return -1;
  }  

  /** Returns two-dimensional index of lookunits in Abbreviation array **/
  protected static int[] lookupUnits(String lookUnits) {
    int[] retval = new int[2];
    int measureType = -1;
    int unitIndex   =  0;
    boolean found = false;
    while (!found && ++measureType < Abbreviation.length) {
      unitIndex = 0;
      while (!found && ++unitIndex < Abbreviation[measureType].length)
        if (Abbreviation[measureType][unitIndex].equalsIgnoreCase(lookUnits)) {
          retval[0] = measureType;
          retval[1] = unitIndex;
          return retval;
        }
    }
    retval[0] = -1;
    retval[1] = -1;
    return retval;
  }
}
