/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.units;

import javax.annotation.concurrent.Immutable;

/**
 * Provides support for units that are based on a logarithm of the ratio of a
 * physical quantity to an underlying reference level.
 *
 * Instances of this class are immutable.
 *
 * @author Steven R. Emmerson
 */
@Immutable
public final class LogarithmicUnit extends UnitImpl implements DerivableUnit {
    private static final long           serialVersionUID = 1L;

    /**
     * The logarithmic base.
     *
     * @serial
     */
    private final double                base;

    private final transient double      lnBase;

    /**
     * The reference level.
     *
     * @serial
     */
    private final DerivableUnit         reference;
    private final transient DerivedUnit derivedUnit;

    /**
     * Constructs from a reference level and a logarithmic base.
     *
     * @param reference
     *            The reference level. Must be a {@link DerivableUnit}.
     * @param base
     *            The logarithmic base. Must be 2, {@link Math#E}, or 10.
     * @throws IllegalArgumentException
     *             if {@code reference} isn't a {@link DerivableUnit}.
     * @throws IllegalArgumentException
     *             if {@code base} isn't one of the allowed values.
     * @throws NullPointerException
     *             if {@code reference} is {@code null}.
     */
    public LogarithmicUnit(final Unit reference, final double base) {
        this(reference, base, null);
    }

    /**
     * Constructs from a reference level, a logarithmic base, and a unit
     * identifier.
     *
     * @param reference
     *            The reference level. Must be a {@link DerivableUnit}.
     * @param base
     *            The logarithmic base. Must be 2, {@link Math#E}, or 10.
     * @param id
     *            The identifier for the new unit.
     * @throws IllegalArgumentException
     *             if {@code reference} isn't a {@link DerivableUnit}.
     * @throws IllegalArgumentException
     *             if {@code base} isn't one of the allowed values.
     * @throws NullPointerException
     *             if {@code reference} is {@code null}.
     */
    public LogarithmicUnit(final Unit reference, final double base,
            final UnitName id) {
        super(id);
        if (reference == null) {
            throw new NullPointerException("Null reference argument");
        }
        if (!(reference instanceof DerivableUnit)) {
            throw new IllegalArgumentException("Not a DerivableUnit: "
                    + reference);
        }
        this.reference = (DerivableUnit) reference;
        if (base != 2 && base != 10 && base != Math.E) {
            throw new IllegalArgumentException("Invalid base: " + base);
        }
        this.base = base;
        lnBase = base == Math.E
                ? 1
                : Math.log(base);
        derivedUnit = reference.getDerivedUnit();
    }

    static Unit getInstance(final Unit unit, final double base) {
        return new LogarithmicUnit(unit, base);
    }

    /**
     * Returns the reference level.
     *
     * @return The reference level.
     */
    public DerivableUnit getReference() {
        return reference;
    }

    /**
     * Returns the logarithmic base.
     *
     * @return The logarithmic base of this unit.
     */
    public double getBase() {
        return base;
    }

    /*
     * From UnitImpl:
     */

    /**
     * Clones this unit, changing the identifier.
     *
     * @param id
     *            The identifier for the new unit.
     * @return This unit with its identifier changed.
     */
    public Unit clone(final UnitName id) {
        return new LogarithmicUnit((Unit) reference, getBase(), id);
    }

    /**
     * Multiply this unit by another unit.
     *
     * @param that
     *            The unit to multiply this unit by. Must be dimensionless.
     * @return The product of this unit and that.
     * @throws MultiplyException
     *             Can't multiply these units together.
     */
    @Override
    protected Unit myMultiplyBy(final Unit that) throws MultiplyException {
        if (!that.isDimensionless()) {
            throw new MultiplyException(that);
        }
        return that instanceof ScaledUnit
                ? new ScaledUnit(((ScaledUnit) that).getScale(), this)
                : this;
    }

    /**
     * Divide this unit by another unit.
     *
     * @param that
     *            The unit to divide this unit by.
     * @return The quotient of this unit and that.
     * @throws DivideException
     *             Can't divide these units.
     */
    @Override
    protected Unit myDivideBy(final Unit that) throws DivideException {
        if (!that.isDimensionless()) {
            throw new DivideException(that);
        }
        return that instanceof ScaledUnit
                ? new ScaledUnit(1.0 / ((ScaledUnit) that).getScale(), this)
                : this;
    }

    /**
     * Divide this unit into another unit.
     *
     * @param that
     *            The unit to divide this unit into.
     * @return The quotient of that unit and this unit.
     * @throws DivideException
     *             Can't divide these units.
     */
    @Override
    protected Unit myDivideInto(final Unit that) throws OperationException {
        throw new DivideException(that);
    }

    /**
     * Raise this unit to a power.
     *
     * @param power
     *            The power to raise this unit by. The only meaningful values
     *            are 0 and 1.
     * @return The result of raising this unit by the power power.
     * @throws RaiseException
     *             Can't raise this unit to {@code power}, which is neither 0
     *             nor 1.
     */
    @Override
    protected Unit myRaiseTo(final int power) throws RaiseException {
        if (power == 0) {
            return DerivedUnitImpl.DIMENSIONLESS;
        }
        if (power == 1) {
            return this;
        }
        throw new RaiseException(this);
    }

    /**
     * Returns the derived unit that is convertible with this unit.
     *
     * @return The derived unit that is convertible with this unit.
     */
    public DerivedUnit getDerivedUnit() {
        return derivedUnit;
    }

    /**
     * Converts a value in this unit to the equivalent value in the convertible
     * derived unit.
     *
     * @param amount
     *            The value in this unit.
     * @return The equivalent value in the convertible derived unit.
     * @throws ConversionException
     *             Can't convert between units.
     */
    public float toDerivedUnit(final float amount) throws ConversionException {
        return (float) toDerivedUnit((double) amount);
    }

    /**
     * Converts a value in this unit to the equivalent value in the convertible
     * derived unit.
     *
     * @param amount
     *            The value in this unit.
     * @return The equivalent value in the convertible derived unit.
     * @throws ConversionException
     *             Can't convert between units.
     */
    public double toDerivedUnit(final double amount) throws ConversionException {
        return reference.toDerivedUnit(Math.exp(amount * lnBase));
    }

    /**
     * Converts values in this unit to the equivalent values in the convertible
     * derived unit.
     *
     * @param input
     *            The values in this unit.
     * @param output
     *            The equivalent values in the convertible derived unit. May be
     *            the same array as input.
     * @return output.
     * @throws ConversionException
     *             Can't convert between units.
     */
    public float[] toDerivedUnit(final float[] input, final float[] output)
            throws ConversionException {
        for (int i = input.length; --i >= 0;) {
            output[i] = (float) (Math.exp(input[i] * lnBase));
        }
        return reference.toDerivedUnit(output, output);
    }

    /**
     * Converts values in this unit to the equivalent values in the convertible
     * derived unit.
     *
     * @param input
     *            The values in this unit.
     * @param output
     *            The equivalent values in the convertible derived unit. May be
     *            the same array as input.
     * @return output.
     * @throws ConversionException
     *             Can't convert between units.
     */
    public double[] toDerivedUnit(final double[] input, final double[] output)
            throws ConversionException {
        for (int i = input.length; --i >= 0;) {
            output[i] = Math.exp(input[i] * lnBase);
        }
        return reference.toDerivedUnit(output, output);
    }

    /**
     * Converts a value in the convertible derived unit to the equivalent value
     * in this unit.
     *
     * @param amount
     *            The value in the convertible derived unit.
     * @return The equivalent value in this unit.
     * @throws ConversionException
     *             Can't convert between units.
     */
    public float fromDerivedUnit(final float amount) throws ConversionException {
        return (float) fromDerivedUnit((double) amount);
    }

    /**
     * Converts a value in the convertible derived unit to the equivalent value
     * in this unit.
     *
     * @param amount
     *            The value in the convertible derived unit.
     * @return The equivalent value in this unit.
     * @throws ConversionException
     *             Can't convert between units.
     */
    public double fromDerivedUnit(final double amount)
            throws ConversionException {
        return Math.log(reference.fromDerivedUnit(amount)) / lnBase;
    }

    /**
     * Converts values in the convertible derived unit to the equivalent values
     * in this unit.
     *
     * @param input
     *            The values in the convertible derived unit.
     * @param output
     *            The equivalent values in this unit. May be the same array as
     *            input.
     * @return output.
     * @throws ConversionException
     *             Can't convert between units.
     */
    public float[] fromDerivedUnit(final float[] input, final float[] output)
            throws ConversionException {
        reference.fromDerivedUnit(input, output);
        for (int i = input.length; --i >= 0;) {
            output[i] = (float) (Math.log(output[i]) / lnBase);
        }
        return output;
    }

    /**
     * Converts values in the convertible derived unit to the equivalent values
     * in this unit.
     *
     * @param input
     *            The values in the convertible derived unit.
     * @param output
     *            The equivalent values in this unit. May be the same array as
     *            input.
     * @return output.
     * @throws ConversionException
     *             Can't convert between units.
     */
    public double[] fromDerivedUnit(final double[] input, final double[] output)
            throws ConversionException {
        reference.fromDerivedUnit(input, output);
        for (int i = input.length; --i >= 0;) {
            output[i] = (float) (Math.log(output[i]) / lnBase);
        }
        return output;
    }

    /**
     * Indicates if this unit is semantically identical to an object.
     *
     * @param object
     *            The object.
     * @return true if and only if this unit is semantically
     *         identical to object
     *				.
     */
    @Override
    public boolean equals(final Object object) {
        if (this == object) {
            return true;
        }
        if (!(object instanceof LogarithmicUnit)) {
            return false;
        }
        final LogarithmicUnit that = (LogarithmicUnit) object;
        return base == that.base && reference.equals(that.reference);
    }

    /**
     * Returns the hash code of this instance.
     *
     * @return The hash code of this instance.
     */
    @Override
    public int hashCode() {
        return Double.valueOf(base).hashCode() ^ getReference().hashCode();
    }

    /**
     * Indicates if this unit is dimensionless.
     *
     * @return true, always.
     */
    public boolean isDimensionless() {
        return true;
    }

    /**
     * Returns the string representation of this unit.
     *
     * @return The string representation of this unit.
     */
    @Override
    public String toString() {
        final String string = super.toString(); // get symbol or name
        return string != null
                ? string
                : getCanonicalString();
    }

    /**
     * Returns the canonical string representation of the unit.
     *
     * @return The canonical string representation.
     */
    public String getCanonicalString() {
        return (base == 2
                ? "lb"
                : base == Math.E
                        ? "ln"
                        : "lg") + "(re " + getReference().toString() + ")";
    }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.iosp.vis5d;

import ucar.ma2.*;

import ucar.nc2.*;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.constants._Coordinate;

import ucar.nc2.iosp.AbstractIOServiceProvider;

import ucar.nc2.iosp.grid.*;

import ucar.nc2.util.CancelTask;

import ucar.unidata.io.RandomAccessFile;

import visad.Set;
import visad.VisADException;

import visad.data.BadFormException;
import visad.data.vis5d.Vis5DCoordinateSystem;

import visad.data.vis5d.Vis5DVerticalSystem;

import java.io.IOException;

import java.util.Date;

import java.util.Hashtable;

/**
 * Vis5D grid file reader.  Only support Vis5D grids, not the TOPO files.
 *
 * @author dmurray
 */
public class Vis5DIosp extends AbstractIOServiceProvider {

  /**
   * Vis5D file reader
   */
  private V5DStruct v5dstruct;

  /**
   * file header
   */
  private static final String V5D = "V5D";

  /** from vis5d-4.3/src/v5d.h */

  /**
   * maximum number of varaiables
   */
  private final static int MAXVARS = 200;

  /**
   * maximum number of times
   */
  private final static int MAXTIMES = 400;

  /**
   * maximum number of rows
   */
  private final static int MAXROWS = 400;

  /**
   * maximum number of columns
   */
  private final static int MAXCOLUMNS = 400;

  /**
   * maximum number of levels
   */
  private final static int MAXLEVELS = 400;

  /**
   * row variable name
   */
  private final static String ROW = "row";

  /**
   * column variable name
   */
  private final static String COLUMN = "col";

  /**
   * level variable name
   */
  private final static String LEVEL = "lev";

  /**
   * time variable name
   */
  private final static String TIME = "time";

  /**
   * latitude variable name
   */
  private final static String LAT = "lat";

  /**
   * longitude variable name
   */
  private final static String LON = "lon";

  /**
   * maximum number of projection arguments
   */
  private final int MAXPROJARGS = MAXROWS + MAXCOLUMNS + 1;

  /**
   * maximum number of projection level arguments
   */
  private final int MAXVERTARGS = MAXLEVELS + 1;

  /**
   * table of param name to units for well know params
   */
  private static Hashtable<String, String> unitTable = null;

  /**
   * table of param name to var index
   */
  private static Hashtable<Variable, Integer> varTable;

  /**
   * Is this a valid file?
   *
   * @param raf RandomAccessFile to check
   * @return true if a valid McIDAS AREA file
   * @throws IOException problem reading file
   */
  public boolean isValidFile(RandomAccessFile raf) throws IOException {
    // quick test
    raf.order(RandomAccessFile.BIG_ENDIAN);
    raf.seek(0);
    String got = raf.readString(V5D.length());
    if (got.equals(V5D)) {
      return true;
    } else {  // more rigorous test
      V5DStruct vv;
      try {
        vv = V5DStruct.v5dOpenFile(raf);
      } catch (BadFormException bfe) {
        vv = null;
      }
      return vv != null;
    }
  }

  /**
   * Get the file type id
   *
   * @return the file type id
   */
  public String getFileTypeId() {
    return "Vis5D";
  }

  /**
   * Get the file type description
   *
   * @return the file type description
   */
  public String getFileTypeDescription() {
    return "Vis5D grid file";
  }

  /**
   * Open the service provider for reading.
   *
   * @param raf        file to read from
   * @param ncfile     netCDF file we are writing to (memory)
   * @param cancelTask task for cancelling
   * @throws IOException problem reading file
   */
  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    super.open(raf, ncfile, cancelTask);
    if (unitTable == null) {
      initUnitTable();
    }
    if (v5dstruct == null) {
      makeFile(raf, ncfile, cancelTask);
    }
  }

  /**
   * Make the netcdf file
   *
   * @param raf        the RandomAccessFile
   * @param ncfile     the netCDF file handle
   * @param cancelTask the cancel task
   * @throws IOException problem reading the file
   */
  private void makeFile(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {

    ncfile.empty();
    int[] sizes = new int[5];
    int[] map_proj = new int[1];
    String[] varnames = new String[MAXVARS];
    String[] varunits = new String[MAXVARS];
    int[] n_levels = new int[MAXVARS];
    int[] vert_sys = new int[1];
    float[] vertargs = new float[MAXVERTARGS];
    double[] times = new double[MAXTIMES];
    float[] projargs = new float[MAXPROJARGS];

    try {
      v5dstruct = V5DStruct.v5d_open(raf, sizes, n_levels, varnames,
              varunits, map_proj, projargs,
              vert_sys, vertargs, times);
    } catch (BadFormException bfe) {
      throw new IOException("Vis5DIosp.makeFile: bad file "
              + bfe.getMessage());
    }

    if (sizes[0] < 1) {
      throw new IOException("Vis5DIosp.makeFile: bad file");
    }

    int nr = sizes[0];
    int nc = sizes[1];
    int nl = sizes[2];
    int ntimes = sizes[3];
    int nvars = sizes[4];
    // System.out.println("nr: "+nr);
    // System.out.println("nc: "+nc);
    // System.out.println("nl: "+nl);
    // System.out.println("ntimes: "+ntimes);
    // System.out.println("nvars: "+nvars);

    Dimension time = new Dimension(TIME, ntimes, true);
    Dimension row = new Dimension(ROW, nr, true);
    Dimension col = new Dimension(COLUMN, nc, true);
    ncfile.addDimension(null, time);
    ncfile.addDimension(null, row);
    ncfile.addDimension(null, col);

    // time
    Variable timeVar = new Variable(ncfile, null, null, TIME);
    timeVar.setDataType(DataType.DOUBLE);
    timeVar.setDimensions(TIME);
    timeVar.addAttribute(
            new Attribute(CDM.UNITS, "seconds since 1900-01-01 00:00:00"));
    timeVar.addAttribute(new Attribute("long_name", TIME));
    timeVar.addAttribute(new Attribute(_Coordinate.AxisType,
            AxisType.Time.toString()));
    Array varArray = new ArrayDouble.D1(ntimes);
    for (int i = 0; i < ntimes; i++) {
      ((ArrayDouble.D1) varArray).set(i, times[i]);
    }
    timeVar.setCachedData(varArray, false);
    ncfile.addVariable(null, timeVar);

    // rows and columns
    Variable rowVar = new Variable(ncfile, null, null, ROW);
    rowVar.setDataType(DataType.INT);
    rowVar.setDimensions(ROW);
    varArray = new ArrayInt.D1(nr, false);
    for (int i = 0; i < nr; i++) {
      ((ArrayInt.D1) varArray).set(i, i);
    }
    rowVar.setCachedData(varArray, false);
    ncfile.addVariable(null, rowVar);

    Variable colVar = new Variable(ncfile, null, null, COLUMN);
    colVar.setDataType(DataType.INT);
    colVar.setDimensions(COLUMN);
    varArray = new ArrayInt.D1(nc, false);
    for (int i = 0; i < nc; i++) {
      ((ArrayInt.D1) varArray).set(i, i);
    }
    colVar.setCachedData(varArray, false);
    ncfile.addVariable(null, colVar);

    // sanity check on levels
    Hashtable<Integer, Object> var_table = new Hashtable<>();
    boolean have3D = false;
    for (int i = 0; i < nvars; i++) {
      int nlevs = n_levels[i];
      if (!have3D && (nlevs > 1)) {
        have3D = true;
      }
      var_table.put(nlevs, new Object());
    }
    int n_var_groups = var_table.size();
    if (n_var_groups > 2) {
      throw new IOException(
              "Vis5DIosp.makeFile: more than two variable groups by n_levels");
    } else if (n_var_groups == 0) {
      throw new IOException(
              "Vis5DIosp.makeFile: number of variable groups == 0");
    }
    Variable vert = null;
    if (have3D) {
      Dimension lev = new Dimension(LEVEL, nl, true);
      ncfile.addDimension(null, lev);
      vert = makeVerticalVariable(vert_sys[0], nl, vertargs);
      if (vert != null) {
        ncfile.addVariable(null, vert);
      }
    }
    varTable = new Hashtable<>();
    String dim3D = TIME + " " + LEVEL + " " + COLUMN + " " + ROW;
    String dim2D = TIME + " " + COLUMN + " " + ROW;
    //String coords3D = TIME + " " + vert.getName() + " " + LAT + " " + LON;
    String coords3D = "unknown";
    if (vert != null) {
      coords3D = TIME + " Height " + LAT + " " + LON;
    }
    String coords2D = TIME + " " + LAT + " " + LON;

    for (int i = 0; i < nvars; i++) {
      Variable v = new Variable(ncfile, null, null, varnames[i]);
      if (n_levels[i] > 1) {
        v.setDimensions(dim3D);
        v.addAttribute(new Attribute(CF.COORDINATES, coords3D));
      } else {
        v.setDimensions(dim2D);
        v.addAttribute(new Attribute(CF.COORDINATES, coords2D));
      }
      v.setDataType(DataType.FLOAT);
      String units = varunits[i].trim();
      if (units.equals("")) {  // see if its in the unitTable
        String key = varnames[i].trim().toLowerCase();
        units = unitTable.get(key);
      }
      if (units != null) {
        v.addAttribute(new Attribute(CDM.UNITS, units));
      }
      // TODO: do two vars with the same name have different values?
      // check agaist duplicat variable names
      if (varTable.get(v) == null) {
        varTable.put(v, i);
        ncfile.addVariable(null, v);
      }
    }
    double[][] proj_args = Set.floatToDouble(new float[][]{
            projargs
    });
    addLatLonVariables(map_proj[0], proj_args[0], nr, nc);
    // Vis5DGridDefRecord gridDef = new Vis5DGridDefRecord(map_proj[0], proj_args[0], nr, nc);
    ncfile.addAttribute(null, new Attribute("Conventions", "CF-1.0"));
    ncfile.finish();
  }

  /**
   * Read the data for the variable
   *
   * @param v2      Variable to read
   * @param section section infomation
   * @return Array of data
   * @throws IOException           problem reading from file
   * @throws InvalidRangeException invalid Range
   */
  public Array readData(Variable v2, Section section) throws IOException, InvalidRangeException {
    // long startTime = System.currentTimeMillis();
    Integer varIdx = varTable.get(v2);
    if (varIdx == null) {
      throw new IOException("unable to find variable index");
    }

    int count = 0;
    int[] shape = v2.getShape();
    boolean haveZ = shape.length == 4;
    int nt = shape[count++];
    int nz = haveZ
            ? shape[count++]
            : 1;
    int ny = shape[count++];
    int nx = shape[count];
    count = 0;

    Range timeRange = section.getRange(count++);
    Range zRange = haveZ
            ? section.getRange(count++)
            : null;
    Range yRange = section.getRange(count++);
    Range xRange = section.getRange(count);
    int grid_size = nx * ny * nz;

    Array dataArray = Array.factory(DataType.FLOAT, section.getShape());
    IndexIterator ii = dataArray.getIndexIterator();

    // loop over time
    for (int timeIdx : timeRange) {

      float[] data = new float[grid_size];
      float[] ranges = new float[2];
      try {
        v5dstruct.v5d_read(timeIdx, varIdx, ranges, data);
      } catch (BadFormException bfe) {
        throw new IOException("Vis5DIosp.readData: "
                + bfe.getMessage());
      }

      if ((ranges[0] >= 0.99E30) && (ranges[1] <= -0.99E30)) {
        //range_sets[j] = new Linear1DSet(0.0, 1.0, 255);
      } else if (ranges[0] > ranges[1]) {
        throw new IOException("Vis5DIosp.readData: bad read "
                + v2.getFullName());
      }

      // invert the rows
      float[] tmp_data = new float[grid_size];

      if (zRange == null) {
        int cnt = 0;
        for (int mm = 0; mm < ny; mm++) {
          int start = (mm + 1) * nx - 1;
          for (int nn = 0; nn < nx; nn++) {
            tmp_data[cnt++] = data[start--];
          }
        }
      } else {
        int cnt = 0;
        for (int ll = 0; ll < nz; ll++) {
          for (int mm = 0; mm < ny; mm++) {
            int start = ((mm + 1) * nx - 1) + nx * ny * ll;
            for (int nn = 0; nn < nx; nn++) {
              tmp_data[cnt++] = data[start--];
            }
          }
        }
      }
      data = tmp_data;

      // copy the data into the array
      for (float aData : data) {
        ii.setFloatNext(aData);
      }
    }

    // long end = System.currentTimeMillis() - startTime;
    return dataArray;
  }

  /**
   * Close this IOSP
   *
   * @throws IOException problem closing file
   */
  public void close() throws IOException {
    if (v5dstruct != null) {
      v5dstruct = null;
    }
    super.close();
  }

  /**
   * Initialize the unit table.  This is used if there are no
   * units in the file.
   */
  private static void initUnitTable() {
    unitTable = new Hashtable<>();
    // temperatures
    unitTable.put("t", "K");
    unitTable.put("td", "K");
    unitTable.put("thte", "K");
    // winds
    unitTable.put("u", "m/s");
    unitTable.put("v", "m/s");
    unitTable.put("w", "m/s");
    // pressure
    unitTable.put("p", "hPa");
    unitTable.put("mmsl", "hPa");
    // moisture
    unitTable.put("rh", "%");
    // misc
    unitTable.put("rhfz", "%");
    unitTable.put("zagl", "m");
  }

  /**
   * Create a vertical dimension variable based on the info.  Based on
   * visad.data.vis5d.Vis5DVerticalSystem.
   *
   * @param vert_sys  the vertical system id
   * @param n_levels  the number of levels
   * @param vert_args the vertical system arguments
   * @return the vertical dimesion variable
   * @throws IOException problem reading the file or creating the data
   */
  private Variable makeVerticalVariable(int vert_sys, int n_levels,
                                        float[] vert_args)
          throws IOException {

    String vert_unit = null;
    String vert_type;
    ArrayFloat.D1 data = new ArrayFloat.D1(n_levels);
    AxisType axisType = null;

    switch (vert_sys) {

      case (0):
        vert_unit = null;
        vert_type = "height";
        break;

      case (1):
      case (2):
        vert_unit = "km";
        vert_type = "altitude";
        axisType = AxisType.Height;
        break;

      case (3):
        vert_unit = "mbar";
        vert_type = "pressure";
        axisType = AxisType.Pressure;
        break;

      default:
        throw new IOException("vert_sys unknown");
    }

    Variable vertVar = new Variable(ncfile, null, null, vert_type);
    vertVar.setDimensions(LEVEL);
    vertVar.setDataType(DataType.FLOAT);
    if (vert_unit != null) {
      vertVar.addAttribute(new Attribute(CDM.UNITS, vert_unit));
    }
    if (axisType != null) {
      vertVar.addAttribute(new Attribute(_Coordinate.AxisType,
              axisType.toString()));
    }

    switch (vert_sys) {

      case (0):
      case (1):
        for (int i = 0; i < n_levels; i++) {
          data.set(i, vert_args[0] + vert_args[1] * i);
        }
        break;

      case (2):  // Altitude in km - non-linear
        for (int i = 0; i < n_levels; i++) {
          data.set(i, vert_args[i]);
        }
        break;

      case (3):  // heights of pressure surfaces in km - non-linear
        try {
          Vis5DVerticalSystem.Vis5DVerticalCoordinateSystem vert_cs =
                  new Vis5DVerticalSystem.Vis5DVerticalCoordinateSystem();
          float[][] pressures = new float[1][n_levels];
          System.arraycopy(vert_args, 0, pressures[0], 0, n_levels);
          for (int i = 0; i < n_levels; i++) {
            pressures[0][i] *= 1000;  // km->m
          }
          pressures = vert_cs.fromReference(pressures);  // convert to pressures
          for (int i = 0; i < n_levels; i++) {
            data.set(i, pressures[0][i]);
          }
        } catch (VisADException ve) {
          throw new IOException("unable to make vertical system");
        }
        break;
    }
    vertVar.setCachedData(data, false);
    return vertVar;
  }

  /**
   * Add lat/lon variables to the file
   *
   * @param map_proj  the map projection identifier
   * @param proj_args the projection args
   * @param nr        number of rows
   * @param nc        number of columns
   * @throws IOException Problem making the projection
   */
  private void addLatLonVariables(int map_proj, double[] proj_args, int nr,
                                  int nc)
          throws IOException {
    //Vis5DGridDefRecord.printProjArgs(map_proj, proj_args);
    Vis5DGridDefRecord vgd = new Vis5DGridDefRecord(map_proj, proj_args, nr, nc);
    GridHorizCoordSys ghc = new GridHorizCoordSys(vgd, new Vis5DLookup(), null);

    Vis5DCoordinateSystem coord_sys;
    try {
      coord_sys = new Vis5DCoordinateSystem(map_proj, proj_args, nr,
              nc);

      Variable lat = new Variable(ncfile, null, null, LAT);
      lat.setDimensions(COLUMN + " " + ROW);
      lat.setDataType(DataType.DOUBLE);
      lat.addAttribute(new Attribute("long_name", "latitude"));
      lat.addAttribute(new Attribute(CDM.UNITS, CDM.LAT_UNITS));
      lat.addAttribute(new Attribute(CF.STANDARD_NAME, "latitude"));
      lat.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lat.toString()));
      ncfile.addVariable(null, lat);

      Variable lon = new Variable(ncfile, null, null, LON);
      lon.setDimensions(COLUMN + " " + ROW);
      lon.setDataType(DataType.DOUBLE);
      lon.addAttribute(new Attribute(CDM.UNITS, CDM.LON_UNITS));
      lon.addAttribute(new Attribute("long_name", "longitude"));
      lon.addAttribute(new Attribute(CF.STANDARD_NAME, "longitude"));
      lon.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lon.toString()));
      ncfile.addVariable(null, lon);

      int[] shape = new int[]{nc, nr};
      Array latArray = Array.factory(DataType.DOUBLE, shape);
      Array lonArray = Array.factory(DataType.DOUBLE, shape);
      double[][] rowcol = new double[2][nr * nc];
      for (int x = 0; x < nc; x++) {
        for (int y = 0; y < nr; y++) {
          int index = x * nr + y;
          rowcol[0][index] = y;
          rowcol[1][index] = x;
        }
      }
      double[][] latlon = coord_sys.toReference(rowcol);
      Index latIndex = latArray.getIndex();
      Index lonIndex = lonArray.getIndex();
            /*
            for (int y = 0; y < nr; y++) {
                for (int x = 0; x < nc; x++) {
                    int index = y * nc + x;
            */
      for (int x = 0; x < nc; x++) {
        for (int y = 0; y < nr; y++) {
          int index = x * nr + y;
                    /*
                    latArray.setDouble(latIndex.set(x, y), latlon[0][index]);
                    lonArray.setDouble(lonIndex.set(x, y), latlon[1][index]);
                    */
          latArray.setDouble(index, latlon[0][index]);
          lonArray.setDouble(index, latlon[1][index]);
        }
      }
      lat.setCachedData(latArray, false);
      lon.setCachedData(lonArray, false);
    } catch (VisADException ve) {
      throw new IOException("Vis5DIosp.addLatLon: " + ve.getMessage());
    }
  }

  /**
   * Get all the information about a Vis5D file
   */
  static public class Vis5DLookup implements GridTableLookup {

    /**
     * Gets a representative grid for this lookup
     */
    public Vis5DLookup() {
    }

    public String getShapeName(GridDefRecord gds) {
      return "Spherical";
    }

    /**
     * gets the grid type.
     */
    public final String getGridName(GridDefRecord gds) {
      return gds.toString();
    }

    /**
     * gets parameter table, then grib1 parameter based on number.
     *
     * @param gr GridRecord
     * @return Parameter
     */
    public final GridParameter getParameter(GridRecord gr) {
      // not needed for this implementation
      return null;
    }

    /**
     * gets the DisciplineName.
     */
    public final String getDisciplineName(GridRecord gr) {
      // all disciplines are the same in Vis5D
      return "Meteorological Products";
    }

    /**
     * gets the CategoryName.
     */
    public final String getCategoryName(GridRecord gr) {
      // no categories in Vis5D
      return "Meteorological Parameters";
    }

    /**
     * gets the LevelName.
     */
    public final String getLevelName(GridRecord gr) {
      // not needed for this implementation
      return null;
    }

    /**
     * gets the LevelDescription.
     */
    public final String getLevelDescription(GridRecord gr) {
      // not needed for this implementation
      return null;
    }

    /**
     * gets the LevelUnit.
     */
    public final String getLevelUnit(GridRecord gr) {
      // not needed for this implementation
      return null;
    }

    public final String getTimeRangeUnitName(int tunit) {
      return "second";
    }

    /**
     * gets the BaseTime Forecastime.
     *
     * @return BaseTime
     */
    public final java.util.Date getFirstBaseTime() {
      return new Date();
    }

    /**
     * is this a LatLon grid.
     */
    public final boolean isLatLon(GridDefRecord gds) {
      return getProjectionName(gds).equals("GENERIC")
              || getProjectionName(gds).equals("LINEAR")
              || getProjectionName(gds).equals("CYLINDRICAL")
              || getProjectionName(gds).equals("SPHERICAL");
    }

    /**
     * gets the ProjectionType.
     */
    public final int getProjectionType(GridDefRecord gds) {
      String name = getProjectionName(gds).trim();
      switch (name) {
        case "LAMBERT":
          return LambertConformal;
        case "STEREO":
          return PolarStereographic;
        default:
          return -1;
      }
    }

    /**
     * is this a VerticalCoordinate.
     */
    public final boolean isVerticalCoordinate(GridRecord gr) {
      // not needed for this implementation
      return false;
    }

    /**
     * is this a PositiveUp VerticalCoordinate.
     */
    public final boolean isPositiveUp(GridRecord gr) {
      // not needed for this implementation
      return false;
    }

    /**
     * gets the MissingValue.
     *
     * @return MissingValue
     */
    public final float getFirstMissingValue() {
      return -9999;
    }

    /**
     * Is this a layer?
     *
     * @param gr record to check
     * @return true if a layer
     */
    public boolean isLayer(GridRecord gr) {
      return false;
    }

    /**
     * Get the projection name
     *
     * @param gds the projection name
     * @return the name or null if not set
     */
    private String getProjectionName(GridDefRecord gds) {
      return gds.getParam(GridDefRecord.PROJ);
    }

    // CF Conventions Global Attributes

    /**
     * gets the CF title.
     *
     * @return title
     */
    public final String getTitle() {
      return "GRID data";
    }

    /**
     * Institution for CF conventions
     *
     * @return Institution
     */
    public String getInstitution() {
      return null;

    }

    /**
     * gets the Source, Generating Process or Model.
     *
     * @return source
     */
    public final String getSource() {
      return null;

    }

    /**
     * comment for CF conventions.
     *
     * @return comment
     */
    public final String getComment() {
      return null;
    }

    /**
     * Get the grid type for labelling
     *
     * @return the grid type
     */
    public String getGridType() {
      return "Vis5D";
    }

  }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.atd.dorade;

import ucar.nc2.constants.CDM;
import ucar.nc2.time.CalendarDateFormatter;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.util.Date;
import java.util.TimeZone;
import java.util.HashMap;

abstract class DoradeDescriptor {

  protected String descName;
  protected String expectedName;
  protected RandomAccessFile file;
  protected boolean littleEndianData;
  protected boolean verbose;

  protected static final TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");
  private static boolean defaultVerboseState = false;
  // map from descriptor names to per-class default verbose states
  private static HashMap<String, Boolean> classVerboseStates = new HashMap<>();

  static class DescriptorException extends Exception {
    protected DescriptorException(String message) {
      super(message);
    }

    protected DescriptorException(Throwable cause) {
      super(cause);
    }
  }

  /**
   * Read and set the descriptor name, size, and endianness, and return the
   * entire contents of the descriptor (including the name and size) as a
   * byte array.  The file position will be left at the beginning of the next
   * descriptor (or at the end of file).
   *
   * @param file             the DORADE sweepfile, positioned at the beginning of a
   *                         descriptor
   * @param littleEndianData set to true iff the file contains little-endian
   *                         data
   * @param expectedName     the expected name for the descriptor being read
   * @throws DescriptorException for file read errors, descriptor name
   *                             mismatch, etc.
   */
  protected byte[] readDescriptor(RandomAccessFile file,
                                  boolean littleEndianData,
                                  String expectedName)
          throws DescriptorException {

    this.file = file;
    this.littleEndianData = littleEndianData;
    this.expectedName = expectedName;
    verbose = getDefaultVerboseState(expectedName);

    byte[] data;

    try {
      //
      // find the next descriptor with our expected name
      //
      findNext(file);

      //
      // keep track of the start of this descriptor
      //
      long startpos = file.getFilePointer();

      //
      // get the name and descriptor size
      //
      byte[] header = new byte[8];
      file.readFully(header);
      descName = new String(header, 0, 4, CDM.utf8Charset);
      int size = grabInt(header, 4);

      //
      // now back up to the start of the descriptor and read the entire
      // thing into a byte array
      //
      file.seek(startpos);

      data = new byte[size];
      file.readFully(data);
    } catch (java.io.IOException ex) {
      throw new DescriptorException(ex);
    }

    //
    // now check the name we got against the expected name
    //
    if (!descName.equals(expectedName))
      throw new DescriptorException("Got descriptor name '" + descName +
              "' when expecting name '" +
              expectedName + "'");

    return data;
  }

  /**
   * Skip the current DORADE descriptor in the file, leaving the file position
   * at the beginning of the next descriptor (or at the end of file).
   *
   * @param file the DORADE sweepfile, positioned at the beginning of a
   *             descriptor
   * @throws java.io.IOException
   */
  protected static void skipDescriptor(RandomAccessFile file,
                                       boolean littleEndianData)
          throws DescriptorException, java.io.IOException {
    try {
      file.readFully(new byte[4]); // skip name
      byte[] lenBytes = new byte[4];
      file.readFully(lenBytes);
      int descLen = grabInt(lenBytes, 0, littleEndianData);
      file.readFully(new byte[descLen - 8]);
    } catch (java.io.EOFException eofex) {
      return; // just leave the file at EOF
    } catch (Exception ex) {
      throw new DescriptorException(ex);
    }
  }

  /**
   * Return the name of the DORADE descriptor at the current location
   * in the file.  The current location will not be changed.
   *
   * @param file the DORADE sweep file, positioned at the beginning of a
   *             descriptor
   * @return the name of the DORADE descriptor starting at the current
   * file position, or null if no descriptor name is available
   * @throws DescriptorException
   */
  protected static String peekName(RandomAccessFile file)
          throws DescriptorException {
    try {
      long filepos = file.getFilePointer();
      byte[] nameBytes = new byte[4];
      if (file.read(nameBytes) == -1)
        return null;  // EOF
      file.seek(filepos);
      return new String(nameBytes, CDM.utf8Charset);

    } catch (IOException ex) {
      throw new DescriptorException(ex);
    }
  }

  /**
   * Determine if the given DORADE sweepfile contains little-endian data
   * (in violation of the DORADE definition...).
   *
   * @param file the DORADE sweepfile,
   * @return true iff the file contains little-endian data
   * @throws DescriptorException
   */
  public static boolean sweepfileIsLittleEndian(RandomAccessFile file)
          throws DescriptorException {
    int descLen;
    try {
      file.seek(0);
      //
      // skip the 4-byte descriptor name
      //
      byte[] bytes = new byte[4];
      file.readFully(bytes);
      //
      // get the descriptor length
      //
      descLen = file.readInt();
      file.seek(0);
    } catch (Exception ex) {
      throw new DescriptorException(ex);
    }
    return (descLen < 0 || descLen > 0xffffff);
  }

  /**
   * Unpack a two-byte integer from the given byte array.
   *
   * @param bytes  byte array to be read
   * @param offset number of bytes to skip in the byte array before reading
   * @return the unpacked short value
   */
  protected short grabShort(byte[] bytes, int offset) {
    int ndx0 = offset + (littleEndianData ? 1 : 0);
    int ndx1 = offset + (littleEndianData ? 0 : 1);
    // careful that we only allow sign extension on the highest order byte
    return (short) (bytes[ndx0] << 8 | (bytes[ndx1] & 0xff));
  }

  /**
   * Unpack a four-byte integer from the given byte array.
   *
   * @param bytes            byte array to be read
   * @param offset           number of bytes to skip in the byte array before reading
   * @param littleEndianData true iff the byte array contains little-endian
   *                         data
   * @return the unpacked integer value
   */
  protected static int grabInt(byte[] bytes, int offset,
                               boolean littleEndianData) {
    int ndx0 = offset + (littleEndianData ? 3 : 0);
    int ndx1 = offset + (littleEndianData ? 2 : 1);
    int ndx2 = offset + (littleEndianData ? 1 : 2);
    int ndx3 = offset + (littleEndianData ? 0 : 3);

    // careful that we only allow sign extension on the highest order byte
    return (bytes[ndx0] << 24 |
            (bytes[ndx1] & 0xff) << 16 |
            (bytes[ndx2] & 0xff) << 8 |
            (bytes[ndx3] & 0xff));
  }

  /**
   * Unpack a four-byte integer from the given byte array.
   *
   * @param bytes  byte array to be read
   * @param offset number of bytes to skip in the byte array before reading
   * @return the unpacked integer value
   */
  protected int grabInt(byte[] bytes, int offset) {
    return grabInt(bytes, offset, littleEndianData);
  }

  /**
   * Unpack a four-byte IEEE float from the given byte array.
   *
   * @param bytes  byte array to be read
   * @param offset number of bytes to skip in the byte array before reading
   * @return the unpacked float value
   */
  protected float grabFloat(byte[] bytes, int offset)
          throws DescriptorException {
    try {
      byte[] src;
      if (littleEndianData) {
        src = new byte[4];
        src[0] = bytes[offset + 3];
        src[1] = bytes[offset + 2];
        src[2] = bytes[offset + 1];
        src[3] = bytes[offset];
        offset = 0;
      } else {
        src = bytes;
      }
      DataInputStream stream =
              new DataInputStream(new ByteArrayInputStream(src, offset, 4));
      return stream.readFloat();
    } catch (Exception ex) {
      throw new DescriptorException(ex);
    }
  }

  /**
   * Unpack an eight-byte IEEE float from the given byte array.
   *
   * @param bytes  byte array to be read
   * @param offset number of bytes to skip in the byte array before reading
   * @return the unpacked double value
   */
  protected double grabDouble(byte[] bytes, int offset)
          throws DescriptorException {
    try {
      byte[] src;
      if (littleEndianData) {
        src = new byte[8];
        src[0] = bytes[offset + 7];
        src[1] = bytes[offset + 6];
        src[2] = bytes[offset + 5];
        src[3] = bytes[offset + 4];
        src[4] = bytes[offset + 3];
        src[5] = bytes[offset + 2];
        src[6] = bytes[offset + 1];
        src[7] = bytes[offset];
        offset = 0;
      } else {
        src = bytes;
      }
      DataInputStream stream =
              new DataInputStream(new ByteArrayInputStream(src, offset, 8));
      return stream.readDouble();
    } catch (Exception ex) {
      throw new DescriptorException(ex);
    }
  }

  protected static long findNextWithName(String expectedName,
                                         RandomAccessFile file,
                                         boolean littleEndianData)
          throws DescriptorException, java.io.IOException {

    //
    // Skip forward through the file until we find a descriptor with
    // the expected name
    //
    String descName;
    while ((descName = peekName(file)) != null) {
      if (descName.equals(expectedName)) {
        try {
          return file.getFilePointer();
        } catch (java.io.IOException ex) {
          throw new DescriptorException(ex);
        }
      }
      skipDescriptor(file, littleEndianData);
    }
    throw new DescriptorException("Expected " + expectedName +
            " descriptor not found!");
  }

  protected long findNext(RandomAccessFile file)
          throws DescriptorException, java.io.IOException {
    return findNextWithName(expectedName, file, littleEndianData);
  }

  /**
   * Return a string with a reasonable and complete representation of the
   * given Date, shown in UTC.
   *
   * @param date Date to be represented
   * @return a string containing the representation of the date
   */
  public static String formatDate(Date date) {
    return CalendarDateFormatter.toDateTimeString(date);
  }

  /**
   * Get the default verbose state for new DoradeDescriptor-s.
   */
  public static boolean getDefaultVerboseState() {
    return defaultVerboseState;
  }

  /**
   * Set the default verbose state for new DoradeDescriptor-s.
   *
   * @param verbose the new default verbose state
   */
  public static void setDefaultVerboseState(boolean verbose) {
    defaultVerboseState = verbose;
    classVerboseStates.clear();
  }

  /**
   * Get the default verbose state for new DoradeDescriptor-s
   * of the given name.
   *
   * @param descriptorName the descriptor name for which the new default
   *                       verbose state will apply
   */
  public static boolean getDefaultVerboseState(String descriptorName) {
    Boolean classVerboseState = classVerboseStates.get(descriptorName.toUpperCase());
    if (classVerboseState != null)
      return classVerboseState;
    else
      return defaultVerboseState;
  }

  /**
   * Set the default verbose state for new DoradeDescriptor-s
   * of the given name.
   *
   * @param descriptorName the descriptor name for which the new default
   *                       verbose state will apply
   * @param verbose        the new default verbose state
   */
  public static void setDefaultVerboseState(String descriptorName,
                                            boolean verbose) {
    classVerboseStates.put(descriptorName.toUpperCase(), verbose);
  }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.units;

/**
 * Provides support for a database of standard units.
 *
 * @author Steven R. Emmerson
 */
public final class StandardUnitDB extends UnitDBImpl {
  private static final long serialVersionUID = 1L;

  /**
   * The singleton instance of this class.
   *
   * @serial
   */
  private static/* final */ StandardUnitDB instance = null;

  /**
   * The unit formatter for parsing unit string specifications.
   *
   * @serial
   */
  private static UnitFormat format;

  /**
   * Constructs from nothing.
   *
   * @throws UnitParseException     A string specification couldn't be parsed.
   * @throws SpecificationException Bad string specification.
   * @throws UnitDBException        Problem with the unit database.
   * @throws PrefixDBException      Problem with the prefix database.
   * @throws NameException          Bad unit name.
   * @throws OperationException     Improper unit operation.
   * @throws UnitSystemException    Problem with UnitSystem.
   */
  private StandardUnitDB() throws UnitParseException, SpecificationException,
          UnitDBException, PrefixDBException, NameException,
          OperationException, UnitSystemException {
    super(50, 50);

		/*
     * SI units:
		 */
    add((UnitDBImpl) SI.instance().getUnitDB());

		/*
		 * Unit specification strings in the following rely on standard unit
		 * syntax. Additionally, unit specification strings may not reference
		 * units that have not yet been defined (i.e. forward referencing is not
		 * allowed).
		 */
    format = StandardUnitFormat.instance();

		/*
		 * Constants:
		 */
    au("PI", Double.toString(Math.PI));
    au("percent", "0.01", "%");
    au("bakersdozen", "13");
    au("pair", "2");
    au("dozen", "12");
    au("ten", "10");
    au("score", "20");
    au("hundred", "100");
    au("thousand", "1000");
    au("million", "1e6");

		/*
		 * Dimensionless derived unit:
		 */
    addUnit(DerivedUnitImpl.DIMENSIONLESS);

		/*
		 * Dimensionless derived units (avoid! Use "1e-6 kg/kg" instead):
		 */
    au("ppt", "1e-3");
    au("pptv", "1e-3");
    au("pptm", "1e-3");
    au("pptn", "1e-3");
    au("ppm", "1e-6");
    au("ppmv", "1e-6");
    au("ppmm", "1e-6");
    au("ppmn", "1e-6");
    au("ppb", "1e-9");
    au("ppbv", "1e-9");
    au("ppbm", "1e-9");
    au("ppbn", "1e-9");

		/*
		 * UNITS OF ELECTRIC CURRENT
		 */
    au("abampere", "10 ampere"); // exact
    au("gilbert", "7.957747e-1 ampere");
    au("statampere", "3.335640e-10 ampere");

    aa("amp", "ampere");
    aa("biot", "abampere");

		/*
		 * UNITS OF LUMINOUS INTENSITY
		 */
    aa("candle", "candela");

		/*
		 * UNITS OF THERMODYNAMIC TEMPERATURE
		 */
    au("rankine", "K/1.8");
    au("fahrenheit", "rankine @ 459.67");

    aa("degree Kelvin", "kelvin", "degrees Kelvin");
    aa("degreeK", "kelvin", "degreesK");
    aa("degree K", "kelvin", "degrees K");

    aa("Celsius", "degree_Celsius");
    aa("degreeC", "degree_Celsius", "degreesC");
    aa("degree C", "degree_Celsius", "degrees C");
    aa("degree centigrade", "degree_Celsius", "degrees centigrade");
    aa("centigrade", "degree_Celsius");

    aa("degree Rankine", "rankine", "degrees Rankine");
    aa("degreeR", "rankine", "degreesR");
    aa("degree R", "rankine", "degrees R");

    aa("degree Fahrenheit", "fahrenheit", "degrees Fahrenheit");
    aa("degreeF", "fahrenheit", "degreesF");
    aa("degree F", "fahrenheit", "degrees F");

    as("degK", "kelvin");
    as("deg K", "kelvin");
    as("degC", "degree_Celsius");
    as("deg C", "degree_Celsius");
    as("degR", "rankine");
    as("deg R", "rankine");
    as("degF", "fahrenheit");
    as("deg F", "fahrenheit");

		/*
		 * UNITS OF MASS
		 */
    au("assay ton", "2.916667e-2 kg");
    au("avoirdupois ounce", "2.834952e-2 kg"); // "oz" ambiguous
    au("avoirdupois pound", "4.5359237e-1 kg", "lb"); // exact
    au("carat", "2e-4 kg");
    au("grain", "6.479891e-5 kg", "gr"); // exact
    au("gram", "1e-3 kg", "g"); // exact
    au("long hundredweight", "5.080235e1 kg");
    au("pennyweight", "1.555174e-3 kg");
    au("short hundredweight", "4.535924e1 kg");
    au("slug", "14.59390 kg");
    au("troy ounce", "3.110348e-2 kg"); // "oz" ambiguous
    au("troy pound", "3.732417e-1 kg");
    au("unified atomic mass unit", "1.66054e-27 kg");
    au("scruple", "20 gr");
    au("apdram", "60 gr");
    au("apounce", "480 gr");
    au("appound", "5760 gr");

    aa("atomic mass unit", "unified_atomic_mass_unit");
    aa("amu", "unified_atomic_mass_unit");
    aa("metricton", "metric_ton");
    aa("apothecary ounce", "troy_ounce");
    aa("apothecary pound", "troy_pound");
    aa("pound", "avoirdupois_pound");
    aa("atomicmassunit", "unified_atomic_mass_unit");

    au("bag", "94 lb");
    au("short ton", "2000 lb");
    au("long ton", "2240 lb");

    aa("ton", "short_ton");
    aa("shortton", "short_ton");
    aa("longton", "long_ton");

		/*
		 * UNITS OF LENGTH
		 */
    au("astronomical unit", "1.495979e11 m", "AU");
    au("fermi", "femtometer"); // definition
    au("light year", "9.46073e15 m");
    au("micron", "micrometer"); // definition
    au("mil", "2.54e-5 m"); // exact
    au("parsec", "3.085678e16 m", "prs");
    au("printers point", "3.514598e-4 m");

		/*
		 * God help us! There's an international foot and a US survey foot and
		 * they're not the same!
		 */

    // US Survey foot stuff:
    au("US survey foot", "1200/3937 m", null, "US survey feet");
    au("US survey yard", "3 US_survey_feet"); // exact
    au("US survey mile", "5280 US_survey_feet"); // exact
    aa("US statute mile", "US_survey_mile");
    au("rod", "16.5 US_survey_feet"); // exact
    au("perch", "5.02921005842012 m");
    au("furlong", "660 US_survey_feet"); // exact
    au("fathom", "6 US_survey_feet"); // exact

    aa("pole", "rod");

    // International foot stuff:
    au("international inch", ".0254 m", null, "international inches"); // exact
    au("international foot", "12 international_inches", null,
            "international feet"); // exact
    au("international yard", "3 international_feet"); // exact
    au("international mile", "5280 international_feet"); // exact

    // Alias unspecified units to the international units:
    aa("inch", "international_inch", null, "in");
    aa("foot", "international_foot", "feet", "ft");
    aa("yard", "international_yard", null, "yd");
    aa("mile", "international_mile");

    au("chain", "2.011684e1 m");

    au("printers pica", "12 printers_point"); // exact

    aa("astronomicalunit", "astronomical_unit");
    aa("asu", "astronomical_unit");
    aa("nmile", "nautical_mile");

    aa("pica", "printers_pica");

    au("big point", "in/72"); // exact
    au("barleycorn", "in/3");

    au("arpentlin", "191.835 ft");

        /*
         * UNITS OF SUBSTANCE
         */
        au("avogadro_constant", "6.02214179e23/mol");
        au("molecule", "1/avogadro_constant");
        au("molec", "molecule");
        au("nucleon", "molecule");
        au("nuc", "molecule");

		/*
		 * UNITS OF TIME
		 */
    aa("sec", "second"); // avoid
    au("shake", "1e-8 s"); // exact
    au("sidereal day", "8.616409e4 s");
    au("sidereal hour", "3.590170e3 s");
    au("sidereal minute", "5.983617e1 s");
    au("sidereal second", "0.9972696 s");
    au("sidereal year", "3.155815e7 s");
		/*
		 * Interval between 2 successive passages of sun through vernal equinox
		 * (365.242198781 days -- see
		 * http://www.ast.cam.ac.uk/pubinfo/leaflets/,
		 * http://aa.usno.navy.mil/AA/ and
		 * http://adswww.colorado.edu/adswww/astro_coord.html):
		 */
    au("tropical year", "3.15569259747e7 s");
    au("lunar month", "29.530589 d");

    au("common year", "365 d");// exact: 153600e7 seconds
    au("leap year", "366 d");
    au("Julian year", "365.25 d");
    au("Gregorian year", "365.2425 d");
    au("sidereal month", "27.321661 d");
    au("tropical month", "27.321582 d");
    as("hr", "hour");
    au("fortnight", "14 d");
    au("week", "7 d");
    au("jiffy", "cs");
    aa("year", "tropical_year", null, "yr");
    au("eon", "Gyr"); // fuzzy
    au("month", "yr/12"); // on average
    aa("anno", "year", null, "ann");

		/*
		 * UNITS OF PLANE ANGLE
		 */
    au("circle", "360 deg");
    au("grade", "0.9 deg"); // exact

    aa("turn", "circle");
    aa("revolution", "circle", null, "r");
    aa("degree", "arc_degree");
    aa("degree north", "arc_degree", "degrees north");
    aa("degree east", "arc_degree", "degrees east");
    aa("degree true", "arc_degree", "degrees true");
    aa("angular degree", "arc_degree");
    aa("angular minute", "arc_minute");
    aa("angular second", "arc_second");
    aa("gon", "grade");

    aa("arcdegree", "arc_degree");
    aa("arcminute", "arc_minute");
    aa("arcsecond", "arc_second");

    aa("arcdeg", "arc_degree");
    aa("arcmin", "arc_minute");
    as("mnt", "arc_minute");
    aa("arcsec", "arc_second");

    aa("degree N", "degree_north", "degrees N");

    aa("degreeE", "degree_east", "degreesE");
    aa("degree E", "degree_east", "degrees E");

    au("degree west", "-1 degree_east", null, "degrees west");

    aa("degreeW", "degree_west", "degreesW");
    aa("degree W", "degree_west", "degrees W");
    aa("degreeT", "degree_true", "degreesT");
    aa("degree T", "degree_true", "degrees T");

		/*
		 * The following are derived units with special names. They are useful
		 * for defining other derived units.
		 */
    au("sphere", "4 PI sr");
    au("standard free fall", "9.806650 m/s2");

    aa("force", "standard_free_fall");
    aa("gravity", "standard_free_fall");
    aa("free fall", "standard_free_fall");

    au("mercury 0C", "13595.1 gravity kg/m3");
    au("mercury 60F", "13556.8 gravity kg/m3");
    au("conventional water", "1000 gravity kg/m3"); // exact
    au("water 4C", "999.972 gravity kg/m3");
    au("water 60F", "999.001 gravity kg/m3");

    aa("conventional mercury", "mercury_0C");

    aa("mercury0C", "mercury_0C");
    aa("mercury 32F", "mercury_0C");
    aa("water4C", "water_4C");
    aa("water 39F", "water_4C"); // actually 39.2 degF
    aa("mercury", "conventional_mercury");
    aa("water", "conventional_water");

    as("Hg", "mercury");
    as("H2O", "water");

		/*
		 * AREA
		 */
    au("circular mil", "5.067075e-10 m2");
    au("darcy", "9.869233e-13 m2");
    // porous solid permeability
    aa("ha", "hectare");
    au("acre", "160 rod2"); // exact

		/*
		 * ELECTRICITY AND MAGNETISM
		 */
    au("abfarad", "GF");
    au("abhenry", "nH"); // exact
    au("abmho", "GS"); // exact
    au("abohm", "nOhm"); // exact
    au("megohm", "MOhm"); // exact
    au("kilohm", "kOhm"); // exact
    au("abvolt", "1e-8 V"); // exact
    au("e", "1.60217733e-19 C");
    au("chemical faraday", "9.64957e4 C");
    au("physical faraday", "9.65219e4 C");
    au("C12 faraday", "9.648531e4 C");
    au("gamma", "nT"); // exact
    au("gauss", "1e-4 T"); // exact
    au("maxwell", "1e-8 Wb"); // exact
    au("oersted", "7.957747e1 A/m", "Oe");
    au("statcoulomb", "3.335640e-10 C");
    au("statfarad", "1.112650e-12 F");
    au("stathenry", "8.987554e11 H");
    au("statmho", "1.112650e-12 S");
    au("statohm", "8.987554e11 Ohm");
    au("statvolt", "2.997925e2 V");
    au("unit pole", "1.256637e-7 Wb");
    au("mho", "S");
    aa("faraday", "C12 faraday");
    // charge of 1 mole of electrons

		/*
		 * ENERGY (INCLUDES WORK)
		 */
    au("electronvolt", "1.602177e-19 J");
    au("erg", "1e-7 J"); // exact
    au("IT Btu", "1.05505585262e3 J");
    au("EC therm", "1.05506e8 J"); // exact
    au("thermochemical calorie", "4.184000 J"); // exact
    au("IT calorie", "4.1868 J"); // exact
    au("ton TNT", "4.184e9 J");
    au("US therm", "1.054804e8 J"); // exact
    au("watthour", "W.h");

    aa("therm", "US_therm");
    as("Wh", "watthour");
    aa("Btu", "IT_Btu");
    aa("calorie", "IT_calorie");
    aa("electron volt", "electronvolt");

    au("thm", "therm");
    au("cal", "calorie");
    as("eV", "electronvolt");
    au("bev", "gigaelectronvolt");

		/*
		 * FORCE
		 */
    au("dyne", "1e-5 N"); // exact
    au("pond", "9.806650e-3 N"); // exact
    au("force kilogram", "9.806650 N", "kgf"); // exact
    au("force ounce", "2.780139e-1 N", "ozf");
    au("force pound", "4.4482216152605 N", "lbf"); // exact
    au("poundal", "1.382550e-1 N");
    au("gf", "gram force");

    au("force gram", "milliforce_kilogram");
    au("force ton", "2000 force_pound"); // exact
    aa("ounce force", "force_ounce");
    aa("kilogram force", "force_kilogram");
    aa("pound force", "force_pound");

    au("kip", "kilolbf");
    aa("ton force", "force_ton");
    au("gram force", "force_gram");

		/*
		 * HEAT
		 */
    au("clo", "1.55e-1 K.m2.W-1");

		/*
		 * LIGHT
		 */
    au("footcandle", "1.076391e-1 lx");
    au("footlambert", "3.426259 cd/m2");
    au("lambert", "1e4/PI cd/m2"); // exact
    au("stilb", "1e4 cd/m2", "sb");
    au("phot", "1e4 lm/m2", "ph"); // exact
    au("nit", "cd.m2", "nt"); // exact
    au("langley", "4.184000e4 J/m2"); // exact
    au("blondel", "1/PI cd/m2");

    aa("apostilb", "blondel");

		/*
		 * MASS PER UNIT LENGTH
		 */
    au("denier", "1.111111e-7 kg/m");
    au("tex", "1e-6 kg/m");

		/*
		 * MASS PER UNIT TIME (INCLUDES FLOW)
		 */
    au("perm 0C", "5.72135e-11 kg/(Pa.s.m2)");
    au("perm 23C", "5.74525e-11 kg/(Pa.s.m2)");

		/*
		 * POWER
		 */
    au("voltampere", "V.A", "VA");
    au("boiler horsepower", "9.80950e3 W");
    au("shaft horsepower", "7.456999e2 W");
    au("metric horsepower", "7.35499 W");
    au("electric horsepower", "7.460000e2 W"); // exact
    au("water horsepower", "7.46043e2 W");
    au("UK horsepower", "7.4570e2 W");
    au("refrigeration ton", "12000 Btu/h");

    aa("horsepower", "shaft_horsepower", null, "hp");
    aa("ton of refrigeration", "refrigeration_ton");

		/*
		 * PRESSURE OR STRESS
		 */
    au("standard atmosphere", "1.01325e5 Pa", "atm"); // exact
    au("technical atmosphere", "kg.(0.01 gravity/m)2", "at");
    au("inch H2O 39F", "inch.water_39F");
    au("inch H2O 60F", "inch.water_60F");
    au("inch Hg 32F", "inch.mercury_32F");
    au("inch Hg 60F", "inch.mercury_60F");
    au("millimeter Hg 0C", "mm.mercury_0C");
    au("footH2O", "foot.water");
    au("cmHg", "cm.Hg");
    au("cmH2O", "cm.water");
    as("pal", "pascal");
    au("inch Hg", "inch.Hg");
    aa("inch hg", "inch_Hg");
    aa("inHg", "inch_Hg");
    aa("in Hg", "inch_Hg");
    au("millimeter Hg", "mm.Hg");
    aa("mmHg", "millimeter_Hg");
    aa("mm Hg", "millimeter_Hg");
    aa("torr", "millimeter_Hg");
    au("foot H2O", "foot.water");
    aa("ftH2O", "foot_H2O");
    au("psi", "pound.gravity/inch2");
    au("ksi", "kip/inch2");
    au("barie", "0.1 N/m2");

    aa("atmosphere", "standard_atmosphere");
    aa("barye", "barie");

		/*
		 * RADIATION UNITS
		 */
    aa("sie", "sievert");

		/*
		 * VELOCITY (INCLUDES SPEED)
		 */
    aa("knot international", "knot");
    aa("international knot", "knot");

		/*
		 * VISCOSITY
		 */
    au("poise", "1e-1 Pa.s", "P"); // exact
    au("stokes", "1e-4 m2/s", "St"); // exact
    au("rhe", "10 (Pa.s)-1");

		/*
		 * VOLUME (INCLUDES CAPACITY)
		 */
    au("acre foot", "1.233489e3 m3");
    // but `acre foot' is 1.2334867714897e3 meters^3. Odd.
    au("board foot", "2.359737e-3 m3");
    au("bushel", "3.523907e-2 m3", "bu");
    au("UK liquid gallon", "4.546090e-3 m3"); // exact
    au("Canadian liquid gallon", "4.546090e-3 m3"); // exact
    au("US dry gallon", "4.404884e-3 m3");
    au("US liquid gallon", "3.785412e-3 m3");
    au("cc", "1e-6 m3");
    au("stere", "m3"); // exact
    au("register ton", "2.831685 m3");

    au("US dry quart", "1/4 US_dry_gallon");
    au("US dry pint", "1/8 US_dry_gallon");

    au("US liquid quart", "1/4 US_liquid_gallon");
    au("US liquid pint", "1/8 US_liquid_gallon");
    au("US liquid cup", "1/16 US_liquid_gallon");
    au("US liquid gill", "1/32 US_liquid_gallon");
    au("US fluid ounce", "1/128 US_liquid_gallon");
    aa("US liquid ounce", "US_fluid_ounce");

    au("UK liquid quart", "1/4 UK_liquid_gallon");
    au("UK liquid pint", "1/8 UK_liquid_gallon");
    au("UK liquid cup", "1/16 UK_liquid_gallon");
    au("UK liquid gill", "1/32 UK_liquid_gallon");
    au("UK fluid ounce", "1/160 UK_liquid_gallon");
    aa("UK liquid ounce", "UK_fluid_ounce");

    aa("liquid gallon", "US_liquid_gallon");
    aa("fluid ounce", "US_fluid_ounce", null, "fl_oz");

    aa("dry quart", "US_dry_quart");
    aa("dry pint", "US_dry_pint");

    au("liquid quart", "1/4 liquid_gallon");
    au("liquid pint", "1/8 liquid_gallon");

    aa("gallon", "liquid_gallon");
    au("barrel", "42 US_liquid_gallon", "bbl");
    // petroleum industry definition
    aa("quart", "liquid_quart");
    aa("pint", "liquid_pint", null, "pt");
    au("cup", "1/16 liquid_gallon");
    au("gill", "1/32 liquid_gallon");
    au("tablespoon", "0.5 US_fluid_ounce", "Tbl");
    au("teaspoon", "1/3 tablespoon", "tsp");
    au("peck", "1/4 bushel", "pk");

    as("floz", "fluid_ounce");
    aa("acre_feet", "acre_foot");
    aa("board_feet", "board_foot");
    aa("tbsp", "tablespoon");
    aa("tblsp", "tablespoon");

    au("fldr", "1/8 floz");
    au("dram", "1/16 floz", "dr");

    au("firkin", "1/4 barrel");
    // exact but "barrel" is vague

		/*
		 * VOLUME PER UNIT TIME (includes FLOW)
		 */
    au("sverdrup", "1e6 m3/s"); // oceanographic flow

		/*
		 * COMPUTERS AND COMMUNICATION
		 */
    au("bit", "1"); // unit of information
    au("baud", "s-1", "Bd");
    au("bps", "s-1");
    au("cps", "s-1");

		/*
		 * MISC
		 */
    au("count", "1");
    au("kayser", "100 m-1"); // exact
    au("rps", "r/s");
    au("rpm", "1/60 rps");
    au("work_year", "2056 h");
    au("work_month", "1/12 work_year");
    aa("geopotential", "gravity", null, "gp");
    aa("dynamic", "geopotential");

    format = null; // allow garbage collection
  }

  /**
   * Gets an instance of this database.
   *
   * @return An instance of this database.
   * @throws UnitDBException The instance couldn't be created.
   */
  public static synchronized StandardUnitDB instance() throws UnitDBException {
    if (instance == null) {
      try {
        instance = new StandardUnitDB();
      } catch (Exception e) {
        throw new UnitDBException("Couldn't create standard unit-database", e);
      }
    }
    return instance;
  }

  /**
   * Adds a derived unit to the database.
   *
   * @param name       The name of the unit.
   * @param definition The definition for the unit.
   * @throws UnitExistsException    Attempt to redefine an existing unit.
   * @throws NameException          Bad unit name.
   * @throws UnitParseException     A string specification couldn't be parsed.
   * @throws SpecificationException Bad string specification.
   * @throws UnitDBException        Problem with the unit database.
   * @throws PrefixDBException      Problem with the prefix database.
   * @throws NameException          Bad unit name.
   * @throws OperationException     Improper unit operation.
   * @throws UnitSystemException    Problem with UnitSystem.
   */
  private void au(final String name, final String definition)
          throws UnitExistsException, NoSuchUnitException,
          UnitParseException, SpecificationException, UnitDBException,
          PrefixDBException, OperationException, NameException,
          UnitSystemException {
    au(name, definition, null);
  }

  /**
   * Adds a derived unit to the database.
   *
   * @param name       The name of the unit.
   * @param definition The definition for the unit.
   * @param symbol     The symbol for the unit.
   * @throws UnitExistsException    Attempt to redefine an existing unit.
   * @throws NameException          Bad unit name.
   * @throws UnitParseException     A string specification couldn't be parsed.
   * @throws SpecificationException Bad string specification.
   * @throws UnitDBException        Problem with the unit database.
   * @throws PrefixDBException      Problem with the prefix database.
   * @throws NameException          Bad unit name.
   * @throws OperationException     Improper unit operation.
   * @throws UnitSystemException    Problem with UnitSystem.
   */
  private void au(final String name, final String definition,
                  final String symbol) throws UnitExistsException,
          NoSuchUnitException, UnitParseException, SpecificationException,
          UnitDBException, PrefixDBException, OperationException,
          NameException, UnitSystemException {
    au(name, definition, symbol, (String) null);
  }

  /**
   * Adds a derived unit to the database.
   *
   * @param name       The name of the unit.
   * @param definition The definition for the unit.
   * @param symbol     The symbol for the unit.
   * @param plural     The plural form of the name of the unit.
   * @throws UnitExistsException    Attempt to redefine an existing unit.
   * @throws NameException          Bad unit name.
   * @throws UnitParseException     A string specification couldn't be parsed.
   * @throws SpecificationException Bad string specification.
   * @throws UnitDBException        Problem with the unit database.
   * @throws PrefixDBException      Problem with the prefix database.
   * @throws NameException          Bad unit name.
   * @throws OperationException     Improper unit operation.
   * @throws UnitSystemException    Problem with UnitSystem.
   */
  private void au(final String name, final String definition,
                  final String symbol, final String plural)
          throws UnitExistsException, NoSuchUnitException,
          UnitParseException, SpecificationException, UnitDBException,
          PrefixDBException, OperationException, NameException,
          UnitSystemException {
    final Unit unit = format.parse(definition, this);
    if (unit == null) {
      throw new NoSuchUnitException(definition);
    }
    addUnit(unit.clone(UnitName.newUnitName(name, plural, symbol)));
  }

  /**
   * Adds an alias for a unit to the database.
   *
   * @param alias The alias for the unit.
   * @param name  The name of the unit.
   * @throws UnitExistsException    Attempt to redefine an existing unit.
   * @throws NameException          Bad unit name.
   * @throws UnitParseException     A string specification couldn't be parsed.
   * @throws SpecificationException Bad string specification.
   * @throws UnitDBException        Problem with the unit database.
   * @throws PrefixDBException      Problem with the prefix database.
   * @throws NameException          Bad unit name.
   * @throws OperationException     Improper unit operation.
   * @throws UnitSystemException    Problem with UnitSystem.
   */
  private void aa(final String alias, final String name)
          throws UnitExistsException, NoSuchUnitException,
          UnitParseException, SpecificationException, UnitDBException,
          PrefixDBException, OperationException, NameException,
          UnitSystemException {
    aa(alias, name, null);
  }

  /**
   * Adds an alias for a unit to the database.
   *
   * @param alias  The alias for the unit.
   * @param name   The name of the unit.
   * @param plural The plural form of the alias for the unit.
   * @throws UnitExistsException    Attempt to redefine an existing unit.
   * @throws NameException          Bad unit name.
   * @throws UnitParseException     A string specification couldn't be parsed.
   * @throws SpecificationException Bad string specification.
   * @throws UnitDBException        Problem with the unit database.
   * @throws PrefixDBException      Problem with the prefix database.
   * @throws NameException          Bad unit name.
   * @throws OperationException     Improper unit operation.
   * @throws UnitSystemException    Problem with UnitSystem.
   */
  private void aa(final String alias, final String name, final String plural)
          throws UnitExistsException, NoSuchUnitException,
          UnitParseException, SpecificationException, UnitDBException,
          PrefixDBException, OperationException, NameException,
          UnitSystemException {
    aa(alias, name, plural, null);
  }

  /**
   * Adds an alias for a unit to the database.
   *
   * @param alias  The alias for the unit.
   * @param name   The name of the unit.
   * @param plural The plural form of the alias for the unit.
   * @param symbol The symbol for the alias.
   * @throws UnitExistsException    Attempt to redefine an existing unit.
   * @throws NameException          Bad unit name.
   * @throws UnitParseException     A string specification couldn't be parsed.
   * @throws SpecificationException Bad string specification.
   * @throws UnitDBException        Problem with the unit database.
   * @throws PrefixDBException      Problem with the prefix database.
   * @throws NameException          Bad unit name.
   * @throws OperationException     Improper unit operation.
   * @throws UnitSystemException    Problem with UnitSystem.
   */
  private void aa(final String alias, final String name, final String plural,
                  final String symbol) throws UnitExistsException,
          NoSuchUnitException, UnitParseException, SpecificationException,
          UnitDBException, PrefixDBException, OperationException,
          NameException, UnitSystemException {
    addAlias(alias, name, symbol, plural);
  }

  /**
   * Adds a symbol for a unit to the database.
   *
   * @param symbol The symbol for the unit.
   * @param name   The name of the unit.
   * @throws UnitExistsException    Attempt to redefine an existing unit.
   * @throws NameException          Bad unit name.
   * @throws UnitParseException     A string specification couldn't be parsed.
   * @throws SpecificationException Bad string specification.
   * @throws UnitDBException        Problem with the unit database.
   * @throws PrefixDBException      Problem with the prefix database.
   * @throws NameException          Bad unit name.
   * @throws OperationException     Improper unit operation.
   * @throws UnitSystemException    Problem with UnitSystem.
   */
  private void as(final String symbol, final String name)
          throws UnitExistsException, NoSuchUnitException,
          UnitParseException, SpecificationException, UnitDBException,
          PrefixDBException, OperationException, NameException,
          UnitSystemException {
    addSymbol(symbol, name);
  }

  /**
   * Tests this class.
   */
  public static void main(final String[] args) throws Exception {
    final UnitDB db = StandardUnitDB.instance();
    System.out.println("db.get(\"meter\")=" + db.get("meter"));
    System.out.println("db.get(\"meters\")=" + db.get("meters"));
    System.out.println("db.get(\"metre\")=" + db.get("metre"));
    System.out.println("db.get(\"metres\")=" + db.get("metres"));
    System.out.println("db.get(\"m\")=" + db.get("m"));
    System.out.println("db.get(\"newton\")=" + db.get("newton"));
    System.out.println("db.get(\"Cel\")=" + db.get("Cel"));
    System.out.println("db.get(\"Roentgen\")=" + db.get("Roentgen"));
    System.out.println("db.get(\"rad\")=" + db.get("rad"));
    System.out.println("db.get(\"rd\")=" + db.get("rd"));
    System.out.println("db.get(\"perches\")=" + db.get("perches"));
    System.out.println("db.get(\"jiffies\")=" + db.get("jiffies"));
    System.out.println("db.get(\"foo\")=" + db.get("foo"));
  }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.util;

import ucar.unidata.util.StringUtil2;

import java.io.File;
import java.net.URI;

/**
 * Networking utilities.
 *
 * @author caron
 */
public class URLnaming {

  /// try to figure out if we need to add file: to the location when writing
  static public String canonicalizeWrite(String location) {
    try {
      URI refURI = URI.create(location);
      if (refURI.isAbsolute())
        return location;
    } catch (Exception e) {
      //return "file:" + location;
    }
    return "file:" + location;
  }

  /**
   * This augments URI.resolve(), by also dealing with file: URIs.
   * If baseURi is not a file: scheme, then URI.resolve is called.
   * Otherwise the last "/" is found in the base, and the ref is appended to it.
   *
 For file: baseURLS: only reletive URLS not starting with / are supported. This is
   * apparently different from the behavior of URI.resolve(), so may be trouble,
   * but it allows NcML absolute location to be specified without the file: prefix.
   *

   * Example :

   * base:     file://my/guide/collections/designfaq.ncml
   * ref:      sub/my.nc
   * resolved: file://my/guide/collections/sub/my.nc
   *

   *
   * @param baseUri     base URI as a Strng
   * @param relativeUri reletive URI, as a String
   * @return the resolved URI as a String
   */
  public static String resolve(String baseUri, String relativeUri) {
    if ((baseUri == null) || (relativeUri == null))
      return relativeUri;

    if (relativeUri.startsWith("file:"))
      return relativeUri;

    // deal with a base file URL
    if (baseUri.startsWith("file:")) {

      // the case where the reletiveURL is absolute.
      // unfortunately, we may get an Exception
      try {
        URI uriRelative = URI.create(relativeUri);
        if (uriRelative.isAbsolute())
          return relativeUri;
      } catch (Exception e) {
        // empty
      }

      if ((relativeUri.length() > 0) && (relativeUri.charAt(0) == '#'))
        return baseUri + relativeUri;

      if ((relativeUri.length() > 0) && (relativeUri.charAt(0) == '/'))
        return relativeUri;

      baseUri = StringUtil2.substitute(baseUri, "\\", "/"); // assumes forward slash
      int pos = baseUri.lastIndexOf('/');
      if (pos > 0) {
        String baseDir = baseUri.substring(0, pos + 1);
        if (relativeUri.equals(".")) {
          return baseDir;
        } else {
          return baseDir + relativeUri;
        }
      }
    }

    // non-file URLs

    //relativeUri = canonicalizeRead(relativeUri);
    try {
      URI relativeURI = URI.create(relativeUri);
      if (relativeURI.isAbsolute())
        return relativeUri;

      //otherwise let the URI class resolve it
      URI baseURI = URI.create(baseUri);
      URI resolvedURI = baseURI.resolve(relativeURI);
      return resolvedURI.toASCIIString();

    } catch (IllegalArgumentException e) {
      return  relativeUri;
    }
  }

  public static String resolveFile(String baseDir, String filepath) {
    if (baseDir == null) return filepath;
    if (filepath == null) return null;
    File file = new File(filepath);
    if (file.isAbsolute()) return filepath;

    if (baseDir.startsWith("file:"))
      baseDir = baseDir.substring(5);

    File base = new File(baseDir);
    if (!base.isDirectory())
      base = base.getParentFile();
    if (base == null) return filepath;
    return base.getAbsolutePath() + "/" + filepath;
  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.units;

/**
 * Provides support for implementations of unit converters.  A unit converter
 * class may be created by subclassing this class and implementing the methods
 * convert(double), convert(float[] input, float[] output)
 * , and convert(double[] input, double[] output) of
 * interface Converter.
 *
 * @author Steven R. Emmerson
 */
public abstract class
ConverterImpl
    implements	Converter
{
    /**
     * Constructs from a "from" unit and a "to" unit.
     * @param fromUnit		The unit from which to convert.
     * @param toUnit		The unit to which to convert.
     * @throws ConversionException	The units are not convertible.
     */
    protected
    ConverterImpl(Unit fromUnit, Unit toUnit)
	throws ConversionException
    {
	if (!fromUnit.isCompatible(toUnit))
	    throw new ConversionException(fromUnit, toUnit);
    }

    /**
     * Factory method for creating a unit converter.
     * @param fromUnit		The unit from which to convert.
     * @param toUnit            The unit to which to convert.
     * @throws ConversionException      The units are not convertible.
     */
    public static Converter
    create(Unit fromUnit, Unit toUnit)
	throws ConversionException
    {
	return fromUnit.getConverterTo(toUnit);
    }

    /**
     * Converts a numeric value.
     * @param amount		The numeric value to convert.
     * @return			The converted numeric value.
     */
    public final float
    convert(float amount)
    {
	return (float)convert((double)amount);
    }

    /**
     * Converts an array of numeric values.
     * @param amounts		The numeric values to convert.
     * @return			The converted numeric values in allocated
     *				space.
     */
    public final float[]
    convert(float[] amounts)
    {
	return convert(amounts, new float[amounts.length]);
    }

    /**
     * Converts an array of numeric values.
     * @param amounts		The numeric values to convert.
     * @return			The converted numeric values in allocated
     *				space.
     */
    public final double[]
    convert(double[] amounts)
    {
	return convert(amounts, new double[amounts.length]);
    }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.ft.point.writer;

import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.dataset.conv.CF1Convention;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.*;
import ucar.nc2.ft.*;
import ucar.unidata.geoloc.EarthLocation;
import ucar.ma2.*;

import java.util.*;
import java.io.IOException;

/**
 * Write a CF 1.6 "Discrete Sample" point file.
 *
 * <pre>
 *   writeHeader()
 *   iterate { writeRecord() }
 *   finish()
 * </pre>
 *
 * @see "http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.6/cf-conventions.html#idp8294224"
 * @author caron
 * @since Nov 23, 2010
 */
public class WriterCFPointCollection extends CFPointWriter {
  //private Map<String, Variable> varMap  = new HashMap<>();

  public WriterCFPointCollection(String fileOut, List<Attribute> globalAtts, List<VariableSimpleIF> dataVars,
                                 CalendarDateUnit timeUnit, String altUnits, CFPointWriterConfig config) throws IOException {
    super(fileOut, globalAtts, dataVars, timeUnit, altUnits, config);
    writer.addGroupAttribute(null, new Attribute(CF.FEATURE_TYPE, CF.FeatureType.point.name()));
    writer.addGroupAttribute(null, new Attribute(CF.DSG_REPRESENTATION, "Point Data, H.1"));
  }

  public void writeHeader(PointFeature pf) throws IOException {
    List<VariableSimpleIF> coords = new ArrayList<>();
    coords.add(VariableSimpleImpl.makeScalar(timeName, "time of measurement", timeUnit.getUdUnit(), DataType.DOUBLE)
            .add(new Attribute(CF.CALENDAR, timeUnit.getCalendar().toString())));

    coords.add(VariableSimpleImpl.makeScalar(latName,  "latitude of measurement", CDM.LAT_UNITS, DataType.DOUBLE));
    coords.add(VariableSimpleImpl.makeScalar(lonName,  "longitude of measurement", CDM.LON_UNITS, DataType.DOUBLE));
    Formatter coordNames = new Formatter().format("%s %s %s", timeName, latName, lonName);
    if (altUnits != null) {
      coords.add( VariableSimpleImpl.makeScalar(altName, "altitude of measurement", altUnits, DataType.DOUBLE)
                      .add(new Attribute(CF.POSITIVE, CF1Convention.getZisPositive(altName, altUnits))));
      coordNames.format(" %s", altName);
    }

    super.writeHeader(coords, null, pf.getDataAll(), coordNames.toString());
  }

  protected void makeFeatureVariables(StructureData featureData, boolean isExtended) throws IOException {
    // NOOP
  }

  /////////////////////////////////////////////////////////
  // writing data

  public void writeRecord(PointFeature sobs, StructureData sdata) throws IOException {
    writeRecord(sobs.getObservationTime(), sobs.getObservationTimeAsCalendarDate(), sobs.getLocation(), sdata);
  }

  private int obsRecno = 0;
  public void writeRecord(double timeCoordValue, CalendarDate obsDate, EarthLocation loc, StructureData sdata) throws IOException {
    trackBB(loc.getLatLon(), obsDate);

    StructureDataScalar coords = new StructureDataScalar("Coords");
    coords.addMember(timeName, null, null, DataType.DOUBLE, timeCoordValue);
    coords.addMember(latName,  null, null, DataType.DOUBLE, loc.getLatitude());
    coords.addMember(lonName,  null, null, DataType.DOUBLE, loc.getLongitude());
    if (altUnits != null) coords.addMember(altName, null, null, DataType.DOUBLE, loc.getAltitude());

    StructureDataComposite sdall = new StructureDataComposite();
    sdall.add(coords); // coords first so it takes precedence
    sdall.add(sdata);

    obsRecno = super.writeStructureData(obsRecno, record, sdall, dataMap);
  }

}